25 · Architecture Decisions & Trade-offs
The judgment layer: choosing between monolith and microservices, SPA, MPA, and micro-frontends, and the rendering/deployment shapes — with concrete cases (e-commerce, dashboards, content sites). The senior skill isn’t knowing the patterns (those are in
09–13,24); it’s knowing which one fits this problem, team, and stage — and being able to defend it.
Positioning
Most architecture mistakes aren’t wrong patterns — they’re right patterns applied at the wrong time or scale. Microservices for a 4-person startup, micro-frontends for a single-team app, or a monolith for a 200-engineer org all fail for the same reason: the architecture didn’t match the organizational and load reality. This file is a decision framework. The recurring meta-principle: start simple, decompose when a real force (team scale, deploy contention, scaling hotspots) demands it — and let Conway’s Law (your system mirrors your org) guide the seams.
Foundations: the forces that decide
Before any choice, name the forces:
- Team size & topology — how many teams, how independent, what release cadence? (The dominant factor.)
- Scale & load shape — uniform or hotspots? Traffic now vs projected?
- Domain complexity — how many distinct bounded contexts (
10)? - Time-to-market & team experience — can you operate the complexity you’re proposing?
- Change rate — which parts change together, which independently?
- Consistency needs — strong transactions vs tolerable eventual consistency (
13).
The honest default for almost everything early: a well-structured modular monolith with a SPA (or SSR app). You decompose outward only when these forces push you.
Decision 1 — Monolith vs Microservices (backend shape)
Monolith
One deployable unit containing all functionality.
- Pros: simplest to build/deploy/test/debug; one codebase; easy refactoring across boundaries; ACID transactions; no network between components; fastest for small teams. Lowest operational cost.
- Cons: at scale, deploys couple teams (everyone ships together); one bug/leak can take down everything; can’t scale subsystems independently; large codebases get tangled if not modularized; tech stack is uniform.
Modular Monolith (the underrated middle)
A monolith with strict internal module boundaries (clear interfaces, enforced dependencies — FSD/bounded contexts, 10). Gets most of the maintainability of services without the distributed-systems tax. The recommended default, and the structure that makes a later extraction to services cheap. “Monolith first” (Fowler): build modular, extract services only when needed.
Microservices
Many independently deployable services around bounded contexts (13).
- Pros: independent deploy/scale per service; team autonomy; fault isolation; tech heterogeneity; targeted scaling of hotspots.
- Cons: distributed-systems complexity (network failures, eventual consistency, sagas,
13); operational overhead (orchestration, observability,24); harder debugging/testing; data consistency is hard; easy to build a “distributed monolith” (all the cost, none of the independence) if boundaries are wrong.
The e-commerce case (concrete)
Picture an online store with catalog, search, cart, checkout/payments, inventory, orders, recommendations, reviews, user accounts.
- Early-stage / single team / modest traffic → modular monolith + SPA (or SSR for SEO on catalog pages). Everything in one well-structured app. Don’t pay the microservices tax to serve thousands of orders/day. Most stores never need more.
- At scale (large catalog, traffic spikes, many teams) → selective microservices, extracted by force, not fashion:
- Search has a totally different scaling/storage profile (Elasticsearch, read-heavy, spiky) → extract early.
- Checkout/Payments has strict consistency, security (
17), and compliance needs, and must stay available during catalog outages → isolate so a recommendations bug can’t break purchases. - Inventory needs strong consistency and high write contention during sales → its own service with careful transaction/saga design (
13). - Recommendations/Reviews are non-critical, independently scalable, and can degrade gracefully → good candidates to split (and to fail-soft in the UI via partial responses,
12). - Catalog is read-heavy and cache-friendly → CDN/edge + read replicas (
24). - A Black Friday spike is the canonical justification: you want to scale checkout and inventory independently of reviews, which a monolith can’t do.
- The trap: a 3-person team copying Amazon’s microservice diagram. Amazon has thousands of engineers; their architecture mirrors their org (Conway). Yours should mirror yours.
Rule of thumb: monolith (modular) until team-scale or independent-scaling pressure is real; then extract the highest-contention, most-independent, or most-critical-to-isolate capabilities first — not everything at once.
Decision 2 — SPA vs MPA vs Micro-Frontends (frontend shape)
SPA (Single-Page Application)
One JS app, client-side routing, the page never fully reloads.
- Best for: app-like, highly interactive products behind a login where SEO is secondary — dashboards, admin panels, SaaS tools, editors, internal tools, Rian’s kind of design-system-driven apps.
- Pros: rich interactivity, smooth transitions, clear client/API separation. Cons: initial JS cost/hydration (
07), SEO/first-paint work needed, you ship a lot of JS. (Modern SSR/RSC meta-frameworks,08, blur “SPA vs MPA” — Next gives SPA-like navigation with server rendering.)
MPA (Multi-Page Application) / server-rendered
Server returns full HTML per route; navigation reloads.
- Best for: content/SEO-driven, mostly-static or low-interactivity sites — marketing sites, blogs, docs, news, e-commerce catalog/product pages where SEO and first paint are paramount. Islands (Astro,
07) are the modern MPA sweet spot: HTML-first with interactivity only where needed. - Pros: great SEO/first paint, minimal JS, simple. Cons: less fluid interactivity, full reloads.
Micro-Frontends (MFE)
Many independently deployed frontend pieces composed into one product (09).
- Best for: large organizations with many autonomous teams whose release cadences collide in a single SPA — large enterprise platforms, big e-commerce with separate teams owning catalog/checkout/account, products integrating multiple legacy apps.
- Pros: independent deployment + team autonomy at frontend scale; incremental migration; tech flexibility. Cons: significant complexity — version skew, shared-dependency management, style isolation, payload/perf overhead, cross-MFE communication, governance (
09). Performance is usually a cost you manage, not a benefit.
MFE vs SPA — how to choose
- One team, or a few that coordinate fine? → SPA (or modular monolith frontend). MFEs would add cost with no benefit. The most common right answer.
- Many teams blocked by a shared frontend deploy / a monolithic SPA that’s become a merge battleground? → MFEs. The driver is organizational, exactly like microservices (
09, Conway). - A modular monolith frontend (feature-sliced,
10) solves most “our SPA is getting big” problems without MFEs. Reach for MFEs when the pain is independent deployment across teams, not just code size.
The e-commerce frontend, mixed in practice
A real store often mixes strategies (07): catalog/product = SSR/SSG for SEO (MPA-ish), cart/checkout = interactive SPA-like islands or a focused SPA, and at large scale different teams own different MFEs (catalog team, checkout team, account team) composed via a shell or Next Multi-Zones (08). The rendering strategy and the team/deployment strategy are separate decisions — don’t conflate them.
Decision 3 — Rendering strategy
Covered in depth in 07 (CSR/SSR/SSG/ISR/streaming/RSC/islands/resumability) with its own decision tree. The one-line mapping: SEO + content → SSG/SSR/islands; app behind login → CSR/SPA or SSR app; mixed product → per-route strategy (Next lets you choose per route, 08). Rendering strategy is orthogonal to monolith-vs-microservices and largely orthogonal to SPA-vs-MFE.
Comparing the software-architecture styles (onion / clean / hexagonal)
For the internal structure of an app (not its deployment shape), see the detailed onion vs clean vs hexagonal comparison in 10 — they’re three drawings of the same dependency rule, differing mainly in framing (ports vs rings vs four-ring synthesis) and ceremony. The decision there is “how much structure does this app’s complexity justify,” and the answer is usually the pragmatic synthesis (10), not a dogmatic full implementation.
Decision-making tools
- Conway’s Law — you ship your org chart; design team boundaries and architecture boundaries together. Inverse Conway Maneuver: shape teams to get the architecture you want. (See Team Topologies.)
- Architecture Decision Records (ADRs) — short markdown files capturing context → decision → consequences for each significant choice, version-controlled. The senior habit for making trade-offs explicit and reviewable.
- Reversible vs irreversible decisions (Bezos “one-way/two-way doors”) — move fast on reversible choices; deliberate on hard-to-undo ones (your datastore, your service boundaries).
- YAGNI / start simple (
20) — the cheapest architecture you can refactor out of beats the elaborate one you guessed wrong. - Fitness functions — automated checks that guard architectural properties (dependency rules, bundle budgets,
14/15) so they don’t erode.
Worked example: an ADR for the e-commerce checkout
# ADR-014: Extract Checkout into a separate service + MFE
## Context
Catalog and checkout are owned by different teams. Catalog deploys ~10×/day;
checkout needs strict change control (PCI, payments). A shared monolith + single SPA
forces coordinated deploys and risks a catalog bug affecting purchases (revenue).
Black-Friday load on catalog must not degrade checkout capacity.
## Decision
- Extract a Checkout *service* (own DB, saga for order→payment→inventory, idempotency keys).
- Extract a Checkout *micro-frontend*, composed via the shell; communicate via URL + a
validated event bus, not shared state. Catalog stays SSR for SEO; checkout is an SPA-like MFE.
- Keep everything else in the modular monolith / single SPA for now (YAGNI).
## Consequences
+ Independent deploy + isolation of the critical path; independent scaling on spikes.
+ Clear PCI boundary (17).
− New distributed-systems complexity (eventual consistency, tracing, 13/24).
− Frontend now manages a shared design-system version across two apps (11).
Revisit if a third team needs its own slice → consider broader MFE adoption.
The value is the explicit trade-off: what you gain, what you pay, and the trigger to revisit.
Pitfalls & gotchas
- Resume-driven / hype-driven architecture — microservices/MFEs/K8s because they’re impressive, not because a force demands them.
- Distributed monolith — services/MFEs that must deploy together: all the cost, none of the autonomy (
13). - Premature decomposition — splitting before you understand the domain; boundaries end up wrong and expensive to move.
- Conflating rendering, deployment, and service decisions — they’re independent axes; decide each on its own merits.
- Ignoring Conway’s Law — architecture that fights the org chart loses.
- Big-bang rewrites instead of incremental extraction (strangler-fig migration).
- No ADRs — decisions and their rationale get lost; the next team re-litigates or cargo-cults.
- Copying a FAANG diagram at startup scale.
Interview questions
- When would you choose a monolith over microservices — and vice versa?
- What’s a modular monolith and why is it often the right default?
- Walk through decomposing an e-commerce backend: what would you extract first and why?
- SPA vs MPA vs MFE — give a concrete product fit for each.
- When do micro-frontends actually pay off, and what do they cost?
- How does Conway’s Law influence these decisions?
- Why are rendering strategy and deployment architecture separate decisions?
- What is a distributed monolith and how do you avoid it?
- What goes in an ADR, and why keep them?
- How do you decide “start simple” vs “design for scale now”?
Recommendations
- Default to a modular monolith + SPA/SSR app; structure it so extraction is cheap later.
- Decompose to microservices/MFEs by force (team-scale, independent deploy, scaling hotspots, isolation needs) — extract the highest-contention/most-critical-to-isolate parts first (checkout, search, inventory in e-commerce).
- Treat rendering, deployment, and service decisions as independent axes; mix strategies per route/area.
- Let Conway’s Law guide boundaries; use the Inverse Conway Maneuver deliberately.
- Record significant choices as ADRs; protect them with fitness functions (dependency lint, bundle budgets).
- Favor reversible moves; deliberate hard on one-way doors (datastore, boundaries).
- Migrate incrementally (strangler fig), never big-bang.
Books & references
- “Building Microservices” (2nd ed) — Sam Newman. Decomposition, when (not) to split, the migration patterns. (
13) - “Monolith to Microservices” — Sam Newman. The strangler-fig and incremental extraction playbook — directly about this decision.
- “Team Topologies” — Skelton & Pais. Conway’s Law operationalized; team shapes that produce good architectures. Essential for the org dimension.
- “Fundamentals of Software Architecture” & “Software Architecture: The Hard Parts” — Richards & Ford. Trade-off analysis, ADRs, fitness functions, distributed vs monolithic styles. The best modern architecture-decision books.
- “Building Micro-Frontends” — Luca Mezzalira (
09); martinfowler.com — “MonolithFirst,” “Microservices,” “Micro Frontends,” and the Technology Radar (adoption cautions). - “Designing Data-Intensive Applications” — Kleppmann (the systems trade-offs underneath) (
24). - adr.github.io — Architecture Decision Records formats and tooling.
Connections
09-micro-frontends.md— the MFE pattern in depth (composition, federation, the hard parts).13-microservices-and-orchestration.md— what microservices entail once you choose them (sagas, EDA, consistency).24-system-and-infrastructure-architecture.md— the infra cost of each choice (scaling, orchestration, observability).10-frontend-architecture.md— internal app structure + the onion/clean/hexagonal comparison.07-rendering-strategies.md— the rendering axis and its decision tree.08-nextjs-and-meta-frameworks.md— Multi-Zones as an MFE/composition mechanism; per-route rendering.20-programming-principles.md— KISS/YAGNI/start-simple as the meta-rule behind all of this.