When developing modern applications it’s a common case that you need to reuse a class or module in another project, or in another service related to the current project, or add the ability to interact through other interfaces. For example, you have an HTTP API, but you also need WebSocket or gRPC. It’s the default case. I’d like to be able to easily and quickly expand the functionality based on the one already available.
The task seems trivial - you get the necessary code that you’re going to reuse and make it dependent only on input parameters. This advice from books doesn’t work, because we have integration with other parts of the system and relationships of entities. In these conditions, the task no longer seems so simple.
First problem - Entity relation.
Domain in our example is a bedroom with chairs, tables and beds.
So the table schema looks like:
This is a reasonable data schema that developers would use. Entities of bedrooms have ‘ManyToOne’ relationships to bedrooms.
If we develop HTTP API after this data design we would have troubles with flexible, reusable, scalable.
Sometimes it becomes necessary to split functionality between different microservices. And we just cannot provide this because for the microservice that is responsible for the chairs, you still have to take the part that is responsible for the bedrooms.
Data design is important. Because in such a case we got absolutely monolithic architecture without scalability, flexibility and code reusability.
Second problem - Infrastructure.
Protocols, databases, service integrations and any other infrastructure stuff has its own characteristics, which is impossible to abstract from. HTTP is HTTP, WebSocket is WebSocket. No “Network Communication Interface” for HTTP and WebSocket protocols. We write the code of the modules explicitly, not one module for all cases. In this case it’ll become a monolith that is hard to maintain, has many bugs and unexpected behaviour.
Solution
All problems can be resolved and it’s possible to make a program flexible. It’s also possible to do reusable code without any edits.
Relations problems resolve
No relations - no relationship problems. We reject that data design. Relationship between module entities - is a wrong way.
Bedroom, Chair, Table, Bed is 4 different modules that have nothing that unites ( no any references ). Bedroom is an aggregate for chairs, tables, beds. Data schema has changed and looks like:
In such a data schema we can move and separate the function of bottom components. We can even write it in another tech stack. The main thing is to provide a communication interface for the top component - Bedroom. So there will be the opportunity to get tables, chairs and beds in Bedroom.
True modularity
Modules are built according to the following principles.
- Low level modules contain only business logic.
- High level modules (Http, Websocket, etc…) are created as needed and provide external interfaces (http, ws, grpc) for low level modules.
- The module is only responsible for its domain and should not provide interfaces for inclusive modules. For example ‘Zoo’ module cannot provide any interface of child module ‘Dog’ to change name of entity
Low level module contains business logic only and works with persistante storages ( databases ). Structure of such module looks like:
Low level module doesn’t contain Controller, Websocket Gateways, gRpcHandlers. This module contains only business logic in services which depends on input arguments only.
Well, what happens if we need to provide a HTTP Api for this module? Low level module is complete. If you want to use it in HTTP just make a new Http module which uses a low level module and provide http controllers that works with methods of service in low level modules.
I removed the postfix “Module” on the diagram in “Chair”.
“HttpChair” wrapped “Chair” and provided a controller.
So we can describe each part of our application, and now architecture looks like:
We can provide any external interface to interact with low level modules like Http, Websocket, Rpc, etc… - Scalable and flexible !
Now we can create a Bedroom module which will be based on other modules.
We can provide Http and Websocket modules over Bedroom.
NOTE: HttpBedroom should provide an interface for the bedroom only. We cannot create, edit, remove chairs using HttpBedroom.
Now we can compile our Http Application with module combination that looks like
Also we can build WebSocket Application using websocket modules.
With this design we got flexibility, scalability and reusability. We can build any variations of our application using modules.
Code example:
https://gitlab.com/cimpleo/blog/nest-modules-architect-example