The Architecture Reference

Sty choosing · Architecture Styles · Advanced

Choosing an Architecture Style

The decision process — monolith vs distributed via the quantum, where data lives, sync vs async — plus worked monolith and distributed case studies.

Sty choosing Advanced ⏱ 5 min read Complete

🧭 Analogy

Choosing a style is like an architect siting a building. You don’t start with “I want a skyscraper” — you start with the plot, the budget, the climate, and who will live there, then the right structure becomes obvious. Pick the style first and you get a skyscraper on a beach: technically impressive, wrong for the conditions.

There is no universal best

Style choice is the culmination of trade-off analysis across characteristics, domain, data, and strategic goals. The answer is always “it depends,” and architectural fashion shifts for real reasons — past pain points, ecosystem changes (Kubernetes appeared from nothing), new capabilities (Docker), domain changes (M&A), and external factors like licensing cost. Know the trends so you can decide when to follow them and when to make exceptions.

The decision criteria

Be comfortable with: the domain (especially operational characteristics), the characteristics that impact structure, the data architecture (collaborate with DBAs), organizational factors (cloud cost, M&A), process/team/operations maturity (Agile maturity gates some styles), and domain/architecture isomorphism — matching topology to the problem (microkernel for customizability, space-based for many discrete operations).

Three determinations the architect must make

graph TD
Start["Identify driving characteristics"] --> Q{"How many quanta?<br/>One set of characteristics<br/>or many?"}
Q -->|"One"| Mono["Monolith<br/>(layered, microkernel,<br/>modular monolith)"]
Q -->|"Many"| Dist["Distributed<br/>(service-based, EDA,<br/>microservices, space-based)"]
Mono --> Data["Where does data live?"]
Dist --> Data
Data --> Comm["Communication:<br/>sync by default,<br/>async when necessary"]
Comm --> Out["Topology + ADRs +<br/>fitness functions"]
  1. Monolith vs distributed — via the architecture quantum (an independently deployable artifact with high functional cohesion and synchronous coupling). One set of characteristics across the system → a single-quantum monolith. Differing characteristics across parts → distributed.
  2. Where should data live? — a monolith assumes one or a few relational databases; a distributed system must decide which services persist and how data flows. Consider structure and behavior, and iterate.
  3. Communication style — sync or async? Sync is convenient but can hurt scalability/reliability; async helps performance/scale but brings data sync, deadlocks, race conditions, and debugging pain.

Default to synchronous

The book’s tip: use synchronous by default, asynchronous only when necessary. Async is powerful but expensive in complexity — reach for it to decouple operational characteristics (e.g., buffering a fast producer against a slow payment service), not by habit.

Worked case study — Silicon Sandwiches (monolith)

A national sandwich franchise wants online ordering. A single quantum suffices (simple, low budget), so the choice is between two monoliths:

  • Modular monolith — domain-centric components + single relational DB + single web UI; separate DB tables per domain to ease future distribution. Customization handled via an Override endpoint each domain references — a perfect fitness function.
  • Microkernel — core domain components + single DB; each customization a plug-in (common + local), matching the customizability characteristic through domain/architecture isomorphism. Synchronous communication suffices (no extreme performance or elasticity needs).

Worked case study — Going, Going, Gone (distributed)

A real-time auction platform where parts have different characteristics: the auctioneer needs far higher availability and reliability than any single bidder; the streamer is read-only and optimizable; scale and elasticity are bursty near the close. Microservices is chosen over event-driven because EDA separates by communication style while this problem separates by operational characteristics. The design resolves to five quanta — Payment, Auctioneer, Bidder, Bidder Streams, Bid Tracker — with async communication chosen mainly to accommodate the differing characteristics (e.g., Payment processes one payment per 500 ms; a queue buffers it against bursty bids).

graph TD
GGG["Going, Going, Gone"] --> Q1["Auctioneer<br/>(high availability)"]
GGG --> Q2["Bidder<br/>(bursty elasticity)"]
GGG --> Q3["Bidder Streams<br/>(read-only, optimizable)"]
GGG --> Q4["Bid Tracker"]
GGG --> Q5["Payment<br/>(1 / 500ms — queue buffered)"]

Aim for least worst, not best

The book is blunt: this is “not the correct or only or best design — but the least worst set of trade-offs.” Hunting for a perfect architecture is the Vasa-warship trap: over-specify and you capsize. Pick the fewest characteristics that matter, choose the structure that least compromises them, and design to stay iterative.

Record and govern the decision

The output is an architecture topology (plus hybrids where one style can’t satisfy conflicting needs), ADRs capturing the why for the hardest decisions, and fitness functions to protect the principles and characteristics over time — because changing an architecture once it is in place is very hard and expensive.

See also

When to use it — and when not

✅ Reach for it when

  • You are at the top-level structural decision for a new system or a migration.
  • You need a repeatable method instead of choosing a style by fashion.
  • Different parts of the system seem to need different characteristics.

⛔ Think twice when

  • You haven't yet identified the system's driving architecture characteristics.
  • You are looking for a universal 'best' style — there isn't one.

Check your understanding

Score: 0 / 4

1. What drives the monolith-vs-distributed decision?

If the whole system needs one set of characteristics, a single-quantum monolith suffices; differing characteristics across parts (e.g., auctioneer vs bidder) call for distributed.

2. What is the recommended default for communication style?

Sync is more convenient but can hurt scalability/reliability; async helps performance/scale but adds data-sync, deadlock, and debugging pain — so default to sync and reach for async only when needed.

3. Why did the Going, Going, Gone case study choose microservices over event-driven?

The auctioneer, bidder, and streamer needed different availability/scalability, so the domain-partitioned, quantum-rich microservices style fit better than EDA's communication-based separation.

4. What is the goal of a style decision, per the book?

Choosing is trade-off analysis; the output is the least worst design for the system's most important goals, recorded as ADRs and protected with fitness functions.

Comments

Sign in with GitHub to join the discussion.