Choosing an Architecture

A practical decision guide. Compare every style in this course on one page, match them to your team, domain and scale, see how they combine, and learn how to evolve an architecture over time.

Intermediate⏱ 6 min readLesson 13 of 13#architecture#decision-guide#trade-offs#evolution

The big idea

There's no "best vehicle". A bicycle, a van, a bus and a plane are each perfect for some journeys and silly for others. Choosing an architecture is the same: match the style to the journey you're actually making, meaning your domain, team, scale and stage.

Architecture styles on one map: from inside one app to the whole systemArchitecture styles on one map: from inside one app to the whole system

Two decisions, not one

Most confusion comes from mixing two separate questions:

Drawing diagram…

You pick an answer to each question. For example: a modular monolith (1), with Hexagonal modules (2), cut along DDD bounded contexts (3), talking via in-process events.

All the styles on one page

Code organisation (inside one application)

StyleCore ideaBest forWatch out for
LayeredPresentation → business → dataSimple/CRUD apps, new teamsBusiness logic depends on the DB; changes cross all layers
MVC / MVVMSeparate view from model and logicUI code (server pages, SPAs, mobile)Only covers presentation
CleanCircles; dependencies point inward; use casesRich business logic, long-lived appsCeremony for simple CRUD
HexagonalCore + ports; adapters on driving and driven sidesMany entry points, swappable integrations, testabilityPorts for things that never change
OnionRings around the domain modelDomain-centric apps (.NET heritage)Same as Clean
Vertical slicesOrganise by feature, top to bottomFeature-heavy apps, CQRS, fast iterationDuplication without a shared domain

System shape

StyleCore ideaBest forWatch out for
MonolithOne deployableEarly products, small teamsTurning into a big ball of mud
Modular monolithOne deployable, strict modules✅ Most teams, most of the timeNeeds boundary enforcement
MicroservicesIndependently deployable servicesMany teams, very different scaling needsDistributed-systems complexity
Event-drivenComponents react to published factsMany reactions to the same facts, integration, bufferingEventual consistency, debugging
MicrokernelSmall core + plug-insExtensible products, per-customer rulesContract design and stability
ServerlessFunctions triggered by events, managed servicesSpiky workloads, event processing, zero-opsCold starts, limits, lock-in, cost at scale

A decision guide

Drawing diagram…

Quality attributes → styles that help

If you most need…Consider
Speed of delivery nowMonolith / modular monolith, layered or vertical slices, serverless for glue
Testability of business rulesHexagonal / Clean / Onion
Independent team autonomyModular monolith → microservices
Elastic scaling of hotspotsMicroservices or serverless for those parts
Extensibility by othersMicrokernel / plug-ins
Loose coupling between reactionsEvent-driven
Low operating cost and complexityModular monolith, serverless for spiky parts

They combine: a realistic example

Drawing diagram…

Each part uses the style that fits its needs, and that's normal. Consistency within a module matters more than uniformity across the whole company.

Evolutionary architecture

Architecture isn't decided once. Requirements, scale and teams change, so design for change:

Drawing diagram…
PracticeWhy
Delay irreversible decisions until you have evidence (last responsible moment)Avoid expensive guesses
Keep boundaries clean even inside a monolithFuture extraction becomes cheap
Fitness functions: automated checks for architecture rules (dependency rules, latency budgets)Guard the architecture continuously in CI
ADRs for every significant decisionFuture you understands the "why"
Strangler fig for big migrations: route traffic piece by piece to the new systemReplace legacy systems without a big-bang rewrite
Drawing diagram…

The strangler fig pattern: new features and migrated routes go to the new system until the old one can be switched off.

Warning signs you picked the wrong style

SymptomLikely problem
Every small change needs 5 services deployed togetherDistributed monolith; merge services or fix boundaries
Business rules scattered across controllers and SQLNeed a real domain core (Clean / Hexagonal)
Six layers of pass-through code for simple CRUDOver-engineering; use slices or simple layers there
"Nobody knows what happens when an order is placed"Event chains without visibility; add orchestration, tracing, an event catalog
Teams constantly blocked by each other in one codebaseMissing module boundaries or team alignment

Key takeaways

  • Separate the questions: system shape, code organisation, domain modelling. Answer each.
  • The safest default: a modular monolith along DDD bounded contexts, Hexagonal/Clean where logic is rich, simpler slices where it isn't.
  • Add events, microservices or serverless for specific, proven needs.
  • Mixing styles per module is normal; consistency inside a module matters most.
  • Architecture evolves: keep boundaries clean, record decisions in ADRs, automate rules with fitness functions, migrate with the strangler fig.