Library
00/07 · ~36 min
GUIDEDECK · how systems are deployed and wired together

Architecture
Patterns & Styles
for whole systems.

A 36-minute tour of system-level shapes — monolith to microservices, the BFF, API gateways, event-driven, serverless, and how to migrate safely. We'll be honest about where the simpler option quietly wins.

~36 MINBEGINNER → INTERMEDIATESYSTEM-LEVEL
SCROLL
01 · Why architecture styles 4 min

Coupling and cohesion —
now at system scale.

Inside one process, a bad boundary is a slow refactor. Across a network, the same bad boundary becomes a retry storm, a failed deploy, and a 2 a.m. page. An architecture style is just where you draw the deployment and integration lines — and what those lines cost when they cross the wire.

Architecture style the system-level shape of how an application is split into deployable units and how those units talk. It's a different altitude from the class-level patterns (layered, hexagonal, clean) — those still live inside each unit. This deck owns the lines between units; scaling techniques live in System Design.

One axis under everything

  • One deployable unit vs. many. Monolith ships as a single artifact; distributed systems ship many, independently.
  • In-process calls vs. network calls.A function call is nanoseconds and never "half-fails." A network call is milliseconds and can time out, retry, or partially succeed.
  • Every style ahead is a different answer to "which boundaries are worth a network hop?"
MONOLITH · one unit orders billing users search in-process calls DISTRIBUTED · many units orders billing users search network calls · can fail

Same four concerns. On the left, calls can't fail; on the right, every line is a thing that can break.

Monolith wins

Simple ops, one deploy, easy local dev, transactions are free. Most products should start here.

Distributed wins

Independent scaling and deploys, team autonomy, fault isolation — if the boundaries are real.

The tax

Distributed adds network failure, partial failure, data consistency, and observability cost to everything.

The rule

Don't pay the distributed tax until coupling pain — not résumé envy — forces it.

02 · Monolith → modular monolith → microservices 7 min

A spectrum, not a
good vs. bad choice.

These aren't three tribes — they're three points on one line, trading deployment simplicity for independence. The honest truth of 2026: microservices are over-adopted. Most teams reach for them years before the pain that justifies them, and pay the tax the whole time.

Modular monolith one deployable unit, but split into strict, well-named modules that only talk through published interfaces. You get clean boundaries withoutthe network. It's the quietly-correct default, and the launchpad if you ever do split out a service.
simpler ops · one deploy more independence · many deploys Monolith one unit · one codebase tangled modules boundaries: soft Modular monolith one unit · strict modules orders ▸ billing ▸ boundaries: enforced, in-process Microservices many units · own data + deploy orders billing users boundaries: over the wire

The middle option is the one most teams skip — and the one most teams actually need.

Distributed monolith — the trap
// "microservices", but they share a database async function checkout(cart) { await http.post("orders-svc/create", cart) await http.post("billing-svc/charge", cart) // network hop await http.post("inventory-svc/reserve", cart) // network hop } // deploy one → must deploy all. Slower AND fragile.
Modular monolith — the honest start
// modules with enforced boundaries, one process async function checkout(cart) { await orders.create(cart) // in-process, can't half-fail await billing.charge(cart) // one DB transaction await inventory.reserve(cart) } // split a module into a service later, when it earns it.
Split a service when…
  • A module must scale on its own (10× the others).
  • A separate team needs an independent deploy cadence.
  • It needs fault isolation or a different runtime.
Don't split when…
  • The boundary is still moving every sprint.
  • Two "services" share a database — that's one.
  • You have fewer engineers than proposed services.
Find the seams

Cut along bounded contexts (DDD) — language and ownership that change together. A network boundary that follows a real seam is cheap; one that splits a transaction is misery.

03 · The Backend-for-Frontend pattern 5 min

One backend per
frontend, tailored to fit.

A web grid, a mobile list, and a partner integration want very different shapes of the same data. A BFF is a thin backend owned by each client team that talks to the downstream services and returns exactly what that client needs — no over-fetching, no twelve round-trips.

BFFBackend for Frontend — a dedicated API layer per client type (web, mobile, partner) that aggregates and reshapes calls to the underlying services. It moves client-specific glue out of a one-size-fits-all API and into a place the client team owns.
Web Mobile Partner Web BFF Mobile BFF Partner BFF orders-svc catalog-svc users-svc

Each client owns a BFF; the BFFs share the same downstream services.

