Chapter 16: Independence
"A good architecture must support... the independent developability of the system's components."
๐ For New Learnersโ
Three Kinds of Independenceโ
A well-structured system enables three types of independence:
- Use-case independence โ adding a new use case doesn't require modifying existing ones
- Development independence โ different teams can work on different use cases simultaneously without stepping on each other
- Deployment independence โ components can be deployed independently, without deploying the entire system
Use-Case Decouplingโ
Each use case should be a separate, isolatable slice through the system. A use case should touch only the layers it needs to:
Use Case: Place Order
โ Controller (web adapter)
โ PlaceOrderUseCase (application layer)
โ Order (domain entity)
โ OrderRepository (persistence)
Use Case: Cancel Order
โ Different controller
โ CancelOrderUseCase (separate class)
โ Same Order entity
โ Same OrderRepository
These two use cases share the domain entity and repository interface but are otherwise independent. A bug in CancelOrderUseCase cannot affect PlaceOrderUseCase.
Operational Decoupling (Scaling)โ
Some use cases need to run at different scales:
- Processing orders: high volume, must scale
- Generating PDF invoices: low volume, can be slow
- Administrative reports: rare, resource-intensive
If these are decoupled, you can scale order processing independently of invoice generation. If they're coupled in one monolithic process, you must scale everything together.
The Decoupling Mode Dilemmaโ
Martin presents a key insight: the right decoupling mode depends on factors you often don't know at design time:
| Mode | Deployment Unit | Communication |
|---|---|---|
| Source level | Single process | In-memory calls |
| Deployment level | Separate JARs, same process | In-memory calls |
| Service level | Separate processes | Network calls |
Starting with source-level decoupling (clean boundaries in a monolith) lets you migrate to service-level decoupling later โ without rewriting business logic.
๐ฌ Senior Deep Diveโ
The Fallacy of "Start with Microservices"โ
The popular advice "build microservices from day one" violates the principle of deferring decisions. Martin argues:
- Microservice boundaries require understanding the domain's natural seams
- Natural seams only become apparent after working with the domain for months
- Premature service boundaries are hard to change โ network calls don't refactor as easily as method calls
- A well-structured monolith can be extracted into microservices later; a poorly structured microservice system cannot be consolidated without a rewrite
The Modular Monolith is an underrated intermediate:
- Single deployable unit (operational simplicity)
- Strict module boundaries enforced by Maven modules
- Use cases independent at the code level
- Can be split into services exactly where the seams are proven
Decoupling in Spring: The Progressionโ
Stage 1: Source-level decoupling (monolith)
// All in one Spring Boot application
// Boundaries enforced by package/module structure
// Communication: direct method calls
orderService.place(command);
Stage 2: Deployment-level decoupling (modular monolith)
// Separate Maven modules, single deployable JAR
// domain-jar, application-jar, web-jar โ assembled into app.jar
// Communication: still method calls
Stage 3: Service-level decoupling (microservices)
// Separate Spring Boot applications
// Communication: REST or messaging
orderServiceClient.place(command); // HTTP call to order-service
// OR
eventPublisher.publish(new PlaceOrderCommand(command)); // async message
The business logic in each stage is identical. Only the communication mechanism and deployment unit change.
Duplication: Real vs. Accidentalโ
Martin warns against conflating two types of duplication:
Accidental duplication: two pieces of code that look similar but change for different reasons. They should NOT be combined โ they will diverge.
Real duplication: two pieces of code that are truly the same concept. They SHOULD be extracted and shared.
Prematurely deduplicating accidental duplication is a common coupling mistake. Two use cases that have similar request/response structures might look like they share a DTO โ but if they evolve independently, coupling them via a shared DTO causes unnecessary friction later.
Summaryโ
| Concept | Key Point |
|---|---|
| Three independence types | Use-case, development, deployment |
| Decoupling modes | Source โ deployment โ service level |
| Monolith first | Defer service extraction until seams are understood |
| Modular monolith | Clean module boundaries + single deployment = best of both worlds |
| Accidental vs. real duplication | Don't deduplicate things that merely look similar but evolve separately |