ADR-006: Spring Modulith as Application Framework
Field |
Value |
|---|---|
Status |
|
Date |
2026-05-12 |
Deciders |
Full Team |
Supersedes |
– |
Superseded by |
– |
Context
We are building an internal backend service with clearly separable business domains. We need a framework that supports modular decomposition within a single deployable unit, enforces module boundaries through tooling rather than convention alone, and integrates well with our chosen language (ADR-005) and communication strategy (ADR-003).
Decision Drivers
Module boundaries must be explicit and verifiable, not just documented
Single deployable unit preferred to keep operational complexity low for a small team
Future extraction of individual modules into independent services must remain feasible
Strong existing Spring Boot knowledge in the team
Considered Options
Option A – Spring Modulith
Option B – Classic Microservices
Option C – Plain Spring Boot Monolith
Decision
Chosen option: Option A – Spring Modulith provides tooling-enforced module boundaries within a single deployable unit, which matches our need for domain separation without the operational overhead of a distributed system.
Rationale
Spring Modulith builds on top of Spring Boot and adds explicit, verifiable module boundaries via
package conventions and @ApplicationModuleTest. It treats modules as first-class citizens:
their public API is defined by what is visible in the api/ package, and cross-module access
to internals is caught at test time. This gives us the structural discipline we want without
requiring a distributed system. It also integrates naturally with Spring Application Events
(ADR-0003), which is our chosen inter-module communication mechanism.
Pros and Cons of the Options
Option A – Spring Modulith
✅ Tooling-enforced module boundaries — violations caught by
@ApplicationModuleTest✅ Single deployable unit — simple CI/CD, monitoring, and local development
✅ Built-in support for event-based inter-module communication
✅ Familiar Spring Boot foundation — minimal learning curve for the team
✅ Migration path preserved — clean module boundaries enable future service extraction
❌ Relatively young framework — some edge cases may require workarounds
❌ Shared deployment — a fault in one module can affect the whole service through the shared database
Option B – Classic Microservices
✅ Independent deployability and scaling per service
✅ Strong fault isolation between services
❌ Network overhead and latency for every inter-service call
❌ Requires distributed tracing, service discovery, and independent pipelines — operational complexity a small team cannot absorb
❌ Data consistency across service boundaries requires significant additional engineering
Option C – Plain Spring Boot Monolith
✅ Simplest possible setup — no additional conventions to learn
❌ No tooling enforcement of module boundaries — they erode silently over time
❌ A monolith without boundaries makes future extraction into services very costly
Consequences
Positive:
Module boundaries are part of the build and test pipeline, not just documentation
The team works on a familiar Spring Boot foundation with a well-defined extension
Negative / Trade-offs:
The entire service must be scaled as one unit
Spring Modulith’s relative immaturity means the team should follow release notes closely
Follow-up actions:
Add
@ApplicationModuleTestto the standard test setup for every moduleDefine inter-module communication conventions → ADR-003
Define internal module structure → ADR-004