How to guarantee your Architecture integrity with Spring Modulith
Have you ever created a Monolith or a Microservice? Spring introduces a new way to build applications, which is by working with application modules driven by the domain.
Choosing an architectural style for your application is not easy, let’s see how Spring Modulith works and how it can help you in your next project.
Spring Modulith allows developers to build well-structured Spring Boot applications and guides developers in finding and working with application modules driven by the domain.
All the examples can be found on this GitHub repo.
Modulith Architecture
In our application, we are following a simple structure of packages, MVC architecture pattern. We have 3 modules, the aggregator, order, and user. The aggregator is responsible for exposing the endpoint and calling the order domains.
├── aggregator
│ ├── controller
│ ├── dto
│ └── service
│
├── order
│ ├── domain
│ ├── repository
│ └── service
│
└── product
├── domain
├── repository
└── service
With this approach, instead of having 3 microservices, we can have only one microservice with a single propose, in our case, creating an order and returning the orders. We can do it by checking with the product domain if the product exists or not and sending this information to the order domain.
The aggregator pattern is a design pattern commonly used in software architecture, particularly in the context of microservices and modular monolithic applications. It involves creating a service (the aggregator) that combines data or functionalities from multiple services or modules into a single response.
Decouple
The modules should be decoupled, and we can guarantee that by creating a test that checks the dependencies of the modules.
@ApplicationModuleTest
class ModularityTests {
ApplicationModules modules = ApplicationModules.of(ModulithSampleApplication.class);
@Test
void verifyModularity() {
modules.verify();
}
}
Spring Modulith also provides ways to open the module to be accessible from other modules. One way of doing it is by adding a package-info.java
to the folder we want to share and add this content:
@ApplicationModule(
type = ApplicationModule.Type.OPEN
)
package com.bomfim.sample.product.domain;
import org.springframework.modulith.ApplicationModule;
Note: This feature is intended to be primarily used with code bases of existing projects gradually moving to the Spring Modulith recommended packaging structure. In a fully-modularized application, using open application modules usually hints at sub-optimal modularization and packaging structures.
In our case, we are going to use the NamedInterface to expose only our current package.
@org.springframework.modulith.NamedInterface("domain")
package com.bomfim.sample.order.domain;
Documentation
We can also generate the documentation of the modules following the C4 model by creating a test like this one:
@ApplicationModuleTest
class ModularityTests {
ApplicationModules modules = ApplicationModules.of(ModulithSampleApplication.class);
@Test
void writeDocumentationSnippets() {
new Documenter(modules)
.writeModuleCanvases()
.writeModulesAsPlantUml()
.writeIndividualModulesAsPlantUml();
}
}
This is the result for the Aggregator module:
Conclusion
The Spring Modulith gives us a new way of building software, focusing more on the domain and modularity.
Pros
- Modularity: Encourages clear separation of concerns within a single application.
- Maintainability: Easier to manage and update than a large, monolithic codebase.
- Simplicity: Fewer deployment units compared to microservices, simplifying operations.
- Flexibility: Modules can be developed and tested independently, and can transition to microservices if needed.
Cons
- Deployment: Still requires a single deployment unit, which can be a bottleneck for large applications.
- Scalability: Limited to vertical scaling; modules are part of a single process and cannot be scaled independently.
- Complexity: Introduces additional complexity compared to a traditional monolith, requiring careful module management to guarantee the decouple.
The example below is only one example of how Spring Modulith can be used. It also allows us to work with events, so the modules can communicate with each other in an event-based approach, but we can talk about it in another article.
Did you know about Spring Modulith? Have you already used it?