Matryoshka Architecture ๐ช
One recursive pattern. Every level. From app to action.
The Problem
Spring Boot has no official guidance for package structures beyond tutorials. Existing approaches each solve part of the puzzle:
| Approach | Strength | Weakness |
|---|---|---|
| Package-by-Layer | Easy to start | No cohesion, no encapsulation |
| Package-by-Feature | High cohesion | No answer for cross-cutting concerns |
| Hexagonal / Clean | Strong boundaries | Massive boilerplate, over-engineered |
| Spring Modulith | Module verification | No guidance for internal structure |
None of them address all levels consistently. That's the gap Matryoshka fills.
The Solution
One recursive pattern, applied at every level:
{level}/
โโโ config/ โ framework setup (top-level only)
โโโ common/ โ shared code (any level)
โโโ {domain}/ โ bounded context, use case, sub-module
App โ Bounded Context โ Use Case โ Action โ same structure, all the way down.
What a Use Case Looks Like
One endpoint, one class, no service layer:
@RestController
@RequestMapping("/api/v1/policies/drafts")
@Transactional
class CreatePolicyDraft {
record Request(String holderName, Coverage coverage) {}
record Response(UUID id, String holderName, Status status) {}
private final PolicyDraftRepository repo;
private final TsidGenerator tsid;
@PostMapping
Response handle(@RequestBody Request req) {
var draft = PolicyDraft.create(
tsid.next(),
req.holderName(),
req.coverage()
);
repo.save(draft);
return new Response(
draft.id(),
draft.holderName(),
draft.status()
);
}
}
Extract a service only when a second caller appears.
Enforcement
This isn't just documentation โ it's enforceable:
- ArchUnit verifies dependency rules (no
configimports from domain) - Spring Modulith ensures BC-to-BC communication only via Services or Events
- CI Integration fails the build on violations
Get Started
Includes 23 Architecture Decision Records, arc42 documentation, and a practical ruleset.