Please 🖱️ here for Github Blogs

Microservice Design Patterns

  • June 19, 2025
Table of Contents

Microservice design patterns — catalogued primarily by Chris Richardson in Microservices Patterns (44+ patterns across 8 categories) — provide battle-tested solutions to recurring problems in distributed systems. This reference covers all pattern groups, their sub-patterns, when to apply each and the trade-offs involved, followed by supplementary Microsoft cloud patterns.


Decomposition Patterns

The foundational challenge: how to split a monolith into services.

Decompose by Business Capability

Define services around business capabilities (e.g., Order Management, Inventory, Shipping) rather than technical layers. Each service owns a distinct business function. Drives alignment with Conway’s Law — organisation structure mirrors system architecture.

Use when you have clear organizational boundaries aligned with business units. Works well when capabilities have low interdependency.

Don’t use when capabilities are tightly coupled or share lots of data — you’ll end up with chatty services and distributed transactions.

Decompose by Subdomain (DDD)

Use Domain-Driven Design’s strategic patterns — bounded contexts, aggregates and domain events — to identify service boundaries. Each bounded context maps to one service.

Use when the domain is complex and you have DDD-experienced teams. Produces services with high cohesion and loose coupling.

Don’t use when the domain is simple CRUD; the overhead of DDD isn’t justified.

Strangler Fig

Gradually replace monolith functionality with microservices by routing specific requests to new services. The monolith is “strangled” over time until it can be decommissioned.

Use when migrating an existing monolith incrementally with zero downtime. Requires a routing layer (API gateway or reverse proxy) that can split traffic per-feature.

Don’t use when you need a full rewrite — strangler fig is explicitly for incremental, coexistence-based migration.

Anti-Corruption Layer (ACL)

Implement a facade or adapter between a modern application and a legacy system to prevent legacy domain concepts from leaking into the new system. Translates between the two domain models.

Use when integrating with a legacy system with a different domain model, especially during strangler fig migration.

Don’t use when there is no legacy system or the integration surface is trivial.


Communication Patterns

The most diverse group, spanning synchronous, asynchronous and hybrid approaches.

API Gateway

A single entry point that routes requests to appropriate services, handles cross-cutting concerns (auth, rate limiting, logging, TLS termination) and may aggregate responses.

Microsoft further decomposes this into:

  • Gateway Routing — route requests to services based on path/headers
  • Gateway Aggregation — combine multiple backend responses into a single client response
  • Gateway Offloading — offload shared functionality (TLS, auth, SSL termination) to the gateway

Use when clients need a unified API, you want to offload cross-cutting concerns or you need protocol translation (e.g., external REST to internal gRPC).

Don’t use when the added hop (latency) is unacceptable or for simple systems where clients can call services directly.

Backend for Frontend (BFF)

Dedicate a separate API gateway per client type (web BFF, mobile BFF, IoT BFF). Each BFF knows the specific data and interaction needs of its client.

Use when client types have substantially different API requirements or when mobile teams need to evolve their API independently of web.

Don’t use when clients are homogeneous — a single API gateway suffices.

Service Discovery

In a microservice architecture, service instances have dynamically assigned network locations. Service discovery patterns handle locating them at runtime.

  • Client-Side Discovery — the client queries a service registry to get the list of available instances and load-balances across them directly
  • Server-Side Discovery — the client makes a request to a load balancer or API gateway, which queries the registry and forwards the request
  • Service Registry — a highly available database (Consul, Eureka, ZooKeeper, Kubernetes DNS) that stores the network locations of service instances
  • Self Registration — a service instance registers itself with the registry on startup and deregisters on shutdown
  • Third Party Registration — a third-party registrar (e.g., Kubernetes, Nomad) handles registration/deregistration without the service needing to know about the registry

Use when service locations are dynamic (container orchestration, auto-scaling) and you need to decouple clients from specific instance addresses.

Don’t use when using a service mesh (Envoy/Istio) — the mesh handles discovery transparently at the sidecar level.

Remote Procedure Invocation (RPI)

Synchronous communication via REST, gRPC or Thrift. gRPC offers strong contracts via protobuf, streaming and bidirectional flow.

Use when low-latency, real-time request-response semantics are needed. gRPC especially for high-performance internal service-to-service calls.

Don’t use when services must remain loosely coupled and independently evolvable — async messaging is a better fit.

Domain-Specific Protocol

Use a custom protocol tailored to a specific domain (e.g., SMTP for email, RTMP for streaming). Avoid generic protocols when domain semantics justify a specialised wire format.

Async Messaging / Event-Driven

Services communicate via messages published to a broker (Kafka, RabbitMQ, AWS SQS/SNS). The producer and consumer are decoupled in time and space.

Use when you need loose coupling, event-driven workflows, or integration across heterogeneous systems.

