-
Notifications
You must be signed in to change notification settings - Fork 536
Patterns recommendations and operations
As stated in Folder structure, the boilerplate follows an architecture inspired by DDD and Clean Architecture. It implies in some patterns and methodologies having to be followed to ensure separation of concerns and make the codebase more maintainable, I'll list of them here and suggest some links in the end with further info of patterns and practices that work well with this architecture.
Separation of concerns is ensured in this codebase in two ways:
- Separating the source code in layers, each layer with a different responsibility that should be followed;
- Each layer is also separated by actual concerns. When we talk about concerns in software development it's not about functionality, like
controllers
ormodels
, but about concepts or contexts, likeusers
,logging
,persistence
and so on.
All the patterns below have direct relation with separation of concerns.
The classes that represent the use cases of your application are called operations in the codebase, and they should be on the app
layer. They are instances of EventEmitter
, so they emit events for each of the possible outputs of the operation, like SUCCESS
, ERROR
, VALIDATION_ERROR
and the like.
The operations should have no idea about the existence of the infra
and interfaces
layer, it should just be a class that receives its dependencies through the constructor (we use dependency injection for that) and have a method that executes the operation emitting events for its outputs. Implementing it like this makes it way easier to test and enabled you to use then in any entry point, just instantiating it in the entry point passing the required data and handling the events. You can see an example of that in the UsersController
, that uses the CreateUser
operation, it just takes the data from the requests and handle it to the CreateUser
instance.
Inside the use cases you should also not touch the database directly, it's not the responsibility of the application layer to know how to work with data persistence. So we implement repositories that handle the persistence internally and inject them on the operations instances.
Attention for this point: the repositories interfaces (as in OOP interfaces, not the interfaces
layer) belongs to the domain
layer, and their implementations (that effectively talk to the database) belongs to the infra
layer, but since we don't have interfaces in JavaScript we just implement them on the infra
layer and inject it with the name of the imaginary interface. An example of that is the UsersRepository
, we use it in the operations like usersRepository
, but what we are really injecting is the SequelizeUsersRepository
that communicates internally with the SQL database. The important point here is that: The operation doesn't know how the repository works internally, it just knows the UsersRepository
methods and the parameters it expects.
The repository implementations should also return something that the domain
and the app
layers can have access, so that's why we use mappers for that, that receives stuff from the database and convert it to objects from the domain
layer. An example of that is the SequelizeUserMapper
, that knows how to convert an record from the users
table of the database to an instance of the User
domain class and vice versa.
Separating the persistence from the app
layer like this make it easier to test the app
layer with different simulated responses from the database, including errors.
You can know more about the subjects that we talked about here in the following links: