Java Microservices (SCS) vs. Spring Modulith
This article examines both architectures, highlighting their strengths, weaknesses, trade-offs, and guidance on when each is most appropriate.
Join the DZone community and get the full member experience.
Join For FreeThis article discusses the differences between a Java microservice architecture (SCS style) using Clean Architecture and a Spring Modulith architecture. It explores their strengths, trade-offs, and when to use each approach.
The architectures are demonstrated using two projects:
AngularMicroFrontendsAndMicroServices shows how to create an SCS-based system of microservices using Clean Architecture.
AngularPortfolioMgr demonstrates a single application built using the Spring Modulith framework and its architectural principles.
General Architecture Ideas
Both projects are based on Domain-Driven Design (DDD).
The ubiquitous language is used to describe the requirements to be implemented. It has the same meaning for business and technical stakeholders. It helps define what information is shared between domains and what that information means. For example, does “length” refer to a car or a flight, and what units are used (meters, miles, kilometers)?
The domain of a service is a bounded context. It defines the logic implemented in a module or service and clarifies where responsibility begins and ends. For example, in an online shop, one service or module may be responsible for payments. It receives all necessary product and pricing information and handles payment processing with external providers. Afterward, it hands the process over to the shipping service or module.
These general principles are implemented in both projects.
Java Microservices (SCS Style)
The project AngularMicroFrontendsAndMicroServices is built using microservices in SCS style. It offers flight booking, hotel booking, and payment processing for customers. These three workflow steps represent the bounded contexts and are implemented as separate SCS microservices.
Each SCS microservice has its own frontend, backend, and database. They can be developed and deployed independently. The SCS microservices are implemented using Clean Architecture.
The independence of the SCS microservices enables the use of different technology stacks. In this case, Angular is used in the frontend, and Spring Boot/Java, Spring Boot/Kotlin, and NestJS/TypeScript are used in the backends.
Communication between the SCS microservices is handled via MQTT messaging with Apache Artemis to decouple them. The system design looks like this:
Spring Modulith Application
The project AngularPortfolioMgr is a single application built according to the Spring Modulith framework.
The modules are common, findata, and stocks, implemented as packages. The findata and stocks packages are each bounded contexts. The services are located in the base packages (common, findata, stocks). The subpackages for entities, DTOs, and controllers are located below the base package and are considered private. This structure is enforced with Modulith tests and injection rules. The Modulith test breaks the build if it finds an architectural violation.
The base package structure looks like this:

The architectural idea is that each module is self-contained and exposes its interface through services in the base package. The implementation resides in the module’s private subpackages below the base package. This creates independence between the modules and enables concurrent development.
An exception is the common module, which is configured as OPEN and contains global setup and configuration that are required but not part of any specific domain. For example, cron jobs are located in the common module and use classes from the findata and stocks modules.
The AngularMicroFrontendsAndMicroServices
The project AngularMicroFrontendsAndMicroServices demonstrates how an SCS microservice architecture can be implemented using Clean Architecture within the services. The three separate SCS microservices are PaymentService, HotelService, and FlightService.
Each SCS microservice has its own domain, Angular frontend, Spring Boot or NestJS backend, and its own database. They communicate via MQTT messaging to decouple them. The system looks like this:

The PaymentService integrates the frontends of the other services using iframes. This decouples the Angular frontends of the FlightService and the HotelService, allowing Angular updates to be deployed independently.
The backend of the PaymentService is implemented in Spring Boot/Java, the backend of the HotelService is implemented in Spring Boot/Kotlin, and the FlightService backend is implemented with NestJS. This demonstrates the flexibility that an SCS microservice architecture offers. It is also possible to use other frontend frameworks such as React.
The backends themselves are implemented using Clean Architecture and look like this:

The infrastructure ring contains all implementations used to communicate with external systems such as databases, messaging systems, REST APIs, as well as configuration and application setup.
The use case ring contains all domain logic for the application’s use cases.
The domain layer contains all entities and DTOs of the domain. The architecture aims to keep them free of framework dependencies. In reality, switching from a relational database using JPA to a NoSQL database would require significant rewriting. Therefore, JPA and JSON annotations remain in the classes.
Conclusion – SCS Microservices
This architecture requires three repositories, three frontends, three backends, three build pipelines, and three deployments.
It offers a high degree of freedom for the development teams of each SCS microservice. The focus of this architecture is the independence of each service. This allows greater optimization when choosing languages, frameworks, and databases.
The cost of this flexibility is the complexity of multiple technologies, as well as separate build pipelines and deployments.
The AngularPortfolioMgr
The AngularPortfolioMgr project shows how the Spring Modulith framework can be used to implement a modular monolith. The modules are structured as follows:

The common module contains the configuration and setup of the application. It is open because, for example, cron jobs use entities from the findata and stocks modules.
To enable access to the subpackages of the findata and stocks modules, the following configuration is used:
@org.springframework.modulith.ApplicationModule(
type = ApplicationModule.Type.OPEN,
allowedDependencies = {"stocks", "stocks :: stocks.dto",
"stocks :: stocks.mapping.open", "stocks :: stocks.entity.dto",
"stocks :: stocks.entity", "findata", "findata :: findata.dto" }
)
package ch.xxx.manager.common;
import org.springframework.modulith.ApplicationModule;
This allows, for example, the common module to access the ‘findata.dto’ package. In the ‘findata.dto’ package, the following configuration is required:
@org.springframework.modulith.NamedInterface("findata.dto")
package ch.xxx.manager.findata.dto;
This defines the name ‘findata.dto’ and opens the package to other modules.
With these tools, access between modules can be controlled and dependencies can be limited. This provides separation of concerns. If the services in the base packages act as module interfaces and only DTOs and some cron job entities are shared, encapsulation of the modules can be maintained.
This enables a workflow similar to microservices but with less configuration and less code duplication. If more than one team works on the modulith, the work remains mostly independent as long as the module interfaces stay stable and the common code does not need to be changed.
If the ‘findata’ package needs to communicate directly with the ‘stocks’ package, the Spring Modulith framework provides event publication. This allows events to be sent and stored via JPA in the database between packages without direct coupling. This is the preferred method of communication between modules.
Spring Modulith tests and injection checks ensure that the architecture remains intact.
Clean Architecture is not used in the AngularPortfolioMgr project. It would need to be implemented in each module and would require additional effort for limited added clarity. Instead, this project uses a three-tier architecture to keep the amount of required code manageable.
Conclusion – Spring Modulith
The modulith can achieve most of the architectural goals of SCS microservices. It provides separation of bounded contexts in its modules and clear interfaces through services and DTOs. If necessary, teams can work mostly independently within the modules.
What Should Be Used?
The advantages of SCS microservices are:
- High independence
- High optimization potential
The advantages of the Spring Modulith are:
- Simple implementation
- Less code duplication
SCS microservices should be used if the code of each service changes frequently or if specialized frameworks or databases are required to handle load. If several teams are working on a larger project, it can be beneficial to split it into several SCS microservices to give teams more independence and increase development speed. It is often a good idea to define a standard technology stack, for example Angular/TypeScript/Spring Boot/(Java | Kotlin). This allows developers to support other teams or services more easily.
The Spring Modulith should be used if code changes are less frequent and a single database is sufficient in terms of performance. It is well suited for applications that do not change frequently and are maintained by one team. The complexity of the application can be managed through modules, and the code remains maintainable. It should be considered for applications that may grow in complexity over time, as it helps keep the architecture intact and scalable.
Published at DZone with permission of Sven Loesekann. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments