There are a lot of articles about why you should decouple your backend to move faster, but very few content on how to practically do it.
This article is a practical approach that we use at Woovi to decouple our backend endpoints breaking them into packages in a monorepo.
As we scale we can easily convert them to their own microservices.
Creating a decoupled backend
We should first start designing what endpoint patterns we are going to expose.
In the image above we have five patterns: /login
, /register
, /home
, /api
, and /provider
.
You can think about each of these patterns as separate services, but we will deploy them in a single service to simplify deployment and maintenance.
For each endpoint pattern, we are going to create a package in our monorepo, and each package will have its own Koa Router.
The monorepo for these 5 "services" will be like this:
packages
- login
- register
- home
- api
- provider
A sample koa router code is described below:
import Router from '@koa/router';
export const getApiRouter = () => {
const apiRouter = new Router();
apiRouter.all('/api/status', statusMiddleware);
apiRouter.get('/api/v1/charge', chargeGet);
apiRouter.post('/api/v1/charge', chargePost);
apiRouter.get('/api/v1/transaction', transactionGet);
apiRouter.post('/api/v1/transaction', transactionPost);
return apiRouter;
}
Only using a Koa router is not enough to expose these endpoints.
We need a Koa app to make this possible.
We will create a new package to combine all these "services" endpoints and expose them.
packages
...
- woovi-server
It will need an app.ts
file that has the Koa app with some middleware and will combine all these "services" endpoints:
const app = new Koa({
proxy: true, // we are behind a proxy
keys: [config.JWT_KEY],
});
//Add your middleware here
app.use()
// combine all routes here
const apiRouter = getApiRouter();
const homeRouter = getHomeRouter();
app.use(apiRouter.routes()).use(apiRouter.allowedMethods());
app.use(homeRouter.routes()).use(homeRouter.allowedMethods());
Then you can run the server
(async () => {
const server = createServer(app.callback());
server.listen(config.SERVER_PORT)
})();
This way each "service"/package keeps its code separate, following the domain-driven design.
Breaking in microservices
As you scale, you can decide to move to microservices.
Instead of using a single server to serve all your endpoints, you can have many servers.
If you follow the approach above, it is as simple as creating a new Koa app for each package that you want to decouple in a new service.
In Conclusion
This approach lets you decouple your backend, making it clear the boundary of each separate "service" without all the burden of a full microservices approach. You get the benefits without the drawbacks.
You can read more about DDD here Domain Driven Design using a Monorepo
Woovi
Woovi is a Startup that enables shoppers to pay as they like. Woovi provides instant payment solutions for merchants to accept orders to make this possible.
If you want to work with us, we are hiring!
Top comments (3)
When you break to microservices there a lot of questions you need to answer generally:
And so on, would be nice to have a text about this
Hi, cool post
Could share a little bit about the decision on using Koa? Since Woovi is a payment solution product I wonder if that's something to do with that.
Koa is the evolution of express, it would be the same with express