alama@world: ~/
./toggle-theme
Modular Monoliths thumbnail

# Modular Monoliths

by Simon Brown
Published on November 22, 2018
Rating:
Software Architecture Modularity Monoliths

Description

If you want evidence that the software development industry is susceptible to fashion, just go and take a look at all of the hype around microservices. It's everywhere! For some people microservices is 'the next big thing', whereas for others it's simply a lightweight evolution of the big service-oriented architectures that we saw 10 years ago 'done right'. Microservices is by no means a silver bullet though, and the design thinking required to create a good microservices architecture is the same as that needed to create a well structured monolith. And this begs the question that if you can't build a well-structured monolith, what makes you think microservices is the answer?

Venue

GOTO 2018

cat notes.md

My Notes

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:

  1. 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.
  2. 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/.
  3. 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.