Skip to content

Microservices Patterns

What are Microservices?

Architectural style where application is composed of small, independent services that communicate over well-defined APIs.

Monolith vs Microservices


Service Decomposition

Decomposition Strategies

Decomposition Strategies


Communication Patterns

Synchronous Communication

Synchronous Communication

Asynchronous Communication

Asynchronous Communication


API Gateway Pattern

API Gateway Pattern

Backend for Frontend (BFF)

Backend for Frontend


Service Discovery

Client-Side Discovery

Client-Side Discovery

Server-Side Discovery

Server-Side Discovery


Circuit Breaker Pattern

Prevents cascading failures by failing fast when a service is unavailable.

Circuit Breaker States

// Resilience4j example
@CircuitBreaker(name = "paymentService", fallbackMethod = "paymentFallback")
public PaymentResponse processPayment(PaymentRequest request) {
    return paymentClient.charge(request);
}

public PaymentResponse paymentFallback(PaymentRequest request, Exception e) {
    // Queue for retry, return cached response, or degrade gracefully
    return PaymentResponse.pending("Payment queued for processing");
}

// Configuration
resilience4j.circuitbreaker:
  instances:
    paymentService:
      failureRateThreshold: 50          # Open at 50% failure rate
      waitDurationInOpenState: 60000    # 60s before half-open
      permittedNumberOfCallsInHalfOpenState: 3
      slidingWindowSize: 10

Bulkhead Pattern

Isolates failures to prevent resource exhaustion.

Bulkhead Pattern

// Thread pool bulkhead
@Bulkhead(name = "paymentService", type = Bulkhead.Type.THREADPOOL)
public PaymentResponse processPayment(PaymentRequest request) {
    return paymentClient.charge(request);
}

// Semaphore bulkhead (limits concurrent calls)
@Bulkhead(name = "inventoryService", type = Bulkhead.Type.SEMAPHORE)
public InventoryResponse checkInventory(String productId) {
    return inventoryClient.check(productId);
}

// Configuration
resilience4j.bulkhead:
  instances:
    paymentService:
      maxConcurrentCalls: 10
      maxWaitDuration: 500ms

Saga Pattern

Manages distributed transactions across services.

Choreography (Event-Based)

Saga Choreography

Orchestration (Central Coordinator)

Saga Orchestration

// Orchestration example
public class OrderSagaOrchestrator {

    public void createOrder(CreateOrderCommand command) {
        try {
            // Step 1: Create order
            Order order = orderService.create(command);

            // Step 2: Reserve inventory
            inventoryService.reserve(order.getItems());

            // Step 3: Process payment
            paymentService.charge(order.getPayment());

            // Step 4: Confirm order
            orderService.confirm(order.getId());

        } catch (InventoryException e) {
            orderService.cancel(order.getId());
            throw e;
        } catch (PaymentException e) {
            inventoryService.release(order.getItems());
            orderService.cancel(order.getId());
            throw e;
        }
    }
}

Sidecar Pattern

Deploy helper components alongside main service.

Sidecar Pattern


Service Mesh

Infrastructure layer for service-to-service communication.

Service Mesh Architecture


Strangler Fig Pattern

Incrementally migrate from monolith to microservices.

Strangler Fig Pattern


Data Patterns

Database per Service

Database per Service

API Composition

// Aggregate data from multiple services
@RestController
public class OrderDetailsController {

    @GetMapping("/order-details/{orderId}")
    public OrderDetails getOrderDetails(@PathVariable String orderId) {
        // Parallel calls to services
        CompletableFuture<Order> orderFuture =
            CompletableFuture.supplyAsync(() -> orderService.getOrder(orderId));
        CompletableFuture<Customer> customerFuture =
            orderFuture.thenCompose(order ->
                CompletableFuture.supplyAsync(() ->
                    customerService.getCustomer(order.getCustomerId())));
        CompletableFuture<List<Product>> productsFuture =
            orderFuture.thenCompose(order ->
                CompletableFuture.supplyAsync(() ->
                    productService.getProducts(order.getProductIds())));

        // Combine results
        return CompletableFuture.allOf(orderFuture, customerFuture, productsFuture)
            .thenApply(v -> new OrderDetails(
                orderFuture.join(),
                customerFuture.join(),
                productsFuture.join()
            )).join();
    }
}

Common Interview Questions

  1. How do you handle distributed transactions?
  2. Saga pattern (choreography or orchestration)
  3. Avoid 2PC, embrace eventual consistency

  4. How do you prevent cascading failures?

  5. Circuit breaker, bulkhead, timeouts, retries with backoff

  6. Sync vs async communication?

  7. Sync: Simple, immediate response, tight coupling
  8. Async: Loose coupling, resilient, complex

  9. How to decompose a monolith?

  10. Strangler fig pattern
  11. Start with bounded contexts
  12. Extract by business capability

  13. Service discovery approaches?

  14. Client-side: Eureka, Consul
  15. Server-side: Kubernetes, load balancers