What the BFF absorbs

  • Aggregation — fan out to 3 services, return one tidy payload, kill the mobile round-trips.
  • Shaping — the web grid gets rich objects; the watch app gets three fields.
  • Client-specific auth & rate limits — without polluting the core services.
  • Owned by the frontend team, so UI needs don't queue behind a platform backlog.
Reach for a BFF when
  • Clients diverge sharply (mobile bandwidth vs. web richness).
  • The frontend is drowning in orchestration logic.
  • You want per-client release independence.
Skip the BFF when
  • You have one client — a BFF is then just an extra hop to maintain.
  • Clients are near-identical — a shared API gateway or GraphQL layer covers it.
  • You'd duplicate the same logic into every BFF — push it down instead.

Like a personal shopper per customer — same warehouse, but each comes back with a bag packed for one person. The cousin idea on the UI side is micro-frontends; powerful, equally over-adopted — one team rarely needs them.

04 · API gateway & service communication 6 min

One front door, then
sync or async behind it.

Clients shouldn't know your service map. An API gatewayis the single entry point that handles the cross-cutting chores — routing, auth, rate limits, TLS — so each service doesn't. Behind it, services talk to each other two ways, and the choice shapes how failure spreads.

API gateway a reverse proxy that fronts your services and centralizes cross-cutting concerns — routing, authentication, rate limiting, TLS termination, and request shaping. A BFF is per-client and owned by frontend teams; a gateway is per-system and owned by platform. They often stack: client → gateway → BFF → services.
# the gateway owns the edge, not the services routes: - path: /api/orders/* to: orders-svc - path: /api/catalog/* to: catalog-svc plugins: - jwt-auth # verify token once, at the edge - rate-limit: 100/min - cors, tls, request-id
clients gateway auth · routing orders catalog users

Auth and routing happen once, at the edge — services stay focused on their job.

Sync — request/response

  • Caller waits for an answer (REST, gRPC). Simple to reason about.
  • But it couples availability — if billing is down, checkout is down. Failure spreads forward.
  • Use for reads and anything the user is waiting on right now.

Async — events

  • Caller emits an event and moves on; consumers react later via a broker.
  • Decouples availability — billing can be down; the event waits in the queue.
  • Costs you eventual consistency. Broker details live in Message Queues & Streaming.

Tooling landscape

Pick on operating model: self-host for control, managed for less ops, full lifecycle for big API programs.

Kong

Pro — open-source, plugin-rich, runs anywhere (self-host or cloud).

Con — you run and scale it yourself.

Choose when you want portability and deep customization.

AWS API Gateway

Pro — fully managed, native Lambda / IAM integration, scale-to-zero.

Con — AWS-locked; per-request cost adds up at high volume.

Choosewhen you're already serverless on AWS.

Apigee

Pro — full API lifecycle: dev portal, monetization, analytics.

Con — heavyweight and pricey; overkill for internal traffic.

Choose when APIs are a product sold to external developers.

Pick on the call's shape: who consumes it, how tight the latency budget, and whether the caller can wait.

REST / JSON

Pro — universal, cache-friendly, debuggable with curl.

Con — chatty; no schema by default.

Choose for public APIs and broad reach.

gRPC

Pro — fast binary, strict schema, streaming, codegen.

Con — not browser-native; harder to eyeball.

Choose for internal, high-throughput service mesh.

GraphQL

Pro — client picks fields; one round-trip for varied UIs.

Con — server complexity; caching and N+1 are real work.

Choose for rich, client-driven frontends / BFFs.

Async events

Pro — decouples availability; absorbs spikes.

Con — eventual consistency; harder to trace.

Choosewhen the caller doesn't need an answer now.

05 · Event-driven — pub/sub, event sourcing, CQRS 6 min

Tell the system what
happened, not what to do.

In an event-driven style, a service announces a fact — OrderPlaced— and walks away. Anyone interested reacts. Producers don't know consumers, so you add new reactions without touching the source. That decoupling is the whole appeal — and the eventual consistency is the whole cost.

Event-driven architecture services communicate by publishing and subscribing to events through a broker rather than calling each other directly. The broker mechanics (Kafka, partitions, delivery guarantees) belong to Message Queues & Streaming; here we care about the architectural shape it creates.
Orders producer broker OrderPlaced Billing → charge Email → confirm Analytics → log add a consumer · producer never changes

Pub/sub: Orders publishes once; three consumers react. Adding a fourth touches nobody upstream.

Two patterns that ride on events

  • Event sourcing — store the stream of events as the source of truth, not just the latest row. State is a replay of what happened; you get a perfect audit log and time-travel for free.
  • CQRS (Command Query Responsibility Segregation) — split the write model from the read model. Commands change state; queries hit read-optimized projections built from the events.
  • They pair naturally, but each is independent— and each adds real complexity. Don't adopt them by reflex.
