3.2

View in English

3.2 Architectural styles and patterns

Overview and motivation

An architectural style is a broad, reusable shape for organising a system: how it is decomposed, how the parts communicate, and where the boundaries fall. Choosing one is among the most consequential (and most misunderstood) decisions a large organisation makes. Too often the choice follows fashion (“everyone is doing microservices”) instead of the real constraints of the team, the domain, and the operational reality. You end up with one of two messes: a distributed system the organisation cannot operate, or a tangled monolith no one can change safely. Neither is the style’s fault. Both come from mismatching the style to the situation.

For large developer teams, styles matter above all because of Conway’s Law: a system’s structure tends to mirror the communication structure of the organisation that builds it. So an architectural style is also an organizational design decision. Splitting a system into services is really a decision about splitting teams, ownership, and on-call responsibility. Enterprises with hundreds of engineers can afford (and often need) fine-grained services with independent deployment, because that independence is how many teams ship without blocking one another. Force the same pattern on a single small team, and it inherits all the operational tax with none of the organizational benefit.

Government and enterprise settings pile on more constraints: long system lifetimes, strict change control, procurement cycles, integration with entrenched systems of record, and auditability. These favour styles that keep boundaries explicit and dependencies easy to inspect. This chapter surveys the major styles: monolith through microservices, event-driven architectures with CQRS and event sourcing, service mesh and gateway patterns, serverless, and the internal disciplines of hexagonal and clean architecture. More to the point, it helps you tell when each one fits.

See also: chapter 2.2 (software design principles, including Domain-Driven Design), chapter 3.1 (architecture fundamentals), and chapter 3.3 (distributed systems).

Key principles

  • Style follows forces, not fashion. Choose based on team size, domain complexity, load, and operational maturity, never because a technology is popular.
  • Coupling is the real enemy, not the number of deployables. A well-modularised monolith beats a distributed big ball of mud.
  • Distribution is a cost you pay for independence. Only split when the value of independent deployment, scaling, or fault isolation exceeds the cost of network calls, partial failure, and data consistency across services.
  • Boundaries should follow the business domain. Align services and modules with bounded contexts (each a self-contained domain model with its own explicit boundary), not with technical layers.
  • Design the inside well regardless of the outside. Hexagonal/clean layering keeps business logic independent of frameworks and infrastructure in every style.
  • Conway’s Law is inescapable, so use it. Design team boundaries and architecture together.
  • Start simpler than you think you need. You can extract services from a good modular monolith; un-distributing a premature microservice mess is far harder.

Recommendations

Default to a modular monolith; split with evidence

Start most systems as a single deployable unit with strong internal module boundaries: clear interfaces, no reaching into another module’s data, and enforced dependency rules. You get simple transactions, easy refactoring, and one thing to deploy and observe. Split a module into its own service only when you have a concrete reason: a part that must scale on its own, a team that needs to deploy on its own cadence, a fault domain you must isolate, or a technology requirement that differs from the rest. When you do split, split along bounded-context lines so each service owns its data and exposes a stable contract.

Know when microservices earn their keep

Microservices give you independent deployability, independent scaling, fault isolation, and the freedom to mix technologies. In return they demand mature CI/CD (continuous integration and continuous delivery), automated infrastructure, distributed tracing, service discovery, and an on-call culture. Ask yourself honestly: can your organisation run dozens of independently deployed services in production reliably? If the platform and operational maturity are not there, microservices just multiply your failure modes without delivering their benefits. Many organisations do best with a handful of coarse-grained services aligned to major domains rather than a swarm of tiny ones.

Use event-driven architecture where decoupling and asynchrony pay off

Event-driven architecture lets producers emit facts without knowing who consumes them. That buys you loose coupling, a buffer for load spikes, and an easy way to add new consumers. Use it where workflows are naturally asynchronous and reactive. CQRS (Command Query Responsibility Segregation) separates the write model from one or more read models, which helps when read and write loads or shapes differ sharply. Event sourcing stores state as an append-only log of events rather than as current state, giving you a perfect audit trail and time-travel. That is powerful for finance and government, where “how did we get to this value?” is a legal question, but it adds real complexity in versioning events, rebuilding projections, and reasoning about eventual consistency. Reach for these on purpose, not by default.

