πŸ›οΈ

Microservices Database Patterns

System Architecture Intermediate 2 min read 300 words
System Design Microservices

Microservices Database Patterns

Strategies for managing data in distributed microservices architectures.

Database Per Service Pattern

Core Principle

Each microservice owns and manages its own database, providing complete data autonomy.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        API Gateway                           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚                     β”‚                     β”‚
    β–Ό                     β–Ό                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Order    β”‚       β”‚  Product  β”‚       β”‚  Customer β”‚
β”‚  Service  β”‚       β”‚  Service  β”‚       β”‚  Service  β”‚
β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
      β”‚                   β”‚                   β”‚
β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”
β”‚  Orders   β”‚       β”‚ Products  β”‚       β”‚ Customers β”‚
β”‚ Database  β”‚       β”‚ Database  β”‚       β”‚ Database  β”‚
β”‚  (SQL)    β”‚       β”‚ (MongoDB) β”‚       β”‚ (Postgres)β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Benefits

  1. Loose Coupling: Services are independent
  2. Technology Freedom: Choose best database for each use case
  3. Independent Scaling: Scale databases separately
  4. Failure Isolation: One DB failure doesn’t affect others
  5. Schema Evolution: Change schema without coordination

Challenges

  1. Data consistency across services
  2. Cross-service queries
  3. Increased operational complexity
  4. Data duplication

Shared Database Anti-Pattern

Why to Avoid

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Service A β”‚  β”‚ Service B β”‚  β”‚ Service C β”‚
β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
      β”‚              β”‚              β”‚
      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
              β”‚   Shared    β”‚  ← Anti-pattern!
              β”‚  Database   β”‚
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Problems:

  • Tight coupling
  • Schema changes affect all services
  • Single point of failure
  • Cannot scale independently
  • Technology lock-in

Data Consistency Patterns

Eventual Consistency

// Event-driven data synchronization
public class OrderCreatedHandler : IHandleMessages<OrderCreated>
{
    public async Task Handle(OrderCreated @event)
    {
        // Customer service updates its read model
        await _customerReadModel.AddOrder(new CustomerOrder
        {
            CustomerId = @event.CustomerId,
            OrderId = @event.OrderId,
            TotalAmount = @event.TotalAmount
        });
    }
}

Saga Pattern for Transactions

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Create   │───▢│ Reserve  │───▢│ Process  │───▢│ Ship     β”‚
β”‚ Order    β”‚    β”‚ Stock    β”‚    β”‚ Payment  β”‚    β”‚ Order    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
     β”‚               β”‚               β”‚               β”‚
     β–Ό               β–Ό               β–Ό               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Cancel   │◀───│ Release  │◀───│ Refund   │◀───│ Cancel   β”‚
β”‚ Order    β”‚    β”‚ Stock    β”‚    β”‚ Payment  β”‚    β”‚ Shipment β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    (Compensating transactions for rollback)

Implementation Example

public class CreateOrderSaga
{
    private readonly IOrderRepository _orders;
    private readonly IInventoryService _inventory;
    private readonly IPaymentService _payment;

    public async Task<Result> ExecuteAsync(CreateOrderCommand command)
    {
        // Step 1: Create Order
        var order = await _orders.CreateAsync(command);

        try
        {
            // Step 2: Reserve Inventory
            var reservation = await _inventory.ReserveAsync(order.Items);

            try
            {
                // Step 3: Process Payment
                var payment = await _payment.ProcessAsync(order.Total);

                // Success - complete order
                await _orders.ConfirmAsync(order.Id);
                return Result.Success(order);
            }
            catch
            {
                // Compensate: Release inventory
                await _inventory.ReleaseAsync(reservation.Id);
                throw;
            }
        }
        catch
        {
            // Compensate: Cancel order
            await _orders.CancelAsync(order.Id);
            throw;
        }
    }
}

Cross-Service Queries

API Composition

