Microservices Architecture Patterns: A Catalogue

Published 26 August 2026.

Every microservices pattern is a bill for an earlier decision. Distribution took away transactions, in-process calls, and a single deployable — and sagas, circuit breakers, and gateways are what you pay to get parts of them back. This catalogue covers the patterns that recur in real systems, what problem each one actually solves, and the cases where adopting it makes things worse.

Patterns are not a menu

Most microservices architecture patterns exist to solve a problem that distribution created in the first place. A saga exists because you gave up transactions. A circuit breaker exists because a local function call became a network call that can hang. An API gateway exists because clients now face ten services instead of one. Reading the catalogue this way — each pattern as the price of an earlier decision — is more useful than treating it as a list of things a modern architecture ought to have.

What follows is the set of patterns that come up repeatedly in real systems, grouped by the problem they address, with the cases where each is the wrong answer. For the practices that sit above these patterns, see microservices best practices.

Decomposition patterns

Decompose by business capability

Draw service boundaries around what the business does — pricing, fulfilment, identity — rather than around technical layers. The test is whether a typical change request lands inside one service. If adding a field to a checkout flow touches four services, the boundaries are wrong regardless of how clean each service looks in isolation.

Decompose by subdomain

The domain-driven variant: identify bounded contexts, then map services onto them. It produces similar boundaries to capability decomposition but gives you an explicit vocabulary per context, which matters when the same word means different things in different parts of the business — an "order" in fulfilment is not an "order" in billing.

Strangler fig

Extract services from a monolith incrementally by routing specific paths to new services while the rest continues to hit the old system, until nothing is left. This is the only decomposition approach with a safe abort: at every step you have a working system, and you can stop. Big-bang rewrites do not offer that. The cost is a routing layer and a period — often years — where both systems are live.

When it is wrong: when the monolith is small enough to rewrite in a quarter, the routing layer is overhead you never recover.

Sidecar

Run cross-cutting concerns — TLS termination, retries, metrics, service discovery — in a separate process deployed alongside each service rather than in a library inside it. This is what a service mesh does, systematically. The gain is that you upgrade the mesh instead of upgrading a library in eleven languages. The cost is a second process per service and a control plane to run.

When it is wrong: below roughly ten services, a shared library is simpler and the mesh's operational surface is not worth it.

API gateway and edge patterns

API gateway

A single entry point that fronts the service estate: it authenticates, rate-limits, routes, and often reshapes responses. The reason it earns its place is that these concerns are identical for every service, and implementing them once at the edge is both cheaper and more consistent than implementing them eleven times. It also decouples the client-facing URL structure from the internal service topology, which is what lets you split or merge services without breaking clients.

The failure mode is business logic migrating into the gateway. Once the gateway knows what an order is, every team is blocked on gateway releases and you have rebuilt the monolith at the edge with worse tooling.

Backend for frontend (BFF)

One gateway per client type — web, iOS, Android, partner API — each shaped for that client's needs. A mobile client wants few, fat responses; a web client can afford more, thinner calls. A single gateway serving both ends up serving neither. BFFs are owned by the client teams, which is the point: the team that feels the pain of a bad response shape is the team that can fix it.

When it is wrong: with one client, a BFF is a gateway with extra steps.

Gateway offloading and aggregation

Aggregation composes several service calls into one client-facing response. It is useful for high-latency clients, and dangerous as a habit: an aggregating endpoint acquires dependencies on every service it touches, and its availability becomes the product of theirs. Aggregate where a client would otherwise make five sequential round trips; do not aggregate because the response looks tidier.

Communication patterns

Request/response over HTTP

The default, and the right one for reads. Keep it synchronous, give it a deadline shorter than your caller's deadline, and make sure every write it fronts is retry-safe — see idempotency keys for APIs.

Event-driven messaging

Services publish facts; interested services subscribe. This is what actually decouples services, because the publisher does not know who consumes. It also moves you from "did this call fail?" to "has this event been processed yet?", which is a harder question to answer and needs tooling — consumer lag metrics, dead-letter queues, replay.

When it is wrong: when the caller needs the answer to continue. Wrapping a synchronous need in asynchronous machinery produces a request/response system with extra latency and no error path.

Circuit breaker, bulkhead, and deadline propagation

Three patterns that together stop one slow service from taking down the estate. A circuit breaker stops calling a failing dependency and fails fast instead of queueing. A bulkhead caps the resources any single dependency can consume, so a stall in one does not exhaust the whole connection pool. Deadline propagation passes the remaining time budget down the call chain so nobody does work whose result is already too late to matter. Adopt all three or the gaps between them are where the incidents happen.

Data patterns

Database per service

Each service owns its schema and no other service reads it directly. This is the pattern that makes the rest possible: a shared database re-couples deployments through schema changes, which is exactly what you split the services to avoid. The cost is that joins across services become application code, and reporting needs a separate path.

Saga

Model a business transaction spanning services as a sequence of local transactions, each with a compensating action that undoes it. Choreographed sagas propagate events; orchestrated sagas have a coordinator that drives the steps. Orchestration is easier to debug and easier to see; choreography couples less. Choose orchestration unless you have a specific reason not to — the debuggability difference is larger than it looks on a whiteboard.

The hard part is that compensation is not rollback. You cannot un-send an email or un-charge a card without a visible refund. Sagas need business sign-off on what "undo" means, not just engineering design.

CQRS

Separate the write model from one or more read models, each shaped for its queries. It solves the genuine problem of a normalised write schema being wrong for reporting or search. It also introduces eventual consistency into the user experience, which means a user can submit a change and not see it in the list. That is a product decision, not just a technical one.

When it is wrong: almost always, at first. CQRS applied to a service whose reads and writes look similar adds a synchronisation problem in exchange for nothing.

Transactional outbox

Write the domain change and the event to be published in the same local transaction, then have a separate process ship the outbox rows to the broker. This is how you avoid the failure where the database commit succeeds and the publish does not, leaving other services permanently unaware. It is unglamorous and it is the single most valuable pattern on this page for anyone doing event-driven work.

Observability and operational patterns

Distributed tracing attaches a trace ID at the edge and propagates it through every hop, so one request can be reconstructed. Install it before you need it; retrofitting tracing during an incident is not possible. Health check APIs distinguish liveness (is the process up) from readiness (can it serve traffic) — conflating them causes rolling deploys to route traffic to services that are still warming up. Log aggregation with a shared correlation ID is what turns eleven log streams back into one story.

Choosing between them

A short decision sequence that fits most situations:

  1. Start with capability-based boundaries and a database per service. Everything else is a response to a problem you have not had yet.
  2. Add a gateway when you have more than one public-facing service, and a BFF only when you have a second client type with genuinely different needs.
  3. Add circuit breakers, bulkheads, and deadlines as soon as any request crosses two services. These are not optional at any scale.
  4. Add the outbox pattern the day you publish your first event.
  5. Add sagas when a business process genuinely spans services, and get agreement on compensation before you write code.
  6. Add CQRS, service mesh, and event sourcing only against a specific, current, measured problem.

The pattern you do not adopt costs nothing to maintain. That asymmetry should bias every decision on this page.

Where to go next

For the surrounding practices — sizing, ownership, testing, governance — see Microservices Best Practices. For what changes when the estate grows past a handful of teams, see Microservices at Enterprise Scale. For the contract-level decisions each of these patterns depends on, see API Design Best Practices and API Versioning and Schema Evolution.