Common System Architectures: A Reference Catalog Every Architect Should Know (With Diagrams and Code)
A practical reference catalog of the eight architectures worth knowing — layered, modular monolith, hexagonal, event-driven, CQRS + event sourcing, microservices, serverless, and the strangler fig. Each with a diagram, the forces that make it the right call, the failure mode that makes it the wrong one, and a link to runnable reference code. Plus a decision flowchart so you pick on fit, not hype.
Ruchit Suthar
15+ years scaling teams from startup to enterprise. 1,000+ technical interviews, 25+ engineers led. Real patterns, zero theory.

After 15 years and a few hundred design reviews, I've watched teams reinvent the same eight or nine architectures from scratch — badly. This is the reference catalog I wish I'd had: layered, modular monolith, hexagonal/ports-and-adapters, event-driven, CQRS + event sourcing, microservices, serverless, and the strangler fig migration pattern. For each, you get a diagram, the forces that make it the right choice, the failure modes that make it the wrong one, and a runnable reference implementation. Every pattern links to working code in the companion open-source repo so you can read the architecture, not just the blog post about it. Use the decision flowchart at the end to pick — don't cargo-cult.
Common System Architectures: A Reference Catalog Every Architect Should Know (With Diagrams and Code)
March 2024. I'm in an architecture review for a team that had just spent four months building microservices for a product with 800 daily active users. Eleven services. Each with its own database, its own deploy pipeline, its own on-call rotation that the same three engineers shared. A single "create order" flow touched five services and two message queues. They'd read the Netflix blog posts. They'd done everything "right."
It was killing them. A feature that should have taken two days took two weeks because every change rippled across service boundaries that didn't map to anything real in the business. Their p99 latency was worse than a monolith because half their requests were now network hops. And the worst part? Nobody could explain why it was eleven services instead of one. "That's just how you build scalable systems."
No. That's how you build a distributed monolith and call it scalable.
The problem wasn't microservices. Microservices are a perfectly good answer to a specific question. The problem was that nobody had asked the question. They'd picked an architecture the way you pick a default — because it was in the air, not because it fit the forces in front of them.
This catalog is the antidote. It's the set of architectures I reach for, when I reach for each one, and — just as important — when I don't. Every pattern has a diagram you can read in thirty seconds and a working reference implementation in the companion repo so you can see the structure in real code instead of taking my word for it.
Read it like a menu, not a ranking. There's no "best" architecture. There's the one that fits your constraints and the one that fights them.
How to read this catalog
For each architecture I give you four things, in the same order every time, so you can scan:
- The shape — a diagram and a one-paragraph definition.
- The forces — the specific conditions under which this is the right call.
- The failure mode — how it goes wrong, usually because someone picked it for the wrong reason.
- The reference — a link to runnable code so you can study the seams.
I've ordered them roughly by how much complexity they introduce. Start at the top. Most teams should stop earlier than they think.
A rule I repeat in every review: complexity is a loan. You pay interest on it every single day, in cognitive load, onboarding time, and operational toil. Take the loan when the return justifies it. Not before.
1. Layered (N-tier) architecture
The default. Presentation → application/service → domain → data access. Each layer talks only to the one below it.
The forces. You're starting out, the domain is small, and you need to ship. Layering gives you a structure everyone already understands. A new engineer can find where things go without a map. It's boring, and boring is a feature when you're trying to find product-market fit.
The failure mode. Layered architecture rots in a predictable way: business logic leaks upward into controllers and downward into the database (stored procedures, fat ORM models), until the "domain layer" is an anemic bag of getters and setters. The tell is a service method that's 600 lines of orchestration with no domain object doing any actual thinking. When you can't unit-test a business rule without spinning up a database, the layers have collapsed.
The reference. /layered — a clean three-layer service with the dependency rule enforced by package boundaries.
2. Modular monolith
A single deployable unit, but internally divided into modules with explicit, enforced boundaries. Each module owns its data and exposes a narrow interface. The boundaries are real — they're just not network boundaries.
The forces. This is my default recommendation for almost every team under ~30 engineers. You get most of the organizational benefits people think they need microservices for — clear ownership, independent reasoning, the ability to extract a service later — without the distributed-systems tax. The module boundaries become your future service boundaries if you ever need them. You deploy once, debug with a single stack trace, and run database transactions across modules when you genuinely need to.
The failure mode. The boundaries aren't enforced, so they erode. Module A reaches directly into Module B's tables "just this once," and eighteen months later you have a big ball of mud wearing a modular costume. Enforce boundaries with tooling — architecture tests (ArchUnit, dependency-cruiser), separate schemas, or compiler-level module systems — not with good intentions in a wiki.
The reference. /modular-monolith — modules with separate schemas and a dependency test that fails the build on a boundary violation.
3. Hexagonal (ports and adapters)
The domain sits in the center, knowing nothing about the outside world. It defines ports (interfaces). The outside world — HTTP, databases, message brokers, third-party APIs — plugs in through adapters that implement those ports.
The forces. Your business logic is valuable and long-lived, but your infrastructure choices aren't. You want to test the domain with zero infrastructure, swap Postgres for DynamoDB without touching a business rule, and keep framework churn at the edges. Hexagonal shines when the domain is genuinely complex and you expect the surrounding tech to change — which, over a 5-year horizon, it always does.
The failure mode. Ceremony for a CRUD app. If your "domain logic" is forwarding a request to the database, ports and adapters add indirection that buys you nothing — three files and an interface to save one field. Hexagonal earns its keep when there's real logic to protect. For a thin CRUD service, layered is honest and hexagonal is cosplay.
The reference. /hexagonal — the same feature implemented twice, once with an in-memory adapter for tests and once with Postgres, to show the swap.
4. Event-driven architecture
Components communicate by producing and consuming events through a broker (Kafka, RabbitMQ, SNS/SQS) instead of calling each other directly. Producers don't know who consumes; consumers don't know who produced.
The forces. You have genuinely independent reactions to the same fact ("an order was placed" → reserve stock, send email, update analytics, trigger fraud check), and you want to add new reactions without touching the producer. Event-driven gives you loose coupling, natural buffering against load spikes, and an audit trail of what happened. It's the right backbone when workflows are asynchronous by nature.
The failure mode. You lose the ability to reason about the system by reading code. A request that used to be one stack trace is now a choreography spread across five services and a broker, and "why didn't the email send?" becomes an archaeology project. Two specific traps: (1) using events for request/response flows that actually need an answer right now — that's just RPC with extra latency and no error path; (2) no idempotency, so a redelivered event charges the customer twice. Every consumer must be idempotent. Non-negotiable.
The reference. /event-driven — producer/consumer with idempotency keys and a dead-letter queue.
5. CQRS + event sourcing
Two patterns that travel together but are separable. CQRS splits the write model (commands) from the read model (queries) so each can be optimized independently. Event sourcing stores state as an append-only log of events; current state is a fold over that log.
The forces. Your read and write workloads are wildly asymmetric (write a trade once, query it a thousand ways). You need a perfect audit trail and the ability to reconstruct state at any point in time — finance, healthcare, anything regulated. Event sourcing lets you answer questions you didn't know to ask when you designed the system, because you kept every fact.
The failure mode. This is the most over-applied "senior" pattern I see. Teams adopt event sourcing for a todo app and discover the hard problems: eventual consistency confuses users ("I saved it, why isn't it there?"), schema evolution of old events is genuinely difficult, and rebuilding projections over millions of events is slow. CQRS without event sourcing is often the sweet spot — separate read/write models, normal database. Reach for full event sourcing only when the audit log is a real business requirement, not an aesthetic preference.
The reference. /cqrs-event-sourcing — an aggregate, an event store, and two independent projections.
6. Microservices
The business is decomposed into independently deployable services, each owning its data and aligned to a business capability. They communicate over the network, sync or async.
The forces. This is the one everyone wants and few need. The real driver for microservices is organizational, not technical: you have enough engineers (think 30+, multiple teams) that coordinating deploys in a single codebase has become the bottleneck. Microservices let independent teams own, deploy, and scale their piece on their own cadence. The technical benefits — independent scaling, fault isolation, polyglot freedom — are real but secondary. Conway's Law isn't a warning here; it's the design tool.
The failure mode. Everything my March 2024 team hit. Services drawn along technical lines (a "database service," an "auth service") instead of business capabilities, so every feature crosses boundaries. Distributed transactions faked with no saga or compensation. Shared databases that couple services through the back door. The diagnostic question I ask in every review: "Can a single team ship a meaningful feature in their service without coordinating a deploy with another team?" If no, you have a distributed monolith — all the cost of distribution, none of the independence.
The reference. /microservices — two services with a saga for a cross-service workflow, plus an API gateway.
7. Serverless / function-as-a-service
Code runs in managed, ephemeral compute (Lambda, Cloud Functions) triggered by events. No servers to manage; you pay per invocation and scale to zero.
The forces. Spiky, event-driven, or low-baseline-traffic workloads where paying for idle servers is wasteful. Glue code, scheduled jobs, image processing, webhook handlers, the early MVP where you want zero ops. Scale-to-zero is genuinely magical for unpredictable load and side projects.
The failure mode. Cold starts on latency-sensitive paths. Vendor lock-in that's deeper than people admit (your whole event wiring is now proprietary). Local development and testing that fight you. And the cost curve flips: serverless is cheap at low and spiky volume but can be dramatically more expensive than a boring container at steady high throughput. "Lambda pinball" — dozens of functions calling each other — recreates the microservices reasoning problem with worse observability.
The reference. /serverless — an IaC-defined function pipeline with local emulation so the inner loop doesn't require a deploy.
8. The strangler fig (migration pattern)
Not a target architecture — a migration architecture. You incrementally replace a legacy system by routing slices of functionality to new code behind a façade, until the old system is "strangled" and can be removed. Named after the vine that grows around a tree and eventually replaces it.
The forces. You have a legacy system that's too risky to rewrite in a "big bang" — which is every legacy system worth its salt. The strangler fig lets you ship value continuously, prove each slice in production, and keep a working system the entire time. There's no flag day, no 18-month rewrite that gets cancelled at month 14.
The failure mode. The migration stalls at 60% because the easy parts went first and the hard, gnarly core was left for "later" — and "later" never gets prioritized over new features. Now you maintain two systems forever, plus the façade. Avoid it by sequencing the migration around business value and by setting a hard decommission date for the legacy system with executive air cover. A strangler fig with no deadline is just two systems.
The reference. /strangler-fig — a routing façade that migrates one endpoint at a time with a metrics-based cutover.
The decision: how to actually pick
Here's the flowchart I use in reviews. It's deliberately biased toward simplicity, because the default failure mode in our industry is over-engineering, not under-engineering.
Three principles behind the flowchart:
-
Start with the simplest thing that could possibly work, and make the boundaries explicit so you can change your mind. A modular monolith with clean seams can become microservices in an afternoon per module. Microservices can't become a monolith without a rewrite. Optimize for reversibility.
-
Distribute for organizational reasons, not technical ones. If one team can fit the whole system in their heads, keep it in one process. The network is not free, and "scalable" is not a synonym for "distributed."
-
Match the architecture to the forces, then write down why. The single highest-leverage artifact you can produce is an Architecture Decision Record capturing which forces drove the choice. Future-you, staring at eleven services, will want to know.
What to do Monday morning
Don't refactor anything yet. Do this instead:
-
Name your current architecture. Open this catalog and find the closest match to what you actually have in production (not what the wiki says). Most systems are an accidental hybrid — "layered, drifting toward distributed monolith" is a real and common answer.
-
List the three forces that matter most for your system in the next 12 months: team size and growth, read/write asymmetry, latency requirements, audit/compliance, deployment independence. Write them on one line each.
-
Run the flowchart against those forces. If it points somewhere different from where you are, you've found either a migration worth planning or a piece of accidental complexity worth deleting. Either is valuable.
-
Clone the reference repo and read the implementation of the pattern you're considering before you commit to it. Reading working code for two hours will teach you more about whether a pattern fits than reading ten blog posts.
Key takeaways
-
There is no best architecture, only fit. Every pattern in this catalog is the right answer to some question and the wrong answer to most others. The skill isn't knowing the patterns — it's knowing the forces.
-
Complexity is a loan with daily interest. The modular monolith is the right default for the vast majority of teams because it gives you clean boundaries without the distributed-systems tax. Take on distribution only when the organizational return justifies the operational cost.
-
Distribute for people, not for machines. The honest driver of microservices is team independence at scale (30+ engineers), not performance. If one team can hold the whole system in their heads, one process is the right call. A service drawn along technical lines instead of business capabilities is a future incident.
-
Make boundaries explicit and reversible. Whether layered, modular, or hexagonal, enforce your seams with tooling, not wishes. Clean seams let you change your mind cheaply; eroded seams turn any architecture into a big ball of mud.
-
Write down why. An ADR that records the forces behind a decision is worth more than the decision itself. It's how the next architect avoids repeating the March-2024 mistake.
Your next step
Pick the one architecture you're most tempted to adopt right now — the one that's "in the air" on your team. Then deliberately argue the other side: write three sentences on why it might be the wrong call for your forces. If you can't make that argument convincingly, you don't understand the pattern well enough to adopt it yet. Read its reference implementation in the repo, then make the call — with your eyes open this time.
AI can generate any of these architectures for you in minutes now. It cannot tell you which one your business actually needs. That judgment — matching the shape to the forces — is the part that's still yours.
Frequently asked questions
What's the difference between a modular monolith and microservices?
Both have explicit module/service boundaries with clear ownership. The difference is the deployment unit: a modular monolith is a single deployable process where modules communicate via in-process calls and can share database transactions, while microservices are independently deployable processes that communicate over the network and own separate databases. A modular monolith gives you most of the design benefits (clear boundaries, independent reasoning) without the operational cost (network failures, distributed transactions, multiple pipelines). It's the recommended default for teams under ~30 engineers.
When should a startup move from a monolith to microservices?
When organizational coordination — not technical performance — becomes the bottleneck. The trigger is typically 30+ engineers across multiple teams where deploying in a single codebase causes constant coordination overhead. Extract services one business capability at a time, starting from the modular boundaries you already have, rather than rewriting into microservices all at once. If you can't articulate which team-coordination problem a service split solves, it's premature.
Do I need event sourcing if I use CQRS?
No. CQRS (separating read and write models) and event sourcing (storing state as an append-only event log) are independent patterns that are often combined but don't require each other. CQRS with a conventional database is frequently the sweet spot: you get optimized read and write paths without the operational difficulty of event schema evolution, eventual-consistency UX issues, and slow projection rebuilds. Adopt full event sourcing only when an immutable audit trail or time-travel state reconstruction is a genuine business requirement.
How do I choose between event-driven architecture and direct service calls?
Use events when reactions to a fact are genuinely independent and asynchronous (an order is placed → update inventory, send email, log analytics) and you want to add reactions without changing the producer. Use direct (synchronous) calls when the caller needs an answer immediately to proceed and must handle the failure inline. A common anti-pattern is using events for request/response flows that actually need a response — that's RPC with added latency and no clear error path. Whatever you choose, every event consumer must be idempotent.
Is serverless cheaper than running containers?
It depends entirely on your traffic shape. Serverless is cheaper for spiky, low-baseline, or event-driven workloads because you scale to zero and pay per invocation. At steady, high throughput it can be significantly more expensive than a well-utilized container, on top of cold-start latency and deeper vendor lock-in. Model your actual request volume and latency requirements before committing; the cost curves cross over at high utilization.

Ruchit Suthar
15+ years scaling teams from startup to enterprise. 1,000+ technical interviews, 25+ engineers led. Real patterns, zero theory.

