Mind Map
# Modular Monoliths
## The Model-Code Gap
- Mismatch between architecture diagrams and code
## Four Packaging Styles
- Package by Layer
- Package by Feature
- Ports and Adapters
- Package by Component
## Boundaries
- Fitness functions
- Java 9 Modules
## Monolith vs Microservices
- Compare modular monolith to microservices
- Use agility as a quality attribute
Key Takeaways
The Model-Code Gap
Simon opens with the argument that abstractions used to describe software should reflect the code — and vice versa. Drawing on George Fairbanks' Just Enough Software Architecture, he calls the mismatch the model-code gap.
- Architecture discussions use words like module, component, service, subsystem, layer
- None of these are first-class keywords in Java (or most languages)
- Reverse-engineering a diagram from code produces classes/interfaces/packages — not the module boxes drawn in architecture diagrams
- Fix: adopt an architecturally-evident coding style — code structure should mirror architectural intent
Simon dogfoods this with Structurizr, a modular monolith running on Java + Spring + Pivotal Cloud Foundry — no Docker, Kubernetes, or microservices.
Four Ways to Package Code
Walks through the common ways to organise a Java codebase (though the ideas apply broadly).
1. Package by Layer — horizontal slicing (web, service, data)
- The default because every book, sample project, and conference demo uses it
- Cargo-cult programming: we do it because everyone else does
- Uncle Bob's Screaming Architecture critique: every enterprise codebase looks the same instead of shouting what it does
- Most feature changes cut across every layer anyway — poor efficiency
2. Package by Feature — vertical slicing (all orders code together)
- Higher cohesion; easier to locate feature code (though modern IDEs make that argument weaker)
- Trickier when features need to link (e.g. orders → customers)
3. Ports and Adapters — hexagonal / clean / onion
- Domain code in the middle, technology adapters on the outside
- Rule: outside depends on inside, never the reverse
- Often cargo-culted: not all frameworks need wrapping. Wrapping Spring MVC to keep domain code framework-agnostic is nuts — Spring MVC is already an abstraction over HTTP
- 100 web pages → 100 adapters. 4 database tables → 4 adapters. Choose your wrapping wisely
4. Package by Component — Simon's hybrid
- Bundles everything belonging to a component (controller + service + data access) in one place, one component per Java package
- Applies the c4 model's abstractions to code: system → containers → components → code
- Separates interface from implementation — same idea as a microservice, but enforced via package-protected classes instead of a network boundary
The Public Keyword Problem
The critical insight of the talk: all four packaging styles look syntactically identical if every class is public.
- Packages then serve only as organisation (folders), not encapsulation
- Remove the packages from a class diagram and layered, ports-and-adapters, and package-by-component collapse into the same picture
- The fix: use access modifiers properly
- Interfaces public where they're the entry point
- Implementation classes package-protected (Spring will still instantiate them)
- Fewer public things = fewer possible dependencies
- The public surface area of an internal API should match architectural intent
Enforcing Architectural Boundaries
- Architectural principles ("web should never call data") plus "we trust our developers" doesn't scale — humans take shortcuts under sprint pressure
- Fitness functions (from Building Evolutionary Architectures) — assert code properties at build time
- Tools: ArchUnit, JQAssistant — declare rules like "types in
**/web shall not access **/data"
- Feels like a hack (external tooling to police the language) but useful today
- Longer term: use the compiler and access modifiers properly. Language limitations are real (no sub-package concept in Java) but that's what we have
Beyond Package Protection
- Java 9 modules distinguish public from published. Public types not listed in the module manifest aren't callable from outside
- Split source trees / Maven modules — physical separation. Recommended for ports & adapters (stops adapters from calling each other) but slows builds. Find the sweet spot
Monolith vs Microservices
- Microservices give you decomposition and modularity "for free" via network boundaries — that's why teams reach for them
- Common failure mode: take a 15-year-old big ball of mud, rewrite it in the same style but with synchronous HTTP between pieces = distributed big ball of mud that must be lockstep-deployed. Now you're paying for Docker, Kubernetes, log aggregation, and monitoring on top of the mess
- Fairer comparison: modular monolith vs microservices, not "unmaintainable monolith vs microservices"
- Use agility as a quality attribute — which parts of your system need to move fast? Put those in services, keep the rest in the monolith
- Choose microservices for their benefits (scalability, resilience, independent deployment) — not because your codebase is a mess. Extracting mess into services doesn't fix mess
Lost Design Skills
- Industry has forgotten how to describe design. People do design, but can't articulate the principles
- Decomposition strategies (functional, volatility-based) are on Wikipedia — go read them
- Parnas' 1970s paper on modularisation still applies; microservices talks show it with "module" crossed out and "service" written in
- CRC (Class-Responsibility-Collaborator) cards — a Rational Unified Process-era workshop technique. Swap "class" for "component/module/service" and the same collaborative design workshop works at any level of abstraction
Notable Quotes
Original tweet that seeded the talk:
If you can't build a well-structured monolith, what makes you think microservices are the answer?
The architect Clifie's response (paraphrased):
I see you have a poly-structure monolith. Would you like me to convert it into a poly-structure set of microservices four orders of magnitude worse in the other direction?
Uncle Bob on Screaming Architecture:
If you look at the blueprints for a library, a house, or a museum, you can tell which it is just by looking at the blueprints. Enterprise codebases don't shout what they do — they shout web/business/data.
How I'll Apply This
Three concrete takeaways for any codebase I'm structuring:
- Audit public keywords. Muscle memory types
public on every class. Delete the ones that don't need it — this is the cheapest lever for real modularity in a language that already gives you package-private.
- Match the code to the diagrams. If I draw architecture with
orders and customers as top-level components, top-level folders should reflect that — not controllers/, services/, repositories/.
- Reach for a modular monolith first, extract services where agility demands it. Microservices are a decoupling mode, not a fix for messy design. Get the modularity right in-process, then extract only the parts that need independent deployability.
Related tools: ArchUnit, JQAssistant, and Structurizr for c4-style diagrams that stay in sync with code.