Apply gateway, BFF, and mesh patterns to manage many services

An API gateway gives external clients a single entry point, handling authentication, rate limiting, routing, and TLS (Transport Layer Security) termination. A Backend-for-Frontend (BFF) gives each client type (web, mobile, partner API) its own tailored aggregation layer, so you avoid a bloated one-size-fits-all API. A service mesh moves cross-cutting concerns (mutual TLS, retries, timeouts, traffic shifting, and telemetry) into a sidecar infrastructure layer, so application teams do not have to re-implement them. Add a mesh only once the number of services makes handling these concerns per-service unmanageable. For a few services, a mesh is more operational weight than it is worth.

Weigh serverless honestly

Function-as-a-service and managed serverless platforms take server management off your plate, scale to zero, and bill per use, which is great for spiky, event-driven, or low-baseline workloads and for small teams. The trade-offs are real: cold-start latency, execution-time and resource limits, harder local testing, possible vendor lock-in, and cost that can exceed provisioned infrastructure at sustained high volume. Use serverless where its economics and operational simplicity clearly win. Do not force steady, high-throughput core systems into it out of enthusiasm.

Keep business logic clean inside every service

Whatever the outer style, keep the inside clean with hexagonal (ports and adapters) or clean architecture: business rules at the centre, depending only on abstractions; frameworks, databases, and messaging at the edges as replaceable adapters. This keeps your valuable domain logic testable without infrastructure and portable across technology changes: a decisive advantage for long-lived government and enterprise systems that will outlast several generations of frameworks.

Trade-offs: pros and cons

StyleBest whenProsCons
Modular monolithMost systems, especially earlySimple ops, easy transactions and refactoringSingle deploy unit; scales as one; risk of erosion
MicroservicesMany teams, high scale, mature platformIndependent deploy/scale, fault isolationDistributed complexity, data consistency, high ops cost
Event-driven / CQRS / event sourcingAsync workflows, audit needs, divergent read/writeLoose coupling, auditability, scalable readsEventual consistency, event versioning, harder debugging
ServerlessSpiky or low-baseline, event-driven workNo server management, scale to zero, pay-per-useCold starts, limits, lock-in, cost at high steady load

The recurring theme: you buy flexibility and independence with operational and cognitive complexity. Distributed and event-driven styles give up the simplicity of a single call stack and a single transaction in exchange for the ability to scale, deploy, and fail independently. That trade pays off at scale and with a mature platform. Without one, it is ruinous. The internal disciplines (hexagonal/clean) are almost always worth it, because they cost little and keep your options open to change styles later.

