Express course · No. 03
You rarely invent an architecture. You recognise which known shape fits the problem in front of you — and pay the price that shape charges.
Essence only · One picture per pattern · Examples over theory
A pattern is a reusable answer to a problem that keeps coming back. Knowing the catalogue means you stop reinventing — and start choosing.
A pattern is a named answer, not a rule
In chess you don't reinvent the opening every game. You recognise "this is a Sicilian" and you already know where it tends to lead.
An architecture pattern is the same: a problem that recurs across thousands of systems — how do the parts find each other? how does it survive load? — paired with a known-good shape and known costs. You're not being clever. You're standing on decades of other people's scars. When you say "services need to react to things happening elsewhere," that's event-driven — already solved, named, and documented.
Architecture patterns are not design patterns
A design pattern is how you arrange the furniture in one room. An architecture pattern is the floor plan of the whole house.
Design patterns (Factory, Observer, Strategy) live inside one module, in the code. Architecture patterns decide the big boxes and the lines between them — what gets deployed separately, what talks to what, where the data lives. This course is about the floor plans, not the furniture.
Every pattern charges a price
A sports car and a minivan are both "correct" — for different lives. Neither is the better car in the abstract.
There is no best pattern, only a fit. Microservices buy you independent scaling and charge you a distributed system to operate. A layered monolith buys you simplicity and charges you painful scaling later. The skill is reading the bill before you sign it.
You don't pick the famous pattern. You pick the one whose price you can afford.
Most systems start as a single deployable. The first patterns are about how you arrange the inside so it doesn't turn to mud.
Layered (n-tier): stack responsibilities in floors
A restaurant: the dining room takes orders, the kitchen cooks, the pantry stores. The waiter never cooks; the chef never goes to the cellar — the next layer down does.
The most common pattern of all. Code is split into horizontal layers — presentation, business logic, data access, database — and a request falls straight down through them. Example: a web app where a controller calls a service, which calls a repository, which hits Postgres. It is easy to understand and easy to test (mock the layer below). The price: everything ships as one unit, and a trivial change can still mean redeploying the whole thing.
Modular monolith: one building, well-walled flats
An apartment block — one roof, one entrance, but each flat has its own walls and its own door. You don't walk through your neighbour's bedroom to reach the stairs.
Still a single deployable, but the inside is split into modules — billing, catalogue, accounts — that talk only through clear interfaces, never by reaching into each other's tables. Example: a backend where catalogue never imports billing internals, only its public functions. You get most of the clarity of microservices with none of the network. For almost everyone, this is the right default.
MVC / MVVM: keep the screen away from the brains
A theatre: the script, the actors and director, and the stage and lights are different jobs done by different people. You can swap the set without rewriting the play.
These patterns put a wall between the user interface and the business logic and data, so a change to how something looks can't endanger what it does. Example: Django and Rails use MVC (Model is data, View is the page, Controller is the glue); rich web and mobile apps lean to MVVM, where a "view model" holds screen state the view just mirrors. The wall is always the same: presentation on one side, meaning on the other.
A monolith isn't a mess. A monolith without internal walls is.
The same idea drawn three ways: push the framework, the database, and the web out to the edges, and keep the business rules ignorant of them.
Ports and adapters (hexagonal): standard sockets around a core
A game console has standard ports. The game doesn't know whether you plugged in a gamepad, a wheel, or an arcade stick — it just reads "input."
The business logic sits in the middle and defines ports — interfaces like "save an order" or "send a notification." Adapters on the outside implement them for a specific technology: Postgres, Stripe, email. Example: your domain calls an OrderRepository port; one adapter backs it with Postgres in production and another with an in-memory list in tests. The outside world can be swapped without the core ever noticing.
Clean / Onion: dependencies point inward
An onion: the core in the centre, layers wrapped around it. You can peel the outer skin without harming the heart — and the heart doesn't depend on the skin.
Same principle, drawn as concentric circles: entities and use-cases in the centre, frameworks and I/O on the rim, and every dependency arrow points toward the centre. Example: the rule "an invoice over 10k needs approval" lives in a plain class that imports nothing from your web framework or ORM — so it outlives both. You can replace the UI library, swap the web framework, migrate the database, and the rules survive untouched.
Frameworks are details. Treat them like the wallpaper, not the foundation.
At some scale — of load, or of teams — one box stops working. These patterns cut the system into independently shippable pieces, and hand you a network in return.
Microservices: a food court, not one big kitchen
A food court — separate stalls, each with its own kitchen, menu, and till. The pizza stall gets busy and hires two more cooks; the salad stall doesn't have to.
The app is split into small services, each owning one capability and its own data, each deployed and scaled on its own. Example: separate order, inventory, and shipping services talking over HTTP or a message bus; on Black Friday you scale only "order" and "payment." They're the lean descendant of SOA, which tried the same split but routed everything through one heavy enterprise service bus and choked on it. The price is steep and real: network latency, no easy transactions across services, and a distributed system to run. Right for big teams and uneven load — overkill for a five-person startup.
Serverless / functions: rent the kitchen by the dish
Instead of owning a car you can't keep full, you call a taxi per trip and pay only while the meter runs.
You write small functions and the cloud runs them on demand, scaling from zero to thousands and back, billing per execution. Example: an image uploaded to storage triggers a function that makes thumbnails — no server sitting idle between uploads. Great for spiky, event-shaped work; awkward for long jobs and anything that can't tolerate a cold start.
Distributed systems turn function calls into network calls. Every one of those can fail.
Once you have more than one box, the architecture is mostly in the lines between them. How they talk decides how they fail.
Request–response: ask and wait at the counter
Ordering at a counter — you ask, you stand there, and you get your coffee before you move on.
The default shape: a client calls a server and waits for the answer (REST, gRPC, a database query). Simple and direct. The catch is coupling in time — if the server is slow or down, the caller is stuck waiting too. Example: a checkout calling the payment API and blocking until it replies.
Event-driven: announce it, let listeners react
A newsroom. Something happens, the announcement goes out, and whoever cares — sports desk, weather, ads — reacts on their own. The announcer doesn't wait for them.
Instead of calling services directly, a component emits an event — "OrderPlaced" — and anything interested reacts. Two flavours: broker (events chain freely, no conductor) and mediator (a coordinator runs the steps in order). Example: "OrderPlaced" fans out to email, inventory, and analytics — each independent, each scalable, none blocking the order. The cost: the flow is harder to follow and to test, and you inherit duplicates and ordering problems.
Queues and pub/sub: a post office between them
A post office holds your letter until the recipient is ready. You drop it and leave; they read it when they wake up.
A message queue or pub/sub broker (RabbitMQ, Kafka) sits between sender and receiver so they no longer have to be online at the same moment. Example: a slow report request is dropped on a queue, a worker picks it up when free, and the website stays snappy. This buys resilience and smooths out spikes — at the cost of eventual, not immediate, results.
Pipe-and-filter: an assembly line for data
A bottling line — wash, fill, cap, label — each station does one thing and passes the bottle on. Or water through a stack of filters.
Data flows through a chain of independent steps, each transforming it and handing it on. Example: a Unix pipe (grep | sort | uniq), an ETL job, or a compiler (tokenise, parse, optimise, emit). Each filter is simple, testable, and reorderable. Best when work is a clear sequence of transformations — wrong when steps need to loop back and gossip with each other.
Synchronous is a phone call. Asynchronous is a letter. Choose by how much each side should depend on the other being awake.
Some patterns aren't about boxes at all. They're about how you write, read, and remember the truth.
CQRS: separate the way you write from the way you read
A library has a quick catalogue for finding books, and a back room where the books are actually filed and reshelved. Looking up is optimised one way; storing is optimised another.
CQRS splits the write model (commands that change things) from the read model (queries that fetch them), so each can be shaped and scaled for its own job. Example: a dashboard reads from a denormalised, pre-computed view while writes go to a clean transactional store. Powerful when reads vastly outnumber writes — but it's two models to keep in sync, so don't reach for it by default.
Event sourcing: store the history, derive the present
A bank doesn't store just your balance — it stores every deposit and withdrawal. The balance is only the sum. Lose it, and you can recompute it from the ledger.
Instead of saving current state and overwriting it, you store the full stream of events that happened; current state is a replay of them. Example: an account kept as "opened, +100, −30" rather than "balance: 70" — giving you a perfect audit trail and the ability to ask "what did this look like last Tuesday?" The price: querying "now" is harder (often paired with CQRS), and events are forever — you can't just edit history.
Saga: a long deal you can unwind
Booking a trip — flight, hotel, car. If the car falls through, you cancel the hotel and the flight too. There's no single "undo"; you walk each step back.
When one business action spans several services that each own their database, you can't wrap it in a single transaction. A saga chains local transactions and, on failure, runs compensating actions to undo the earlier ones. Example: place order, reserve stock, charge card; if the charge fails, release the stock and cancel the order. It's how you keep distributed data consistent without a global lock.
A transaction is one atom of intent. When it spans services, you have to build the atom yourself.
A few patterns exist for one specific pain. Reach for them when you have that exact pain — and not a moment before.
Microkernel / plug-in: a tiny core with swappable parts
A power drill: one motor, a dozen interchangeable bits. The drill stays the same; you snap on whatever the job needs.
A minimal core provides the basics, and everything else arrives as plug-ins that register themselves without depending on one another. Example: VS Code, Eclipse, and browsers are mostly plug-in hosts; an insurance system can keep state-specific rules as plug-ins, so a new state is a new module rather than a rewrite. Ideal when features are volatile or customer-specific.
Space-based: copy the data into memory, kill the bottleneck
A stadium with twenty ticket booths, each holding its own copy of the seating chart in hand — instead of one box office everyone queues at.
When a single database becomes the chokepoint under extreme load, this pattern spreads many processing units that each keep the data in memory, kept in sync between them — with no central database to queue on. Example: a flash sale or an auction taking a tidal wave of concurrent bids. It's powerful and expensive, and only earns its keep at genuinely huge, spiky concurrency.
Peer-to-peer: no centre at all
A potluck dinner — everyone is both cook and guest. No restaurant, no waiter; the group feeds itself.
Nodes talk directly to each other with no central server, each one both client and provider. Example: BitTorrent, where every downloader also uploads, so the system gets stronger as more people join. Wonderful for resilience and scale without a central host — hard for consistency and control.
Exotic patterns are medicine, not vitamins. Take them for a diagnosed illness.
Patterns are a vocabulary, not a menu you order one item from. Real systems layer several — and the wisest move is often the plainest one.
You combine patterns; you rarely pick just one
A house uses many patterns at once — load-bearing walls, plumbing, wiring, insulation — not "the one true building pattern."
A real system might be a modular monolith inside, clean architecture in each module, request-response for the UI, an event for the slow side-effects, and a queue in front of the heavy job. The question is never "which pattern" but "which patterns, where." Example: most successful products are a boring layered monolith that grew a handful of events and one or two extracted services exactly where the pain was.
Start with the simplest shape that works
You don't pour a stadium's foundation to build a cabin.
Almost every system should begin as a modular monolith and earn its complexity. Microservices, CQRS, event sourcing, space-based grids — these are answers to problems of scale and team size you may never have. Adopt them when the pain is real and named, not in anticipation. The cheapest distributed system is the one you didn't build.
- What recurring problem does this shape solve? Name it in one sentence. - Do I have that problem now — or am I guessing about the future? - What does it cost? In latency, ops, complexity, money. - What's the simplest pattern that would still work? Start there. - Can I adopt this later, when the pain actually shows up? - Which patterns will this one have to live alongside? They have to fit together.
- You have more services than engineers. - A single user action touches five services and two queues to do what a function call could. - You added CQRS or event sourcing before you had a read or audit problem. - Nobody on the team can draw the whole system from memory. - You chose the pattern because it's what big companies use.
- A new engineer can find where a change goes in minutes. - The parts that change together live together; the parts that scale differently are separate. - When something breaks, the blast radius is one box, not all of them. - You can explain each pattern's job and its price in a sentence. - The architecture matches the load and the team you actually have, not the one you imagine.
The best architecture is the simplest one that survives your real requirements — and not one pattern more.