A Clean Architecture
What is clean architecture and why do we even care about it? The clean architecture approach is a software design pattern and a guideline proposed by Robert C. Martin (Uncle Bob). This architecture urges us to build a cleaner code and more structured code.
So why do we care about it, why is it a good fit (at least, for me) to be used with a Node.js project, especially with TypeScript?
While there is a catch like a more complex code and it may be overkill for some simple or some quick projects, we have some benefits that we can get from following this guideline like great maintainability, testability, and flexibility.
There are some layers of clean architecture: infrastructure layer, adapter/controller layer, application, and domain layer.
The infrastructure layer consists of any kind of framework and infrastructure that we use in our application. For example, database connections and instances, message brokers, caches, and even external API clients.
The adapter/controller layer is like a bridge between our infrastructure and application layer. In this case, the infrastructure layer is like API listeners (for example, we can have express.js requests or message broker subscribers as the infrastructure). The listened API then will be handled by this controller layer. Please be aware that in this layer, we don’t want to have any business rules and logic. It is purely just to receive any inputs from the infrastructure layer, maybe transform it, and call the usecase from the application layer.
A Great Match for Microservice
A microservice consists of some operations and logic that are scoped and contained within a boundary. As it is named, the service size is small and micro.
Using a clean architecture for microservice can be a good option since this microservice can be tidy and clean. Of course, it will be greatly structured and easily understood by other developers.
One of the best benefits of using microservice is that the business logic and rules are encapsulated and separated from the infrastructure and whatever we do outside them. We can change, move, and replace any infrastructure we want without touching and interfering with any code inside the business rules. This one key of clean architecture can be suited perfectly by using Dependency Injection (DI).
By using a clean architecture, we can improve the service maintainability and testability. In this case, we can easily test and maintain them without interfering with other projects and even with other use cases or business logic.
Dependency Injection (DI)
Dependency Injection is a design pattern that we can use for this matter. As the name suggests, we can inject any service and dependencies into a class or an entity inside our application. In this case, we can inject any kind of dependencies into our business use cases. A business use case can be contained in a class inside our application layer.
DI sometimes also relies on interface usage. Every use case class should have an interface that works as a template or mold of what kind of dependencies we can inject into a use case class.
So what we can conclude is, that we can inject any dependencies into a use case, that will be used within the application and business flow. Sometimes, injection can occur by using a class constructor inside our use case class.
Of course, there are a lot of resources online that you can read to learn more about this pattern.
A Clean Architecture Example with Node.js
Since we use DI and OOP patterns for this architecture, it will be the best fit to use TypeScript as our project language. While it is strict and has a lot of benefits for this project, TypeScript has a huge community and is an actively maintained technology that we can use for a long time.
Here is an example of a Node.js project with TypeScript using a clean architecture approach and DI pattern using tsyringe
.
There is an alternative to using NestJS to implement our approach. But, sometimes it may be an overkill approach since NestJS has a higher level of complexity and may add some complication for the developers.
In the project, we are using tsyringe
since it is a library maintained by Microsoft and of course, it should be compatible and be a great fit with TypeScript.
This project uses a clean architecture approach by separating them into three major directories: application, controller, and infrastructure. All entities (domains) are included inside the application layer.
We are using express.js as the web server listening for API requests. In this case, all the handlers will be placed inside the controller directory which has a class that points into a use case class. Dependency injection mostly happened here with the creation of use case classes while injecting them with their required dependencies.
The application layer consists of some use cases. In this project, each use case has its separate class file. For example, creating and fetching posts has different use case classes. This is done for a cleaner approach and structured dependency injections.
The infrastructure layer has an example of using a mock class too. In this case, I have an example of a use case implementing an interface that needs a repository. The repository can be switched in the controller class with an infrastructure with the same interface implementation.
There is also an implementation of DTO uses. We use DTO to make sure the business domain and entities are decoupled with other objects. DTOs are primarily used as the use case input and output. It also can be used to regulate and standardize API response structure.
I happily share this project with you all for your inspiration and use. Of course, this is far from a perfect project that includes all the rules and flows by the guidelines. But, regardless I hope you like it. I am also very open to some feedback and improvement. Please share your thoughts in the comments!