The aim of this article is to share our years of experience with the modular single repository approach to microservice architecture. Over four years of production experience with this architecture at ConceptArea have proven the advantages of modular applications over microservices, such as accelerated development, faster bug fixing, easier version control and deployment composition flexibility. Article below is a demonstration of the essentials for implementing this pattern. You also can find hands-on examples of the pattern implementation with detailed workflows explanation for both monolithic and microservice hosting on example of two monorepo projects (Java/Kotlin and Node.js) by the link at the end of this article.
In modern software development, microservices architecture is recognized for its scalability and flexibility. However, managing multiple microservices across different repositories can introduce significant challenges related to version control, dependency management and operational complexity. Solution to these challenges is a modular application architecture that consolidates the benefits of microservices within a single repository, emphasizing domain-driven design and strict interface enforcement to achieve modularity and isolation.
Architecture Overview
This architecture involves storing all module code in a single repository, yet allowing each module within the codebase to be deployed independently as a microservice. Each deployment can be individually configured with specific communication protocols and addresses, providing both the isolation typically associated with microservices and the unified management of a monolithic system.
Layers
API: Handles various types of communication protocols like HTTP APIs, gRPC interfaces, etc.
Mediator: Routes messages from module-specific contracts to the modules based on module-specific configurations.
Module Contract: Each module exposes its own interface that describes incoming/outgoing messaging contracts.
Module: Encapsulates the business logic within its bounded context. Modules communicate through the Mediator using contracts of other modules. Modules have no reference to other modules but have reference to the contracts of other modules. Modules do not invoke other modules directly, instead they send messages from other modules' contracts to the mediator that is responsible for modules' invocation and returning response to the calling module.
Database: If data access is needed, each module should manage its own database context independently. This does not apply limitations for database context hosting composition, as long as there are no direct references between contexts (no foreign keys from/to another moduleβs data context) they could be stored in different tables of the same database.
Single host inter-module communication flow (Monolith deployment)
When the red module needs to send a message to the green module it invokes a mediator with a message from the green module contract.
Mediator checks configuration to find where the green module is hosted. In this scenario green module and mediator are hosted in the same process so mediator simply invokes green module handler for processing in the same process. Then returns a response from the green module to the red module that invoked it.
Multiple hosts inter-module communication flow (Microservice deployment)
When the red module needs to send a message to the green module it invokes a mediator with a message from the green module contract.
Mediator checks configuration to find where the green module is hosted. In this scenario the green module is hosted in the different process and is available by the certain protocol at the certain address specified in configuration so the mediator sends the message by the specified protocol to the specified address to the inter-module communication endpoint at host 2.
At the receiving end inter-module communication endpoint at host 2 passed a received message to the mediator where step 2 is occurring again. Mediator checks configuration to find where the green module is hosted. At the host 2 the green module is hosted together with the mediator so the mediator simply invokes the green module handler for processing in the same process. And returns a response from the green module to the inter-module communication endpoint that invoked the mediator. inter-module communication endpoint responds to the request from the host 1 with a response from the green module.
Mediator at the host 1 receives a response from the host 2 with the response from the green module. Then returns a response from the green module to the red module that invoked it in the first place.
Advantages and Comparison with Traditional Microservices Architecture
Conclusion
The modular application architecture within a single repository offers a powerful blend of microservices' agility and scalability with the consistency and simplicity of a monolithic approach. By adhering to strict domain boundaries and interface contracts, this architecture effectively addresses the typical challenges associated with microservices, such as service contract synchronization and complex debugging processes, making it a compelling choice for teams aiming to streamline development processes while retaining flexibility to adapt to changing requirements.
You can find a hands-on demonstration of the pattern implementation with detailed workflows explanation for both monolithic and microservice hosting on example of two monorepo projects (Java/Kotlin and Node.js) by following this link:
Top comments (0)