public class OrderDetailsQueryHandler
{
    public async Task<OrderDetailsDto> Handle(GetOrderDetails query)
    {
        // Fetch from multiple services in parallel
        var orderTask = _orderService.GetOrderAsync(query.OrderId);
        var customerTask = _customerService.GetCustomerAsync(query.CustomerId);
        var productTasks = query.ProductIds
            .Select(id => _productService.GetProductAsync(id));

        await Task.WhenAll(
            orderTask,
            customerTask,
            Task.WhenAll(productTasks)
        );

        // Compose the response
        return new OrderDetailsDto
        {
            Order = await orderTask,
            Customer = await customerTask,
            Products = productTasks.Select(t => t.Result).ToList()
        };
    }
}

CQRS with Read Models

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     Events      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Order Service│────────────────▢│  Order Details       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                 β”‚  Read Model          β”‚
                                 β”‚  (Denormalized view) β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     Events      β”‚                      β”‚
β”‚ Customer Svc │────────────────▢│ Order + Customer +   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                 β”‚ Product data         β”‚
                                 β”‚ in single table      β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     Events      β”‚                      β”‚
β”‚ Product Svc  │────────────────▢│                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Database Technology Selection

Polyglot Persistence

Service Database Reason
User Profiles PostgreSQL Complex relations, ACID
Product Catalog MongoDB Flexible schema, nested data
Shopping Cart Redis Fast access, TTL support
Order History Cassandra High write throughput, time-series
Search Elasticsearch Full-text search, analytics
Sessions Redis In-memory, fast expiration

Selection Criteria

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                 Database Selection                       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                         β”‚
β”‚  Structured data + ACID    ──▢  PostgreSQL/SQL Server   β”‚
β”‚                                                         β”‚
β”‚  Document/Flexible schema  ──▢  MongoDB/CouchDB         β”‚
β”‚                                                         β”‚
β”‚  Key-Value/Caching         ──▢  Redis/Memcached         β”‚
β”‚                                                         β”‚
β”‚  Time-series/Logs          ──▢  InfluxDB/TimescaleDB    β”‚
β”‚                                                         β”‚
β”‚  Graph relationships       ──▢  Neo4j/CosmosDB          β”‚
β”‚                                                         β”‚
β”‚  Full-text search          ──▢  Elasticsearch/Solr     β”‚
β”‚                                                         β”‚
β”‚  Wide column/Scale         ──▢  Cassandra/ScyllaDB      β”‚
β”‚                                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Data Migration Strategies

Strangler Fig Pattern

Phase 1: Legacy database still primary
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Monolith    │───▢ Legacy DB (read/write)
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Phase 2: Dual writes
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”€β”€β”€β–Ά Legacy DB (write)
β”‚ Monolith    │───▢ New DB (write)
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Phase 3: New database primary
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”€β”€β”€β–Ά New DB (read/write)
β”‚ Microserviceβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”€β”€β”€β–Ά Legacy DB (deprecated)

Change Data Capture (CDC)

// Using Debezium connector concept
public class OrderCdcProcessor
{
    public async Task ProcessChange(ChangeEvent change)
    {
        switch (change.Operation)
        {
            case "INSERT":
                await PublishEvent(new OrderCreated(change.After));
                break;
            case "UPDATE":
                await PublishEvent(new OrderUpdated(change.Before, change.After));
                break;
            case "DELETE":
                await PublishEvent(new OrderDeleted(change.Before));
                break;
        }
    }
}

Best Practices

1. Define Clear Data Ownership

  • Each service owns its domain data
  • No direct database access between services
  • Use APIs for data sharing

2. Design for Eventual Consistency

  • Accept that data won’t always be immediately consistent
  • Implement idempotent operations
  • Handle duplicate events

3. Implement Proper Boundaries

// Good: Service exposes API
public interface IOrderService
{
    Task<Order> GetOrderAsync(string orderId);
}

// Bad: Direct database access
public class ReportService
{
    // Don't do this!
    private readonly OrderDbContext _orderDb;
}

4. Plan for Failure

  • Implement compensating transactions
  • Use outbox pattern for reliable messaging
  • Handle partial failures gracefully

Sources

  • Arhitectura/microservices dbs.gif

πŸ“š Related Articles