Don’t use when request-response semantics are required and the added latency of async is unacceptable.

Publisher-Subscriber

An intermediary (topic/exchange) fans out messages to all subscribers. Enables broadcast of domain events to multiple consumers.

Use when a single event must trigger multiple independent actions (e.g., “OrderPlaced” triggers billing, shipping, notifications).

Don’t use when you need point-to-point delivery with competing consumers — use a queue instead.

Claim Check

Split a large message into a claim check (reference) and payload stored elsewhere. The message bus carries only the reference.

Use when message payloads exceed broker size limits or you want to avoid transferring large payloads through the bus.

Competing Consumers

Multiple consumer instances read from the same queue and compete for messages. Each message is processed by exactly one consumer.

Use when you need to scale throughput horizontally by adding more consumer instances.

Don’t use when message ordering is critical (competing consumers break ordering guarantees).

Priority Queue

Priority queue ensures high-priority messages are processed before low-priority ones.

Use when different classes of workload have different urgency levels.


Data Management Patterns

Each service owns its data; no shared database across services. Services communicate only via their API.

Use when you need independent deployability, schema evolution per service and isolated failure domains. The default choice for microservices.

Don’t use when reporting or cross-service queries become too complex — you may need a CQRS view or an aggregated reporting database (with careful ownership).

Database per Service

The canonical microservice data pattern. Each service has its own private database — no direct access by other services.

Saga

Manage data consistency across multiple services without distributed transactions (which are not feasible in microservices).

Choreography: Each service publishes events after local transactions; downstream services listen and react. Decentralised, simple but hard to track.

Orchestration: A central coordinator (orchestrator) tells each service what to do and handles compensation on failure. Centralised, easier to monitor but adds a dependency.

Use when a business operation spans multiple services and requires eventual consistency with rollback capability.

Don’t use when strong consistency across services is mandatory (rare in microservices — consider a monolith instead).

API Composition

Perform a query that retrieves data from multiple services by having an API composer (often the API gateway or a dedicated service) invoke each service and combine the results.

Use when data is spread across multiple services and the query can be satisfied with simple joins from a few services.

Don’t use when the query requires complex joins or aggregations across large datasets — use CQRS with a materialised view instead.

CQRS (Command Query Responsibility Segregation)

Split service into command and query models, often with separate data stores. Commands use write-optimised storage; queries use read-optimised views (possibly denormalised).

Use when reads and writes have asymmetric throughput requirements, when queries are complex (need multiple DB types) or when event sourcing is in play.

Don’t use when the domain is simple or the consistency lag between models causes user-facing confusion (must tolerate eventual consistency).

Event Sourcing

Persist state as a sequence of domain events rather than a single current-state row. Reconstruct current state by replaying events.

Use when you need a complete audit trail, temporal queries (state at any point in time) or complex event-driven workflows.

Don’t use when query performance matters (event replay is expensive for high-read systems), the event schema changes frequently or the domain is simple CRUD.

Transactional Outbox

Persist a domain event in the same local database transaction as the state change. A relay process publishes the event to a message broker. Two primary implementation strategies:

  • Transaction Log Tailing (CDC) — a process (Debezium, Kafka Connect) tails the database transaction log and publishes changes to the message broker. Zero impact on application code.
  • Polling Publisher — a background process periodically polls an outbox table for unpublished events and publishes them. Simpler to implement but adds latency and database load.

Use when you need reliable event publishing without 2PC (two-phase commit). Guarantees at-least-once delivery.

Don’t use when your database doesn’t support the required primitives (e.g., lack of CDC capability in some hosted DBs).

Shared Database

Services share a single database (anti-pattern for microservices but used during strangler fig migration).

Use when migrating incrementally from a monolith — keep a shared DB temporarily while splitting services.

Don’t use when building new microservices — it couples deployability, creates contention and prevents independent scaling.

Materialized View

Precompute and store denormalised views of data across multiple services to serve read queries efficiently.

Use when you need to query across service boundaries without complex runtime joins.

Sharding

Partition a data store horizontally across multiple database instances using a shard key. Not a microservice-specific pattern but essential for data scalability in distributed systems.


Observability Patterns

Log Aggregation

Centralise logs from all services into a searchable store (ELK, Loki, CloudWatch). Each service writes structured, correlated logs.

Use when you have 3+ services and need root-cause analysis across service boundaries.

Application Metrics

Collect and expose metrics (request rate, error rate, latency percentiles, resource utilisation) from each service. Tools: Prometheus, Micrometer, OpenTelemetry.

Use when you need to monitor service health, set up alerts and drive auto-scaling decisions.

Distributed Tracing

Propagate a trace ID across service calls to reconstruct end-to-end request flows. Tools: Jaeger, Zipkin, OpenTelemetry.

Use when latency-sensitive or multi-service operations need per-request debugging.

