Thinking out architecture for Node.js microservicescalendar_today Posted 9 months ago · 4 minute-read · Technology
I’ve used Node and Express a lot in the past few years, especially when building self-contained services that I can later integrate with larger applications made in other languages.
Until not too long ago, I didn’t really think of a Node project structure for my apps, I just did everything inside of the Express callback for each route. The tasks my micro-services had to do weren’t complex at all, so I didn’t really have a hard time maintaining them (swapping libraries in and out, changing dependencies and configuration, modifying business logic). It was just ‘all there’.
This meant a few things:
- The app structure has to be layered. This ensures that the business logic is properly separated from other logic, for example, data access logic. Continue reading to see what layers I chose.
- Dependencies have to be injected into each and every module instead of using Node’s famous “require()” everywhere. This makes it easy to swap dependencies, change implementations, and pre-configure them before the module gets to use them. Dependency injection can be achieved with the factory pattern. This would also make every app module easily testable.
- Express shouldn’t have all the control. Express is good at receiving and serving HTTP requests, nothing more. Your app is what’s going to be good at carrying out your business-related tasks. To achieve this, it’s important that we use interface adapters.
- Use interface adapters to interface with external dependencies. In the future, we might not like how a certain dependency carries out some task, so we’ll want to create a custom implementation ourselves. With adapters, we can change our dependencies and/or implementations without affecting the rest of our app’s code.
- Prefer fatty entities to dumb, thin entities. Entities (like Shopping Cart, User, Product etc.) should know best how to be themselves. Fatty entities are those that instead of just having database fields in them, getters and setters, they also include validation logic (business rules) related to the entity. This helps keep our business logic contained in our entity instead of it being all over the place.
- Use functions instead of classes. Node development shines mostly when using a functional approach to build an application. This is mostly a matter of preference, and this is my preference.
Bob Martin’s Clean Architecture is what inspired me to build my Node apps this way:
Clean Architecture is a way to separate concerns that makes your apps scalable, maintainable and testable.
In Clean Architecture, we have:
- Entities, that include business rules related to the entity.
- Use cases, application-specific business rules. What your application can do. i.e. list products, add item to shopping cart, apply a discount.
- Interface adapters, to interface with our dependencies to not become tightly-coupled to them, and adapt the resulting data into a format that our application can best understand. i.e. a MongoDB interface adapter implemented as repositories.
- External drivers and frameworks. A REST API client, a MongoDB Node.js driver, Express…
The dependency flow goes from external drivers to entities, as you can see in the chart — this means that inner layers cannot depend on outer layers.
It also means that modifications made in the inner layers will “ripple” back to the outer layers. This is why the business rules are in the two innermost circles — they are less likely to change over time.
In my next post on this topic, I’ll go into more detail about the technical aspects of implementing clean architecture in Node.js, what project structure I follow, with code examples and probably a GitHub companion repo.
Just wanted to share where my mindset is at right now! 😊