// the log of facts IS the truth events = [ { type: "OrderPlaced", items: 3 }, { type: "OrderPaid", amount: 90 }, { type: "ItemRefunded", amount: 30 }, ] // current state = fold over events balance = events.reduce(apply, 0) // → 60
client write modelcommands read modelqueries event logprojections

CQRS: writes append events; reads are served from projections built off that log.

Worth it when
  • Many independent reactions to the same business fact.
  • Audit, replay, or temporal queries are first-class needs.
  • Read and write loads are wildly asymmetric (CQRS).
Resist when
  • A simple CRUD table answers every question you have.
  • The team isn't ready for eventual consistency and debugging-by-trace.
  • You only need one consumer — a direct call is clearer.
06 · Serverless & the strangler-fig migration 5 min

No servers to mind —
and a safe way to migrate.

Serverlesslets you ship a function, not a fleet: the platform runs it on demand and scales it to zero when idle. And when you finally do modernize a monolith, you don't rewrite it in one terrifying leap — you strangle it, one route at a time.

Serverless / FaaS you deploy functions; the provider handles servers, scaling, and idle-time cost. You pay per request and per millisecond, scale to zero, and never patch an OS. The trade: cold starts, execution limits, and vendor coupling.
Great fit

Spiky or low traffic, glue/cron jobs, event handlers, webhooks, lightweight APIs at the edge.

Poor fit

Steady high traffic (cheaper on always-on compute), long jobs, ultra-low-latency, heavy stateful workloads.

Watch for

Cold starts, timeouts, and lock-in. Keep functions small and the business logic provider-agnostic.

Serverless platforms

AWS Lambda

Pro — deepest ecosystem; integrates with everything in AWS.

Con — cold starts on big runtimes; AWS lock-in.

Choose when you live in AWS and need rich event sources.

Google Cloud Functions

Pro — clean DX, tight Firebase / GCP data integration.

Con — smaller ecosystem than Lambda.

Choose when your data and stack already sit on GCP.

Cloudflare Workers

Pro — runs at the edge with near-zero cold start (V8 isolates).

Con — constrained runtime; not full Node by default.

Choose for global low-latency edge logic and lightweight APIs.

Strangler-fig migration put a proxy in front of the legacy system and redirect one slice of functionality at a time to new code until the old system has nothing left to do and is removed. Named after a vine that grows around a tree and gradually replaces it.
clients facade proxy / route legacy monolith most routes (shrinking) new service one slice (growing)

The facade shifts routes from legacy to new, one slice at a time — no big-bang rewrite.

Why strangle, not rewrite

  • Always shippable — every slice goes live on its own; you never freeze for a year.
  • Reversible — if a new slice misbehaves, route back to legacy in one config change.
  • Risk in small bites — big-bang rewrites are the classic way to kill a product.
  • Start with the slice that hurts most, or changes most — not the easiest.
07 · Choosing a style + recap 3 min

Start simple. Earn every
extra moving part.

There is no "best" architecture — only the cheapest one that meets today's real constraints. The recurring mistake is buying distributed complexity on credit and paying interest forever.

1Default to a modular monolith.Clean boundaries, zero network tax. It's the right answer far more often than its reputation suggests.
2Split a service for a reason— independent scale, deploy, or fault isolation along a real bounded context. Never "because microservices."
3Sync when the user waits; async to decouple. Events buy you resilience at the price of eventual consistency — choose deliberately.
4Add layers only when they pay rent. BFF for diverging clients, gateway for cross-cutting edge, CQRS/event-sourcing for audit and asymmetry — not by default.
5Migrate by strangling. One slice at a time, always shippable, always reversible. Never bet the company on a rewrite.

A 60-second decision guide

  • One team, one product, unclear boundaries? → Monolith, kept modular. Ship features, not diagrams.
  • Boundaries firming up, deploy contention? → Modular monolith with enforced modules; extract the one painful service.
  • Distinct clients fighting over the API? → Add a BFF per client; keep core services shared.
  • Many reactions to the same fact, or spiky load? → Event-driven and/or serverless for those slices — see Message Queues & Streaming.
  • Drowning in a legacy monolith? → Strangler-fig, hardest slice first.
Knowledge check

Did it stick?

Five quick questions on monoliths, BFFs, gateways, events, and migration — instant feedback, no sign-in.

Rate this deck
be the first

Navigate with ← → or scroll · back to library