Questions to discuss with your team

  1. Before you split the next service, will you split the team that owns it, and who has the authority to do that? Conway’s Law means a service boundary is really a team boundary, so a split the org chart does not back produces a distributed monolith: two deployables, one release train, shared on-call. In a large enterprise the authority to reshape teams usually sits above engineering, with reporting lines, finance, and HR, which is why architecture and organisation design have to be decided together. Bring evidence to the discussion: does the proposed service have a team that can own it end to end, staff its own on-call, and deploy on its own cadence? If the answer is no, either fund the team or keep the capability as a module in the monolith. Splitting code without splitting ownership buys every cost of distribution and none of the independence.

  2. Can you deploy each of your services independently today, or do they secretly ship in lockstep? The distributed monolith is the worst outcome in this chapter: you pay for network calls, partial failure, and data consistency across services, yet still cannot release one without the others. The tell-tale signs are a shared database, a shared library that forces coordinated upgrades, and integration tests that must run the whole estate together. For a large team this quietly caps throughput, because every team queues behind one release even though the diagram shows independence. Take one recent change and count how many services had to deploy together to make it safe; if that number is greater than one for a change that touched a single capability, your boundaries are wrong. The fix is usually to give each service its own data and a stable, versioned contract, not to add more services.

  3. Which of your existing service splits have stopped paying off, and would you consolidate them back? The chapter’s most advanced habit is treating style decisions as reversible: extract when a driver appears, and re-merge when the driver disappears. Most organisations only ever split, so nanoservices and chatty entity services accumulate until orchestration and network overhead dwarf the work each service does. Look for services that always deploy together, that exist because of a database table rather than a business capability, or whose network hops now dominate a request’s latency. In enterprise and government estates, where headcount and budgets are scrutinised, folding two thin services back into one coarse-grained service is a legitimate, cost-saving move, not an admission of failure. Put re-consolidation on the table as openly as extraction, and decide both with the same evidence.

  4. Does your platform and on-call maturity actually support the style you are proposing, and can you name the specific gaps before you commit? Microservices, meshes, and event-driven backbones only deliver their benefits on top of mature CI/CD, distributed tracing, service discovery, and an on-call culture that can reason about partial failure. A large organisation tends to decide the target style in an architecture forum and discover the missing platform later, once dozens of services are already in production and every incident takes hours to diagnose. Weigh the appeal of independent deployment and scaling against the sober question of who runs it at 3 a.m.: the same split that frees teams to ship in parallel also multiplies the failure modes each team must understand. Bring an honest inventory to the discussion: current deployment frequency, mean time to recovery, whether you have tracing across service boundaries, and how many services a single team can realistically operate. In enterprise and government settings, add the procurement and hiring lead times for the platform capabilities you lack, because a style that assumes a mesh and a platform team you have not funded is a plan to run an unsupportable estate.

  5. For which parts of the domain is a full event-sourced audit trail a legal necessity rather than a convenience, and who has the authority to decide? Event sourcing and CQRS buy you a perfect, reconstructable history and read models that scale on their own, but they cost you event versioning, projection rebuilds, and reasoning about eventual consistency for the life of the system. Applied to a domain that never needed the audit trail, that complexity is pure tax; withheld from a domain where “how did we arrive at this value?” is a legal question, its absence is a compliance failure. The competing considerations are auditability and query scalability on one side, and debugging difficulty and developer cognitive load on the other, so the decision belongs with people who understand both the regulatory obligation and the operational burden, not with whoever is most enthusiastic about the pattern. Bring the specific legal or contractual retention and reconstruction requirements, the expected event volume, and an honest estimate of the versioning and projection work. In finance, tax, and government, where reconstructing a decision years later can be a statutory duty, name the accountable owner who signs off that a given bounded context does, or does not, require an immutable event log.

  6. How will you stop the modular monolith from eroding so that later extraction stays cheap, and what will enforce the boundaries? The whole case for starting with a modular monolith rests on the promise that clean internal boundaries make later service extraction affordable, yet those boundaries decay silently the moment a deadline tempts one module to reach into another’s data. For a large team with many contributors, good intentions and code review alone will not hold the line; without an enforcing mechanism the monolith quietly becomes the big ball of mud the style was meant to avoid. Weigh the friction of enforced dependency rules and module interfaces against the cost of discovering, years later, that no boundary is real and every extraction means untangling shared state. Bring evidence: are module boundaries enforced by build tooling, static analysis, or package structure, or are they merely documented conventions that the last six merges ignored? In long-lived enterprise and government systems that must survive strict change control and several framework generations, treat boundary enforcement as an auditable control, so that the option to distribute later is one you have actually preserved rather than one you assume you still hold.

Sector lens

Startup. Default to a single modular monolith and resist the pull of microservices, because your scarcest resource is engineering attention and a swarm of services is operational tax you cannot afford before product-market fit. Keep clean module boundaries so you can extract later, and reach for serverless where scale-to-zero and pay-per-use suit your spiky, low-baseline load. Split exactly one thing only when a concrete driver appears, such as a bursty notification sender, and never sooner.

