Skip to main content

Command Palette

Search for a command to run...

Microservices Design Patterns: The Patterns Every Software Architect Should Know

Updated
7 min readView as Markdown
B
Senior Software Architect with 30+ years of experience building enterprise systems using Java, Spring Boot, and cloud-native technologies.

Most developers think microservices are about splitting a monolith.

They're not.

Microservices are actually about managing complexity after the split.

The hardest problems don't appear when you create services.

They appear when hundreds of services start talking to each other.

  • How do you maintain consistency without distributed transactions?

  • How do you prevent cascading failures?

  • How do you discover services dynamically?

  • How do you deploy independently without downtime?

  • How do you trace a single request across dozens of services?

These aren't coding problems.

They're architecture problems.

And architecture problems are solved with design patterns.

After working on distributed systems for years, I've learned that successful microservice platforms aren't built around frameworks.

They're built around patterns.

Here are the design patterns every software architect should understand.


1. API Gateway Pattern

Without an API Gateway, every client communicates directly with every service.

The result?

A tangled web of APIs.

Clients must know service locations, authentication mechanisms, versions, rate limits, and routing rules.

An API Gateway becomes the single entry point.

It handles:

  • Authentication

  • Authorization

  • Rate limiting

  • SSL termination

  • Request routing

  • API aggregation

  • Monitoring

Instead of every service solving the same problems repeatedly, the gateway centralizes them.

Popular technologies include:

  • Spring Cloud Gateway

  • Kong

  • NGINX

  • Envoy

  • Azure API Management

  • AWS API Gateway


2. Service Discovery Pattern

In Kubernetes or cloud-native environments, service IP addresses constantly change.

Hardcoding endpoints is impossible.

Service Discovery allows services to locate each other dynamically.

Two common approaches:

Client-side discovery

The client queries the registry directly.

Examples:

  • Eureka

  • Consul

Server-side discovery

A load balancer or service mesh resolves service locations.

Examples:

  • Kubernetes DNS

  • Istio

  • Linkerd

Without service discovery, scaling becomes painful.


3. Circuit Breaker Pattern

One failing service should never bring down the entire platform.

Unfortunately, that's exactly what happens in many distributed systems.

Imagine:

Service A → Service B → Service C

If Service C becomes slow...

B waits.

A waits.

Threads become blocked.

Connection pools fill up.

Soon the entire application becomes unavailable.

A Circuit Breaker monitors failures.

After a threshold is reached, it "opens" and immediately rejects requests instead of waiting for inevitable failures.

After a recovery period, it allows limited requests to test whether the downstream service has recovered.

Popular implementations:

  • Resilience4j

  • Istio

  • Envoy


4. Bulkhead Pattern

Ships have bulkheads for a reason.

If one compartment floods, the entire ship doesn't sink.

Software should work the same way.

Instead of sharing one thread pool for everything:

  • Payment

  • Search

  • User Profile

  • Notifications

each critical function gets isolated resources.

A failure in one workload cannot consume resources needed by another.

Bulkheads dramatically improve resilience during incidents.


5. Saga Pattern

Distributed transactions don't scale.

Two-Phase Commit introduces latency, complexity, and availability issues.

Instead, modern systems use Sagas.

A Saga breaks one business transaction into multiple local transactions.

Example:

Customer places an order.

  1. Reserve inventory

  2. Create order

  3. Process payment

  4. Arrange shipment

  5. Send confirmation

If payment fails...

The system compensates.

  • Release inventory

  • Cancel order

instead of rolling back a distributed transaction.

Two implementation styles exist:

Choreography

Services react to events independently.

Orchestration

A central workflow coordinates each step.

Workflow engines like Camunda, Temporal, and Netflix Conductor simplify orchestration significantly.


6. Event-Driven Pattern

Synchronous APIs create tight coupling.

Every request depends on another service responding immediately.

Event-driven architecture changes this.

Instead of asking another service to perform work...

A service publishes an event.

Other services subscribe independently.

Example:

Order Created
      │
      ├── Inventory Service
      ├── Payment Service
      ├── Shipping Service
      ├── Analytics Service
      └── Notification Service