Health Check

Expose /health (liveness) and /ready (readiness) endpoints for load balancers, orchestrators and monitoring systems.

Use with Kubernetes liveness/readiness probes for automatic pod replacement.

Exception Tracking

Aggregate and alert on exceptions across services using tools like Sentry, Rollbar or Application Insights.


Reliability Patterns

Circuit Breaker

Wrap calls to external services in a circuit breaker. On repeated failures the circuit “opens” and subsequent calls fail fast (or return a fallback). After a timeout, a “half-open” trial determines if the service recovered.

Use when calling remote services that may fail or slow down; prevents cascading failures.

Bulkhead

Isolate resources (thread pools, connections, memory) per service or client group so failure in one partition doesn’t exhaust shared resources.

Use when you must guarantee that a failing client or service doesn’t starve others.

Retry with Exponential Backoff

Retry transient failures with increasing delay + jitter to avoid thundering herd on recovery.

Use when failures are transient (network blips, DB connection timeouts).

Don’t use when failures are non-transient (400 Bad Request) — retrying is wasteful.

Timeout

Bound the maximum time a service waits for a response from a downstream dependency. Fail fast rather than hanging indefinitely.

Use when every remote call — always. Timeouts prevent resource exhaustion from slow or hung dependencies.

Rate Limiting / Throttling

Control the rate of requests a service accepts. Rate limiting is applied at the client side to stay within limits; throttling is server-side enforcement that returns 429 Too Many Requests.

Use when you need to protect a service from overload, enforce SLAs or control costs.

Queue-Based Load Leveling

Use a queue as a buffer between a producer (task sender) and consumer (task processor) to smooth intermittent heavy loads.

Use when workload spikes are common and you want to protect the backend from being overwhelmed.

Scheduler Agent Supervisor

Coordinate a set of distributed actions. A scheduler initiates the workflow, an agent performs each action and a supervisor monitors outcomes, retrying or compensating on failures.

Use when you need a reliable, distributed batch process or long-running workflow.

Leader Election

Elect one instance among a cluster to coordinate tasks (e.g., which instance processes the CDC stream). Tools: ZooKeeper, etcd, Kubernetes leader election.

Use when only one instance should perform a task at a time and you need automatic failover.


Security Patterns

Access Token

Clients present an access token (opaque or JWT) with each request. The API gateway or service validates the token before processing.

JWT (JSON Web Token)

A self-contained access token format carrying claims (user ID, roles, expiry) signed by the issuer. Stateless verification — no database lookup needed.

Use when you need stateless, distributed authentication across services.

Federated Identity

Delegate authentication to an external identity provider (Azure AD, Okta, Auth0) rather than building your own auth system.

Gatekeeper

A dedicated, hardened host instance that validates and sanitises all requests before forwarding them to internal services. Reduces attack surface.

Valet Key

Provide clients with a restricted, time-limited token that grants direct access to a specific resource (e.g., Azure SAS, presigned S3 URL) without exposing the master key.


Deployment Patterns

Service Instance per Container

Each service instance runs in its own container (Docker), orchestrated by Kubernetes, Nomad or similar. The standard deployment model.

Service Instance per VM

Each service instance runs in its own VM. Stronger isolation but heavier and slower to provision.

Multiple Service Instances per Host

Run multiple service instances (or different services) on the same host/VM. Simpler operations but weaker isolation and risk of resource contention.

Blue/Green Deployment

Run two identical environments (blue and green). Route traffic to green; when green is healthy, switch traffic. Enables zero-downtime deploys and instant rollback.

Canary Deployment

Roll out new version to a small subset of users, monitor, then gradually increase traffic. Cuts blast radius of bad releases.

Serverless

Deploy individual functions (AWS Lambda, Cloud Functions) that scale automatically per request. Good for event-driven workloads and simple APIs.

Use when traffic is spiky or unpredictable and cold-start latency is acceptable.

Don’t use when workloads are steady-state (provisioned servers are cheaper) or functions require long-running compute.

Deployment Stamps

Deploy multiple independent copies of the entire application stack (including data stores), typically per tenant or per region. Each stamp is fully self-contained.

Use when multi-tenant isolation, geo-distribution or per-customer deployment is required.


Testing Patterns

Consumer-Driven Contract Tests

The consumer defines the expected contract (request/response shape). The provider verifies it satisfies all consumer contracts in CI. Tools: Pact, Spring Cloud Contract.

Use when multiple consumers depend on a single service and you need to detect breaking changes early.

Consumer-Side Contract Tests

The consumer validates that the provider API matches the expected contract. Run on the consumer side in CI; often paired with a mock provider.

Service Component Test

Test a single service in isolation by stubbing its dependencies. Fast, cheap and run in CI on every commit.

Service Integration Contract Test

Test the integration between a service and its external dependencies using real infrastructure (or lightweight test containers).