Small business. With no platform team and a tight budget, favour a monolith or a handful of coarse services on a managed platform, and buy hosted infrastructure rather than building meshes, tracing, and service discovery yourself. Weigh serverless and managed databases as a way to avoid running servers at all, and be wary of a distributed design whose operational burden you have no one to carry. The right architecture is the one one or two people can actually deploy, observe, and recover.

Enterprise. The real problem is many teams and Conway’s Law: align coarse-grained services to bounded contexts and to team ownership, and invest deliberately in the platform (CI/CD, tracing, mesh, and service discovery) that makes distribution safe. Standardise gateway, BFF, and internal clean-architecture patterns so groups stop reinventing them, and govern extraction and re-consolidation as evidence-based portfolio decisions rather than local preference. Budget the operational cost of every split explicitly, because at your scale the distributed-monolith failure is expensive and slow to unwind.

Government. Long system lifetimes, strict change control, procurement cycles, and auditability shape the choice: favour styles with explicit, inspectable boundaries and durable contracts that outlast vendors and framework generations. Event sourcing earns its complexity where reconstructing a citizen-facing decision is a statutory duty, so use it deliberately for core ledgers and keep clean architecture inside every service to isolate rules that change with each budget. Treat portability and exit from proprietary serverless or vendor platforms as procurement requirements, not afterthoughts.

Examples

Startup. A four-person startup building a scheduling product feels pressure to start with microservices because a competitor blogged about them, but resists. They ship a single modular monolith with clear internal boundaries (scheduling, billing, notifications) as separate modules in one deployable, so one engineer can run the whole thing locally and a release is one push. As the product finds traction, only the notification sender, which fans out to email and SMS under bursty load, gets pulled into its own service. They inherit none of the operational tax of a dozen services while they are still hunting for product-market fit.

Enterprise. A large e-commerce company starts as a modular monolith. As traffic grows and teams multiply, it pulls out the highest-load, most independently evolving domains (catalogue, cart, checkout, and search) into separate services, each owning its data. Checkout emits events that inventory, fulfilment, and analytics consume through an event backbone, so new consumers (fraud detection, loyalty) can attach without touching checkout. An API gateway handles auth and rate limiting, and a BFF tailors payloads for mobile. The remaining lower-traffic domains stay in the monolith, which avoids needless fragmentation.

Government. A tax authority builds an assessment platform on event sourcing for the core ledger, because every change to a taxpayer’s liability has to be reconstructable and legally auditable for years. Commands (file return, apply payment, issue adjustment) produce immutable events, and read models project current balances for officers and citizens. CQRS lets the public-facing query side scale on its own for peak filing season without putting the write side at risk. Inside, each service follows clean architecture, so the assessment rules, which change with each budget, stay isolated from the persistence and messaging technology.

Business case: motivations, ROI, and TCO

The money at stake in a style choice is enormous, because the decision is expensive to reverse. Adopt microservices too early and you inflate total cost of ownership through platform build-out, duplicated infrastructure, distributed debugging, and a heavier operations burden: costs that stay with you for the system’s life. Refuse to split a genuinely overloaded monolith and you cap delivery throughput: teams queue behind a shared release, and every change puts the whole system at risk. The ROI conversation is really about matching operational spend to organizational need.

Make the case to leadership in terms of throughput and risk, not technology. Independent deployability means more teams shipping in parallel and shorter lead times: measurable business velocity. Fault isolation means fewer total outages and a smaller blast radius: measurable availability and reputational protection. But be just as honest about the platform investment each style demands: a mesh, tracing, and CI/CD maturity are prerequisites, not optional extras, and their cost belongs in the TCO. For many organisations the cheapest path is a well-modularised monolith now, with clean internal boundaries that make later extraction cheap. That buys you the option to distribute without paying for it before you need it.