Benefits:

  • Loose coupling

  • Better scalability

  • Higher resilience

  • Easier feature expansion

Technologies include:

  • Kafka

  • RabbitMQ

  • Azure Service Bus

  • Google Pub/Sub


7. CQRS Pattern

Not every workload should use the same model for reads and writes.

Large systems often receive thousands of reads for every write.

CQRS separates them.

Command Side

  • Create

  • Update

  • Delete

Query Side

  • Read

  • Search

  • Reporting

Each side can scale independently.

This greatly improves performance for read-heavy systems.


8. Database per Service Pattern

Sharing a database creates hidden coupling.

Soon every service depends on everyone else's schema.

One database change affects multiple teams.

Database per Service solves this.

Each service owns its data.

Communication happens only through APIs or events.

This enables:

  • Independent deployment

  • Independent scaling

  • Technology diversity

  • Team autonomy

It also enforces proper service boundaries.


9. Strangler Fig Pattern

Few organizations can rewrite an entire monolith.

Instead...

Replace it gradually.

The Strangler Pattern routes new functionality to microservices while existing features continue running inside the monolith.

Over time:

Monolith
      ↓
Monolith + Services
      ↓
Mostly Services
      ↓
Monolith Removed

This minimizes risk while enabling continuous modernization.


10. Sidecar Pattern

Some capabilities shouldn't live inside application code.

Examples include:

  • Logging

  • Metrics

  • Security

  • TLS

  • Service discovery

  • Configuration

Instead, deploy them as a companion container.

The application focuses on business logic.

The sidecar handles infrastructure concerns.

Service meshes rely heavily on this pattern.


11. Outbox Pattern

One of the hardest problems in distributed systems is ensuring that a database update and an event publication either both happen or neither does.

Imagine:

  1. Save an order to the database ✅

  2. Publish "OrderCreated" event ❌

Now the database says the order exists, but no downstream service knows about it.

The Outbox Pattern solves this by writing both the business data and an "outbox" event in the same local transaction. A separate process reliably publishes the event afterward.

This pattern is widely used with Kafka and Change Data Capture (CDC) tools such as Debezium to achieve reliable event delivery.


12. Backend for Frontend (BFF) Pattern

Different clients often have different API needs.

A mobile app, a web application, and an admin portal shouldn't all consume the same backend API.

The Backend for Frontend (BFF) pattern provides a dedicated backend for each client type.

Benefits include:

  • Optimized payloads

  • Reduced over-fetching

  • Independent frontend evolution

  • Better user experience

This pattern has become increasingly important as organizations support web, mobile, IoT, and AI-powered interfaces simultaneously.


Common Mistakes I Still See

Many microservice projects fail—not because the technology is wrong, but because the architecture is incomplete.

Some recurring mistakes include:

  • Sharing databases across services

  • Using synchronous calls everywhere

  • Ignoring idempotency in event processing

  • Missing distributed tracing and observability

  • Treating eventual consistency as a bug instead of a design principle

  • Splitting services before identifying clear domain boundaries

  • Choosing microservices for small teams with simple products

Microservices amplify both good and bad architecture. Without clear boundaries and operational discipline, they create complexity faster than they create value.


Final Thoughts

There is no "perfect" microservice architecture.

Every successful platform is a combination of patterns chosen to solve specific business and operational challenges.

The most resilient systems rarely rely on a single pattern. Instead, they combine several:

  • API Gateway for controlled access

  • Service Discovery for dynamic routing

  • Circuit Breakers and Bulkheads for resilience

  • Sagas and Outbox for reliable distributed workflows

  • Event-Driven Architecture for loose coupling

  • CQRS for scalability

  • Database per Service for autonomy

  • Sidecars for infrastructure concerns

  • BFF for client-specific experiences

Great software architects don't memorize patterns.

They understand when to apply them, why they work, and what trade-offs they introduce.

That's what separates systems that survive production from systems that merely work in development.

Which microservices design pattern has had the biggest impact on your architecture—or taught you the hardest lesson? Share your experience in the comments.

More from this blog

B

Bill LIao's Blog

137 posts

A technical blog on modern backend development, software architecture, and practical AI agent workflows