BoatyardX

Introduction to Modular Monoliths

Optimal Architecture

LinkedIn
Facebook

In recent years, software architectures have evolved rapidly, reflecting the changing needs of software developers and the business environment. The shift from traditional monoliths to microservices has brought numerous benefits, but even their proponents acknowledge that using them involves significant costs, making them useful only in the context of an extraordinarily complex system.

Traditionally, application frameworks have provided structural guidance, such as annotations in the Spring Framework (@Controller, @Service, @Repository).

However, recently, there has been a shift to a paradigm where it is recommended that the code structure aligns as closely as possible with the problem domain the application addresses, leading to better-structured applications that are easier to understand and maintain.Microservice-based systems have become ubiquitous in recent years. However, modular monolithic systems have recently regained popularity.

“You shouldn't start a new project with microservices, even if you're sure your application will be big enough to make it worthwhile.”

In this context, the concept of “Modular Monoliths” has emerged as an intermediate solution that combines the simplicity and coherence of a monolith with modular structure and organisation. In this article, we will explore what a modular monolith is, its advantages and disadvantages, and discuss scenarios where this architecture can be considered optimal.

What are Modular Monoliths? 

A modular monolith is an architectural approach where an application is developed as a single artifact but is divided into distinct and autonomous modules, while maintaining a single codebase and deployment. Each module has clear responsibilities and is developed, tested, and maintained independently of the other modules. This allows teams to retain the advantages of a traditional monolith, such as version management simplicity, while benefiting from a modular structure that facilitates organisation, scalability, and maintainability of the application. 

Key Features 
  • Separation of Responsibilities: Each module has a well-defined responsibility and is isolated from other modules. 
  • Well-Defined Interfaces: Modules communicate with each other through clearly defined interfaces, reducing tight dependencies. 
  • Independent Development: Each module can be developed and tested separately, allowing teams to work in parallel without interfering with each other. 
  • Single Artifact: All modules are combined into a single artifact, simplifying the deployment and monitoring process. 
Advantages of Modular Monoliths 
  • Simplicity of Implementation and Deployment: Modular monoliths allow for the deployment of a single artifact in a production environment, reducing infrastructure complexity. This is particularly beneficial for small teams or organisations that do not want to manage the complexity of a microservices architecture. 
  • Consistency and Maintenance: Managing a single codebase facilitates applying updates and maintaining consistency. Since all modules are part of the same application, synchronisation and compatibility between modules are guaranteed. 
  • Performance and Efficiency: Modular monoliths can offer superior performance by avoiding network latencies associated with microservice communication. Additionally, data consistency is easier to ensure in a monolithic architecture. 
  • Dependency Management: Dependencies between modules are easier to manage since they are all part of the same artifact. This reduces the risk of incompatibilities and facilitates refactoring. 
Disadvantages of Modular Monoliths 
  • Scalability Limitations: Although modular monoliths offer a degree of scalability, they cannot achieve the flexibility and granularity provided by microservices. For applications that require independent scalability of components, the modular monolith can become a constraint. 
  • Code Management Complexity: As the application grows, managing a large codebase can become complicated. It requires strict discipline in development and a clearly defined module structure to avoid “spaghetti code.” 
  • Technology and Language Restrictions: All modules in a modular monolith must be developed using the same technologies and programming languages, which can limit the flexibility of teams to choose the most suitable tools for each component. 
  • Development and Deployment Time: Although modular monoliths can reduce initial development time, eventually, managing and deploying updates can become more time-consuming, especially if modules are not well isolated. 
When to Implement a Modular Monolith? 
  • Small or Medium-Sized Projects: For small or medium-sized applications, where the complexity and volume of code do not justify the infrastructure complexity required for microservices, the modular monolith is an ideal choice. It allows for rapid development and simplified deployment. 
  • Small Development Teams: Small development teams can benefit from the modular monolith, as it simplifies coordination and collaboration. By avoiding the need to manage a distributed ecosystem, teams can work more efficiently. 
  • Rapidly Evolving Projects: In the initial stages of product development, where requirements are constantly changing, the modular monolith allows for rapid and iterative adaptation without introducing the additional complexity of microservices. It provides a solid foundation that can be scaled and refactored as the application evolves. 
  • High-Performance Systems: For applications that require high performance and low latency, the modular monolith can offer a more efficient solution compared to distributed architectures. Network latencies are reduced, and data consistency is easier to manage within a single application.

Implementation – Spring Modulith 

Spring Modulith is a project from Spring, in our opinion, second in importance only to Spring Boot, which supports program developers in building modular monolithic applications.

It helps structure code around domains, making it easier to manage and evolve.
 

Application Modules

We can consider the domain or business modules of our application as direct sub-packages of the main application package. In a Spring Boot application, a module is a unit of functionality consisting of the following parts: 

  • An API exposed to other modules, implemented through Spring bean instances or events published by the module. 
  • Internal implementation components that should not be accessed by other modules. 
  • References to APIs exposed by other modules in the form of Spring bean dependencies or configuring an event listener that listens for events generated by other modules. 
				
					├───pom.xml 
├───src 
├───main 
│ ├───java 
│ │ └───main-package 
│ │ └───module A 
│ │ └───module B 
│ │ ├───sub-module B 
│ │ └───module C 
│ │ ├───sub-module C 
│ │ │ MainApplication.java 
				
			

Verifying the Modular Structure 

 

We can use the verify() method from ApplicationModules to identify if our code’s modularity respects the intended constraints. Spring Modulith uses the ArchUnit project to provide this capability. 

				
					@Test
void verifiesModularStructure() {
    ApplicationModules modules = ApplicationModules.of(Application.class);
    modules.verify();
}
				
			

Documenting Modules 

We can document the relationships between the modules of a project. Spring Modulith offers the generation of diagrams based on PlantUML. 

				
					@Test
void createModuleDocumentation() {
    ApplicationModules modules = ApplicationModules.of(Application.class);
    new Documenter(modules)
        .writeDocumentation()
        .writeIndividualModulesAsPlantUml();
}
				
			

Conclusion

Modular monoliths represent a balanced approach in software architecture, combining the simplicity and efficiency of the traditional monolith with the organisational benefits of microservices. In the right context, they can offer an optimal solution for developing and scaling applications, allowing teams to focus on delivering functionality quickly without sacrificing performance or internal flexibility. Although not a universal solution, modular monoliths are worth considering for projects that fit the discussed scenarios, offering a viable compromise between monolithic structure and microservices complexity. 

This approach provides development teams with an efficient way to manage application complexity as they evolve, ensuring that performance and maintenance remain manageable. Modular monoliths, therefore, represent an important step in the evolution of software architectures, offering a welcome balance between tradition and innovation. 

 

Need help architecting your next software project? Our team at BoatyardX specialises in building scalable, maintainable applications. Get in touch! 

References 

  • “Modular Monolith: A Primer”, Simon Brown, 2019. 
  • “Microservices For Greenfield?”, Sam Newman, 2015. 
  • “MonolithFirst”, Martin Fowler, 2015. 
  • “Microservices vs. Monoliths: The Case for a Symbiotic Design”, Mark Richards, 2016. 

Read more tech topics