Anti-patterns and pitfalls

  • Distributed monolith. Services that must be deployed together and share a database: all the cost of distribution, none of the independence.
  • Nanoservices. Services so fine-grained that orchestration and network overhead dwarf the work they do.
  • Microservices without a platform. Splitting before you have CI/CD, tracing, and on-call maturity; failure modes multiply.
  • Entity services. Splitting by database table (“User service,” “Order service”) instead of by business capability, forcing chatty cross-service calls for every operation.
  • Event sourcing everywhere. Applying it to domains that do not need an audit trail, paying the complexity tax for no benefit.
  • Gateway as a monolith. Putting business logic in the API gateway, recreating a central chokepoint.
  • Framework-coupled core. Business logic tangled with the web or ORM (object-relational mapping) framework, making both testing and technology change painful.

Maturity model

  • Level 1: Initiate. Style is chosen by fashion or accident. You have one tangled monolith or an accidental distributed mess, boundaries follow technical layers or history rather than the domain, and splits happen reactively when something breaks.
  • Level 2: Develop. Some teams draw deliberate module boundaries inside the monolith or stand up a few coarse services, and some cross-cutting concerns are handled consistently. Practice is uneven: one group aligns services to bounded contexts while another still splits by database table, and extraction remains ad hoc.
  • Level 3: Standardise. A documented approach is enforced across the organisation: services align to bounded contexts and own their data, gateway and BFF patterns are used where appropriate, internal clean or hexagonal layering is the standard, and every split requires a stated driver. Module boundaries are enforced by tooling, not just convention.
  • Level 4: Manage. Style decisions are measured and controlled against baselines. You track deployment frequency and lead time per service, mean time to recovery, how many services must deploy together for a typical change, and network-hop latency, and you compare each split’s cost against the independence it was meant to buy. Evidence, not preference, drives whether a boundary survives, and drift toward a distributed monolith is caught by metrics rather than by an outage.
  • Level 5: Orchestrate. Architecture is integrated with organisation design and continuously adapted. A mature platform (CI/CD, tracing, and mesh where warranted) makes both distribution and re-consolidation cheap, teams routinely extract when a driver appears and fold services back when one disappears, and style choices are rebalanced as team topology, load, and the risk picture shift across the whole estate.

Ideas for discussion

  1. Where in your system is a monolith actually a strength, and where is it a genuine bottleneck?
  2. What concrete driver would justify extracting your next service, and can you name it before you build it?
  3. Does your organisation have the operational maturity that microservices demand? What is missing?
  4. For which parts of your domain is a full event-sourced audit trail a legal or business necessity versus a nice-to-have?
  5. How well do your current service boundaries mirror your team boundaries, and is that alignment helping or hurting?
  6. If you had to change your web framework or database next year, how much of your business logic would you have to rewrite?

Key takeaways

  • Choose architectural style from real forces (team size, domain, load, operational maturity), not from fashion.
  • A modular monolith is the right default for most systems; extract services only with a concrete driver and along bounded-context lines.
  • Microservices trade operational and cognitive complexity for independent deployment, scaling, and fault isolation; they require a mature platform.
  • Event-driven, CQRS, and event sourcing offer decoupling and auditability at the cost of eventual consistency and versioning complexity; adopt deliberately.
  • Gateways, BFFs, and meshes tame many-service estates but add weight; introduce them when scale demands, not before.
  • Apply hexagonal/clean architecture inside every service to keep valuable business logic testable and durable across technology change.

References and further reading

  • Sam Newman, Building Microservices and Monolith to Microservices
  • Chris Richardson, Microservices Patterns
  • Eric Evans, Domain-Driven Design
  • Vaughn Vernon, Implementing Domain-Driven Design
  • Robert C. Martin, Clean Architecture
  • Alistair Cockburn, “Hexagonal Architecture (Ports and Adapters)”
  • Gregor Hohpe and Bobby Woolf, Enterprise Integration Patterns
  • Martin Fowler, Patterns of Enterprise Application Architecture (and articles on CQRS and Event Sourcing)
  • Matthew Skelton and Manuel Pais, Team Topologies