End-to-End Test

Test a cross-service flow end-to-end, including infrastructure. Slow and brittle — use sparingly for critical paths only.


Cross-Cutting Patterns

Microservice Chassis

Start each service from a standardised foundation (framework, logging, metrics, configuration, health checks). Reuse across the organisation to reduce boilerplate.

Externalised Configuration

All configuration (DB URLs, feature flags, secrets) lives outside the service code — environment variables, config server (Consul, Spring Cloud Config) or Kubernetes ConfigMaps/Secrets.

Use when configuration varies across environments (dev/staging/prod) or changes without a redeploy.

Ambassador

A helper sidecar that handles network concerns (retries, circuit breaking, authentication, observability) on behalf of the application container. Placed on the same pod/host as the service.

Use when you want to offload cross-cutting network concerns without modifying the application code or adopting a full service mesh.

Service Mesh (Sidecar)

Offload cross-cutting concerns (service discovery, retries, circuit breaking, mTLS, observability) to a sidecar proxy (Envoy, Linkerd, Istio). Application code stays focused on business logic.

Use when the organisation has many services and wants platform-level traffic management without changing application code.

Don’t use when the operational complexity of running a mesh outweighs the benefits (small clusters, few services).


Additional Microsoft Cloud Patterns

The following patterns from the Microsoft Azure Architecture Center address concerns not covered by the core Chris Richardson catalog but are relevant to production microservices:

PatternPurpose
AmbassadorOffload network concerns to a helper sidecar (already covered above)
Cache-AsideLoad data into a cache on-demand; the cache is populated lazily on read miss
Claim CheckSplit large messages into reference + blob (already covered above)
Competing ConsumersScale message processing horizontally (already covered above)
Compute Resource ConsolidationConsolidate multiple tasks into a single compute unit to reduce cost
Deployment StampsPer-tenant/region deployment copies (already covered above)
GatekeeperHardened request validation proxy (already covered above)
GeodeDeploy backend services across geographically distributed nodes; any node can serve any request
Leader ElectionCoordinate single-active-instance tasks (already covered above)
Materialized ViewPrecomputed cross-service query views (already covered above)
Priority QueuePrioritised message processing (already covered above)
Queue-Based Load LevelingBuffer spikes with a queue (already covered above)
Scheduler Agent SupervisorReliable distributed batch workflow (already covered above)
Sequential ConvoyProcess related messages in order without blocking other message groups
ShardingHorizontal data partitioning (already covered above)
Static Content HostingServe static assets directly from cloud storage (CDN + blob storage)
ThrottlingServer-side request rate enforcement (already covered above)
Valet KeyScoped, time-limited direct resource access (already covered above)

Cache-Aside

On a read miss, the application loads data from the database into the cache (Redis, Memcached). Subsequent reads hit the cache. Writes invalidate the cache entry.

Use when read-heavy workloads benefit from low-latency cache hits.

Don’t use when data changes so frequently that cache invalidation overhead outweighs benefits.

Geode

Deploy backend services across multiple geographically distributed nodes, each capable of handling any client request. Provides low-latency access regardless of client location.

Use when you have a global user base and need sub-100ms response times from every region.

Sequential Convoy

Process related messages in a defined order without blocking other groups. Each group maintains its own ordering sequence (e.g., all events for a single order ID are processed in order).

Use when ordering within a partition matters but cross-partition ordering does not.

Static Content Hosting

Serve static assets (images, CSS, JS) directly from cloud blob storage (Azure Blob, S3) fronted by a CDN rather than from application servers.


Pattern Selection Guide

ProblemPrimary Pattern
How to split services?Decompose by subdomain (DDD)
How to migrate?Strangler Fig
How to find services?Service Registry + Client/Server Discovery
How to route traffic?API Gateway
Different clients?BFF
Distributed transaction?Saga
Reliable event publishing?Transactional Outbox (CDC or Polling)
Query across services?API Composition or CQRS + Materialized View
Audit trail / temporal queries?Event Sourcing
Read/write asymmetry?CQRS
Prevent cascading failures?Circuit Breaker + Bulkhead
Protect from overload?Rate Limiting / Queue-Based Load Leveling
Isolate concerns from code?Ambassador or Service Mesh
Client-independent deploys?Consumer-Driven Contracts
Zero-downtime deploys?Blue/Green or Canary
Global low-latency?Geode

References

Share :
comments powered by Disqus

Related Posts

Kong API Gateway

Kong is an open-source cloud-native API gateway for managing securing and orchestrating API traffic

Read More

System Design Principles and Concepts

System design is a critical aspect of software engineering that involves creating scalable, reliable and efficient systems.

Read More

Practising System Design - Youtube

Practical approach to System Design interviews, followed by a deep-dive into designing a scalable video platform like Youtube.

Read More