Microservices Architecture Patterns
Essential patterns for designing, deploying, and operating microservices: decomposition, communication, data management, and operational patterns.
Microservices architecture structures an application as a collection of loosely coupled, independently deployable services. This guide covers the essential patterns.
Decomposition Patterns
1. Decompose by Business Capability
Organize around business functions, not technical layers.
❌ Technical layers:
├─ UI Service
├─ Business Logic Service
└─ Data Access Service
✅ Business capabilities:
├─ Order Management
├─ Customer Management
├─ Inventory Management
├─ Payment Processing
└─ Shipping & Fulfillment
2. Decompose by Subdomain (DDD)
Use Domain-Driven Design to identify bounded contexts.
// Each bounded context = potential microservice
public class OrderContext { // Order Management
// Entities: Order, OrderItem, Shipment
// Aggregates: Order (root)
}
public class CustomerContext { // Customer Management
// Entities: Customer, Address, PaymentMethod
// Aggregates: Customer (root)
}
public class InventoryContext { // Inventory Management
// Entities: Product, Warehouse, StockLevel
// Aggregates: Product (root)
}
3. Strangler Fig Pattern
Incrementally migrate from monolith.
Phase 1: Phase 2: Phase 3:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Monolith │ │ Monolith │ │ Monolith │
│ │ │ ┌───────┐ │ │ ┌───────┐ │
│ [Orders] │──────────▶ │ [Orders] │──────────▶ │ [Orders] │
│ [Customer] │ Extract │ [Customer] │ Extract │ [Customer] │
│ [Inventory]│ │ [Inventory]│ │ [Inventory]│
└─────────────┘ │ ┌───────┐ │ │ ┌───────┐ │
│ │Order Svc│ │ │ │Order Svc│ │
│ └───────┘ │ │ └───────┘ │
└─────────────┘ └─────────────┘
Communication Patterns
1. Synchronous (Request-Response)
REST/HTTP
GET /api/v1/orders/{orderId}
Authorization: Bearer <token>
Accept: application/json
Response: 200 OK
{
"orderId": "ord_123",
"customerId": "cust_456",
"status": "SHIPPED",
"items": [...]
}
gRPC (Recommended for internal)
// order.proto
service OrderService {
rpc GetOrder(GetOrderRequest) returns (Order);
rpc CreateOrder(CreateOrderRequest) returns (Order);
rpc StreamOrders(StreamOrdersRequest) returns (stream Order);
}
message Order {
string order_id = 1;
string customer_id = 2;
OrderStatus status = 3;
repeated OrderItem items = 4;
}
2. Asynchronous (Event-Driven)
// Event publication
public class OrderService {
private final EventPublisher publisher;
public Order createOrder(CreateOrderCommand cmd) {
Order order = Order.create(cmd);
orderRepository.save(order);
// Publish domain event
publisher.publish(new OrderCreatedEvent(
order.getId(),
order.getCustomerId(),
order.getItems(),
order.getTotal()
));
return order;
}
}
// Event consumption (in Payment Service)
@EventListener
public class PaymentEventHandler {
private final PaymentService paymentService;
@EventHandler
public void handle(OrderCreatedEvent event) {
paymentService.processPayment(
event.getOrderId(),
event.getCustomerId(),
event.getTotal()
);
}
}
3. API Gateway Pattern
Single entry point for all clients.
# Kong API Gateway config
services:
- name: order-service
url: http://order-service:8080
routes:
- name: orders
paths: ["/api/orders"]
methods: ["GET", "POST"]
plugins:
- name: rate-limiting
config:
minute: 1000
policy: redis
- name: jwt
- name: correlation-id
- name: customer-service
url: http://customer-service:8080
routes:
- name: customers
paths: ["/api/customers"]
4. Backend for Frontend (BFF)
Tailored APIs per client type.
┌─────────────┐
│ Web BFF │──▶ Web App
└──────┬──────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Orders │ │Customer │ │Inventory│
│ Service │ │ Service │ │ Service │
└─────────┘ └─────────┘ └─────────┘
│ │ │
└────────────┼────────────┘
▼
┌─────────────┐
│ Mobile BFF │──▶ Mobile App
└─────────────┘
Data Management Patterns
1. Database per Service
Each service owns its data.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Order │ │ Customer │ │ Inventory │
│ Service │ │ Service │ │ Service │
│ ┌───────┐ │ │ ┌───────┐ │ │ ┌───────┐ │
│ │OrderDB│ │ │ │CustDB │ │ │ │InvDB │ │
│ └───────┘ │ │ └───────┘ │ │ └───────┘ │
└─────────────┘ └─────────────┘ └─────────────┘
2. Saga Pattern (Distributed Transactions)
// Choreography-based saga
public class OrderSaga {
private final EventPublisher publisher;
public void createOrder(CreateOrderCommand cmd) {
// Step 1: Create pending order
Order order = Order.createPending(cmd);
orderRepository.save(order);
// Step 2: Reserve inventory (async)
publisher.publish(new ReserveInventoryCommand(
order.getId(), order.getItems()
));
}
@EventHandler
public void handle(InventoryReservedEvent event) {
Order order = orderRepository.find(event.getOrderId());
order.confirmInventoryReserved();
orderRepository.save(order);
// Step 3: Process payment
publisher.publish(new ProcessPaymentCommand(
order.getId(), order.getCustomerId(), order.getTotal()
));
}
@EventHandler
public void handle(PaymentProcessedEvent event) {
Order order = orderRepository.find(event.getOrderId());
order.confirmPayment();
orderRepository.save(order);
// Step 4: Create shipment
publisher.publish(new CreateShipmentCommand(order));
}
// Compensating transactions
@EventHandler
public void handle(InventoryReservationFailedEvent event) {
Order order = orderRepository.find(event.getOrderId());
order.cancel("Inventory unavailable");
orderRepository.save(order);
}
}
3. Event Sourcing
Store state changes as events.
// Event-sourced aggregate
public class Order {
private String orderId;
private OrderStatus status;
private List<OrderItem> items;
private List<DomainEvent> uncommittedEvents = new ArrayList<>();
public static Order create(CreateOrderCommand cmd) {
Order order = new Order();
order.apply(new OrderCreatedEvent(
cmd.getOrderId(), cmd.getCustomerId(), cmd.getItems()
));
return order;
}
public void addItem(AddItemCommand cmd) {
if (status != OrderStatus.PENDING) {
throw new IllegalStateException("Cannot modify confirmed order");
}
apply(new ItemAddedEvent(orderId, cmd.getItem()));
}
private void apply(DomainEvent event) {
when(event);
uncommittedEvents.add(event);
}
private void when(OrderCreatedEvent e) {
this.orderId = e.getOrderId();
this.status = OrderStatus.PENDING;
this.items = new ArrayList<>();
}
private void when(ItemAddedEvent e) {
this.items.add(e.getItem());
}
}
4. CQRS (Command Query Responsibility Segregation)
Separate read and write models.
// Write model (commands)
public class OrderCommandHandler {
private final EventStore eventStore;
@CommandHandler
public void handle(CreateOrderCommand cmd) {
Order order = Order.create(cmd);
eventStore.save(order.getUncommittedEvents());
}
}
// Read model (queries) - optimized for reading
@Entity
@Table(name = "order_summary")
public class OrderSummary {
@Id String orderId;
String customerName;
OrderStatus status;
BigDecimal total;
LocalDateTime createdAt;
int itemCount;
}
// Projector updates read model
@EventHandler
public class OrderProjector {
private final OrderSummaryRepository readRepo;
public void on(OrderCreatedEvent event) {
readRepo.save(new OrderSummary(
event.getOrderId(),
event.getCustomerName(),
OrderStatus.PENDING,
event.getTotal(),
event.getTimestamp(),
event.getItems().size()
));
}
}
Operational Patterns
1. Service Discovery
# Consul service registration
service:
name: order-service
port: 8080
tags: ["v1", "api"]
check:
http: http://localhost:8080/health
interval: 10s
timeout: 5s
2. Configuration Management
# Spring Cloud Config / Consul Config
order-service:
database:
url: ${DATABASE_URL}
pool-size: 20
inventory:
service-url: http://inventory-service
timeout: 5s
retry-attempts: 3
circuit-breaker:
failure-threshold: 50%
timeout: 30s
3. Distributed Tracing
// OpenTelemetry instrumentation
@SpanAttribute
public Order getOrder(String orderId) {
return tracer.spanBuilder("getOrder")
.setAttribute("order.id", orderId)
.startScopedSpan(() -> {
return orderRepository.findById(orderId);
});
}
4. Health Checks
@Component
public class OrderHealthIndicator implements HealthIndicator {
private final OrderRepository repository;
private final InventoryClient inventoryClient;
@Override
public Health health() {
// Check database
if (!repository.isHealthy()) {
return Health.down()
.withDetail("database", "unavailable")
.build();
}
// Check dependencies
if (!inventoryClient.isHealthy()) {
return Health.up()
.withDetail("inventory", "degraded")
.withDetail("database", "healthy")
.build();
}
return Health.up()
.withDetail("database", "healthy")
.withDetail("inventory", "healthy")
.build();
}
}
Anti-Patterns to Avoid
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Distributed Monolith | Services must deploy together | Ensure independent deployability |
| Shared Database | Tight coupling, schema conflicts | Database per service |
| Chatty Communication | Latency, cascade failures | Async events, batch APIs |
| Synchronous Chains | A→B→C→D blocks | Event-driven, saga |
| Data Duplication | Inconsistency | Single source of truth + events |
| Nanoservices | Operational overhead | Right-size services |
Technology Choices
| Category | Options |
|---|---|
| Service Mesh | Istio, Linkerd, Consul Connect |
| API Gateway | Kong, AWS API Gateway, Traefik, Zuul |
| Service Discovery | Consul, etcd, Eureka, Kubernetes DNS |
| Message Broker | Kafka, RabbitMQ, NATS, Pulsar |
| Distributed Tracing | Jaeger, Zipkin, Tempo, AWS X-Ray |
| Metrics | Prometheus + Grafana, Datadog, CloudWatch |
| Logging | ELK/EFK, Loki, Splunk |
| Deployment | Kubernetes, Nomad, ECS, Cloud Run |
Further Reading
- Microservices Patterns - Chris Richardson
- Building Microservices - Sam Newman
- Enterprise Integration Patterns - Hohpe & Woolf
- Domain-Driven Design - Eric Evans
- Release It! - Michael Nygard
Related Notes
Monolith vs Microservices
A practical comparison of monolithic and microservice architectures — coupling, deployment, scaling, team context — and how to choose between them.
Event-Driven Architecture
Communicating through immutable, persisted events — replayability, idempotency, event sourcing, CQRS, and the trade-offs of eventual consistency.
Netflix Architecture: Microservices at Scale
How Netflix built a resilient, scalable streaming platform serving 230M+ subscribers across 190 countries.