πŸ›οΈ

Microservices Design Patterns

System Architecture Intermediate 2 min read 300 words
Design Patterns System Design Microservices

Microservices Design Patterns

A comprehensive guide to essential design patterns for building robust microservices architectures.

Communication Patterns

1. API Gateway Pattern

Central entry point for all client requests.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Client   │────▢│ API Gateway │────▢│  Services    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Responsibilities:

  • Request routing
  • Rate limiting
  • Authentication/Authorization
  • Load balancing
  • Response aggregation

Implementation (ASP.NET Core + Ocelot):

// ocelot.json
{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/orders/{everything}",
      "DownstreamScheme": "https",
      "DownstreamHostAndPorts": [{ "Host": "orders-service", "Port": 443 }],
      "UpstreamPathTemplate": "/orders/{everything}",
      "UpstreamHttpMethod": [ "GET", "POST", "PUT" ]
    }
  ]
}

2. Service Discovery

Dynamic service location without hardcoded addresses.

Client-Side Discovery:

// Using Consul
var services = await _consul.Health.Service("order-service", passing: true);
var selectedService = LoadBalance(services);

Server-Side Discovery:

  • Load balancer queries service registry
  • Kubernetes built-in DNS

3. Circuit Breaker

Prevent cascade failures when services are unavailable.

// Using Polly
var circuitBreaker = Policy
    .Handle<HttpRequestException>()
    .CircuitBreakerAsync(
        exceptionsAllowedBeforeBreaking: 3,
        durationOfBreak: TimeSpan.FromSeconds(30),
        onBreak: (ex, duration) => _logger.LogWarning("Circuit open"),
        onReset: () => _logger.LogInformation("Circuit closed")
    );

States:

  • Closed: Normal operation
  • Open: Requests fail immediately
  • Half-Open: Testing if service recovered

Data Management Patterns

4. Database per Service

Each service owns its data store.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Order Svc   β”‚     β”‚ User Svc    β”‚     β”‚ Product Svc β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚                   β”‚                   β”‚
β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
β”‚  Orders DB  β”‚     β”‚  Users DB   β”‚     β”‚ Products DB β”‚
β”‚  (SQL)      β”‚     β”‚  (Postgres) β”‚     β”‚  (MongoDB)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Benefits:

  • Loose coupling
  • Independent scaling
  • Technology freedom
  • Data isolation

5. Saga Pattern

Distributed transactions across services.

Choreography (Event-driven):

public class OrderCreatedHandler : IHandleMessages<OrderCreated>
{
    public async Task Handle(OrderCreated message)
    {
        var result = await _inventory.ReserveStock(message.Items);

        if (result.Success)
            await _bus.Publish(new StockReserved { OrderId = message.OrderId });
        else
            await _bus.Publish(new OrderCancelled { OrderId = message.OrderId });
    }
}

Orchestration (Central coordinator):

public class CreateOrderSaga : Saga<CreateOrderSagaData>
{
    public async Task Start(CreateOrder command)
    {
        await RequestTimeout<OrderTimeout>(TimeSpan.FromMinutes(5));
        await _inventory.ReserveStock(command);
    }

    public async Task Handle(StockReserved message)
    {
        await _payment.ProcessPayment(Data.OrderId);
    }

    public async Task Handle(PaymentProcessed message)
    {
        await _shipping.CreateShipment(Data.OrderId);
        MarkAsComplete();
    }
}

6. CQRS (Command Query Responsibility Segregation)

Separate read and write models.

         Commands                        Queries
             β”‚                              β”‚
             β–Ό                              β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Write Model    │───events──▢│  Read Model    β”‚
    β”‚ (Normalized)   β”‚            β”‚ (Denormalized) β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜            β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚                            β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”            β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚   Write DB     β”‚            β”‚   Read DB      β”‚
    β”‚ (PostgreSQL)   β”‚            β”‚ (ElasticSearch)β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

7. Event Sourcing

Store state as sequence of events.

public class Order : AggregateRoot
{
    public void CreateOrder(CreateOrderCommand cmd)
    {
        Apply(new OrderCreated { OrderId = cmd.OrderId });
    }

    public void AddItem(AddItemCommand cmd)
    {
        Apply(new ItemAdded { ProductId = cmd.ProductId, Quantity = cmd.Quantity });
    }

    private void On(OrderCreated e) => Id = e.OrderId;
    private void On(ItemAdded e) => Items.Add(new OrderItem(e.ProductId, e.Quantity));
}

Resilience Patterns

8. Retry Pattern

Automatic retry with backoff.

var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .WaitAndRetryAsync(
        retryCount: 3,
        sleepDurationProvider: attempt =>
            TimeSpan.FromSeconds(Math.Pow(2, attempt))
    );

9. Bulkhead Pattern

Isolate resources to prevent cascade failures.

var bulkhead = Policy.BulkheadAsync(
    maxParallelization: 10,
    maxQueuingActions: 20
);

// Each service has isolated thread pool
var orderBulkhead = CreateBulkhead("orders", 10);
var paymentBulkhead = CreateBulkhead("payments", 5);

10. Sidecar Pattern

Deploy helper components alongside services.

# Kubernetes Pod with Sidecar
spec:
  containers:
  - name: main-app
    image: my-service:latest
  - name: logging-sidecar
    image: fluentd:latest
  - name: proxy-sidecar
    image: envoy:latest

Deployment Patterns

11. Blue-Green Deployment

Zero-downtime releases.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Load Balancer  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
    β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”
    β”‚         β”‚
    β–Ό         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”
β”‚ Blue  β”‚ β”‚ Green β”‚
β”‚ (v1)  β”‚ β”‚ (v2)  β”‚ ← New version
β”‚ ACTIVEβ”‚ β”‚ IDLE  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”˜

12. Canary Deployment

Gradual traffic shift to new version.

// Traffic routing example
public IEndpoint RouteRequest(string userId)
{
    var hash = ComputeHash(userId);

    if (hash % 100 < 5) // 5% to canary
        return _canaryEndpoint;

    return _stableEndpoint;
}

Observability Patterns

13. Distributed Tracing

Track requests across services.

// Using OpenTelemetry
using var activity = ActivitySource.StartActivity("ProcessOrder");
activity?.SetTag("order.id", orderId);

await _httpClient.PostAsync(url, content); // Automatically propagates trace context

14. Log Aggregation

Centralized logging from all services.

{
  "timestamp": "2024-01-15T10:30:00Z",
  "service": "order-service",
  "traceId": "abc123",
  "spanId": "def456",
  "level": "INFO",
  "message": "Order created",
  "orderId": "ORD-001"
}

15. Health Check Pattern

Monitor service health.

public class DatabaseHealthCheck : IHealthCheck
{
    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken)
    {
        try
        {
            await _db.ExecuteAsync("SELECT 1");
            return HealthCheckResult.Healthy();
        }
        catch (Exception ex)
        {
            return HealthCheckResult.Unhealthy("Database unavailable", ex);
        }
    }
}

Pattern Selection Guide

Scenario Recommended Pattern
Cross-service transactions Saga
High read/write ratio CQRS
Audit requirements Event Sourcing
Service unavailability Circuit Breaker
Network instability Retry + Timeout
Resource isolation Bulkhead
Zero-downtime deploy Blue-Green
Risk mitigation Canary

Sources

  • Arhitectura/microservices design patterns.png

πŸ“š Related Articles