Incremental Platforms: Monolithic Modular Architecture
Single deployment but with a clear separation into modules or components
This is the first issue of a series dedicated to Incremental Architectures for a SaaS product.
👩🏽💻 To grow as a Software Engineer or Tech Lead, on the technical ladder, is not only about human skills but also about growing on technical stuff. That’s why I’m putting in place this series. Hope you like it.
Because this is the first issue, let’s put some background and common language.
An incremental architecture is an architecture that is designed to grow.
☝🏼 Also, why an incremental architecture is something you should desire as a Software Engineer or Tech Lead?
Two points:
You (should) care about code.
Your code and your system evolve, and that’s natural.
If your software and the architecture of your platform are not easily modifiable, you will have a problem. You need to have a platform that is easily modifiable, so you can iterate fast without having to redo the whole system.
👉🏼 There are a set of patterns and architectures that, following them, will make your platform modifiable, ie, incremental.
In today’s issue, we will address one of them, the Modular Monolithic Architecture. More concretely:
What is it?
How to achieve it.
Benefits.
Let’s start.
🪨 What is a Modular Monolithic Architecture?
A single deployable unit builds the application under a modular monolithic architecture.
It divides its internal structure into defined modules or components. Building an application as a single deployable unit using the modular monolithic approach enables you to harness simplicity while enjoying modular and decoupled system advantages.
Let’s concrete a bit.
☝🏼 What do I mean by “single deployment”?
👉🏼 All the code is packaged and deployed as one application.
☝🏼 And by “dividing its internal structure”?
👉🏼 The application is divided into modules or components that encapsulate specific functionalities or areas of the domain. Each module has clearly defined interfaces and communicates with other modules through well-established contracts.
This logical separation means that, although the application is deployed as a monolith, its internal design is organized with a separation of concerns, making maintenance and evolution easier.
Keep in mind “separation of concerns”, because it’s key for having an incremental architecture, whether within a software or a platform.
Good, those were great words that make a lot of sense. Now, how can we do that in the real world?
🎯 How to achieve it?
This is the good stuff that will give you the value you're looking for.
By defining clear boundaries and well-defined contracts:
Explicit Interfaces: Each module should expose public interfaces that clearly define how it communicates with other modules.
Encapsulation: Avoid allowing other modules to directly access a module’s internal implementation; instead, use patterns like facades.
By separation of concerns (I told you before!):
Internal Cohesion: Ensure that each module has a unique and well-defined responsibility.
Low Coupling: Minimize direct dependencies between modules to facilitate independent maintenance and evolution.
By code organization and package structure:
Packages: Use a package structure and naming conventions that reflect the modular division of the business.
Isolated Logical Modularization: Even though the deployment is monolithic, structure the code so that each module can, if necessary, be extracted or moved to another context in the future.
By testing and automation:
Use Case Testing: Implement tests to validate all the use cases of your monolith, to ensure that changes in one area do not inadvertently affect others.
Continuous Integration: Use CI/CD pipelines to constantly validate the integrity and communication between the modules.
By dependency management and communication:
Dependency Injection: Use dependency injection patterns to manage inter-module relationships, making it easier to replace or update components.
Internal Messaging and Events: Consider using messaging patterns or events to decouple modules so that they communicate via messages rather than direct calls. A simple queue will help you with this.
Follow those guidelines and I assure you will achieve a proper monolithic architecture.
Now, just in case you want to read some arguments in favor of this approach, let’s enumerate some of them.
🗝️ Benefits
Yeah, never thought a monolithic architecture could have some benefits, right? It does when you do it right.
Simplicity in Deployment and Operations
Single Artifact: Having one package or binary to deploy reduces the complexity of the software lifecycle. Not always 3 microservices are needed to solve 1 problem.
Lower Operational Overhead: There is no need to manage the additional infrastructure often required by distributed architectures (such as networks, orchestrators, or inter-service communication configurations).
Be careful on this aspect though. If this monolith provides a service that must be 100% up and running, you still have some overload in the operations, like modifying the load balancer to take the newer version of your artifact and get rid of the old one.
Ease of Development and Refactoring
Code Coherence: Developing within the same process and memory space allows for simpler transaction handling and shared access to resources, without the need for complex inter-service communication mechanisms.
Continuous Refactoring: Internal modularization makes it easier to refactor and evolve the system, as changes can be isolated to specific modules without impacting the entire application.
Performance and Consistency
Local Execution: Calls between modules are typically in-memory invocations (functions or methods), resulting in superior performance compared to network communications.
Transactional Integrity: Maintaining consistency and handling transactions is simpler within a monolith, as all operations occur in the same environment.
Facilitates Domain-Driven Design
DDD Application: The approach supports implementing Domain-Driven Design (DDD) by dividing the system into modules that align with “bounded contexts.” This helps in modeling the domain clearly and maintaining well-defined responsibilities/concerns (told you before!).
Let’s wrap up for today.
✨ Summary
In today’s issue, you’ve learned about the Modular Monolithic Architecure, which can help you to achieve an Incremental Platform.
You see? not everything is microservices, a monolith could be something good too 😊.
A modular monolithic architecture offers a balance between the simplicity of a monolith and the benefits of a modular, decoupled design.
It is ideal for early-stage projects, small teams, and systems where transactional integrity and consistency are important.
By following best practices such as defining clear boundaries, employing dependency injection, organizing the code effectively, and maintaining rigorous testing and continuous integration, you can ensure that your system evolves sustainably and remains flexible.
This approach also provides a solid foundation that can be gradually transitioned into a more distributed architecture, such as microservices, should the business requirements change over time.
Something else you would like to share? Write it in the comments, I read and reply to all!
We are more than ✨843 Optimist Engineers✨!! 🚀
Thanks for your support and feedback, really appreciate it!
You’re the best! 🖖🏼
𝘐𝘧 𝘺𝘰𝘶 𝘦𝘯𝘫𝘰𝘺𝘦𝘥 𝘵𝘩𝘪𝘴 𝘱𝘰𝘴𝘵, 𝘵𝘩𝘦𝘯 𝘤𝘭𝘪𝘤𝘬 𝘵𝘩𝘦 💜. 𝘐𝘵 𝘩𝘦𝘭𝘱𝘴!
𝘐𝘧 𝘺𝘰𝘶 𝘬𝘯𝘰𝘸 𝘴𝘰𝘮𝘦𝘰𝘯𝘦 𝘦𝘭𝘴𝘦 𝘸𝘪𝘭𝘭 𝘣𝘦𝘯𝘦𝘧𝘪𝘵 𝘧𝘳𝘰𝘮 𝘵𝘩𝘪𝘴, ♻️ 𝘴𝘩𝘢𝘳𝘦 𝘵𝘩𝘪𝘴 𝘱𝘰𝘴𝘵.