📌Why Patterns Matter in Microservices
Splitting a system into services trades one set of problems (a big codebase, coupled deployments) for another: network failures, distributed data, partial outages and observability. Microservices design patterns are the proven answers to those problems — and the vocabulary that senior backend and architect interviews use.
Not sure microservices are right for you yet? Read microservices vs monolith first. To build a working system, follow the Spring Boot microservices tutorial.
📌Patterns at a Glance
📌1. API Gateway
A single entry point that routes requests to services and handles cross-cutting concerns: authentication, rate limiting, CORS, request logging and response aggregation. Clients no longer need to know about every service.
spring: cloud: gateway: routes: - id: orders uri: lb://order-service predicates: - Path=/api/orders/** filters: - name: CircuitBreaker args: name: orders fallbackUri: forward:/fallback/ordersA variant, Backend for Frontend (BFF), gives each client type (web, mobile) its own gateway tailored to its needs.
📌2. Service Discovery
Service instances come and go with scaling and deployments, so hard-coded addresses break. With client-side discovery, services register with a registry such as Eureka and clients look them up (the lb:// URI above). With server-side discovery, a load balancer or platform does it — on Kubernetes, a Service's DNS name is all you need, so many teams drop Eureka entirely.
📌3. Circuit Breaker
When a downstream service is slow or failing, callers that keep waiting exhaust their own threads and fail too — a cascading failure. A circuit breaker tracks failures and, past a threshold, opens to fail fast (or return a fallback), then half-opens to test recovery.
@Servicepublic class InventoryClient { private final RestClient restClient; public InventoryClient(RestClient.Builder builder) { this.restClient = builder.baseUrl("http://inventory-service").build(); } @CircuitBreaker(name = "inventory", fallbackMethod = "unknownStock") @Retry(name = "inventory") public StockResponse stock(String sku) { return restClient.get().uri("/api/stock/{sku}", sku) .retrieve().body(StockResponse.class); } private StockResponse unknownStock(String sku, Throwable ex) { return new StockResponse(sku, -1, "UNKNOWN"); }}resilience4j: circuitbreaker: instances: inventory: sliding-window-size: 20 failure-rate-threshold: 50 wait-duration-in-open-state: 10s retry: instances: inventory: max-attempts: 3 wait-duration: 200msPair it with timeouts on every remote call, retries with backoff only for idempotent operations, and bulkheads to isolate thread pools.
📌4. Database per Service
Each service owns its data and no other service touches its tables. This is what makes services independently deployable and scalable. The cost: no cross-service joins and no ACID transactions across services — which is why the next patterns exist.
📌5. Saga
A saga replaces a distributed transaction with a sequence of local transactions, each publishing an event or command. If a step fails, compensating transactions undo the earlier steps.
Example — placing an order: create order (PENDING) → reserve stock → charge payment → confirm order. If payment fails: release stock → cancel order.
Make every step idempotent, because events can be delivered more than once.
📌6. Transactional Outbox
A service must update its database and publish an event. Doing both separately risks one succeeding and the other failing. The outbox pattern writes the event to an outbox table in the same local transaction as the business change; a relay then publishes it to Kafka or RabbitMQ.
@Transactionalpublic Order placeOrder(OrderRequest req) { Order order = orderRepository.save(Order.pending(req)); outboxRepository.save(OutboxEvent.of("Order", order.getId(), "OrderCreated", toJson(order))); return order;}The relay can poll the table or use change data capture with Debezium. Consumers must still be idempotent.
📌7. CQRS
Command Query Responsibility Segregation separates the write model (commands that change state) from the read model (queries optimised for display). The read side is often a denormalised view, a search index such as Elasticsearch, or a cache — updated from events. Use it when reads and writes have very different shapes or scale; it adds complexity and eventual consistency.
📌8. Event Sourcing
Instead of storing current state, store the sequence of events that led to it (OrderPlaced, ItemAdded, OrderShipped) and rebuild state by replaying them. You get a complete audit log, time travel and natural event publishing — at the cost of more complex querying (usually solved with CQRS projections) and event schema evolution. Common in finance, ledgers and order management.
📌9. Strangler Fig
The safe way to migrate a monolith. Put a gateway in front, then carve out one capability at a time into a new service and route its traffic there, until the monolith is "strangled". No big-bang rewrite, and every step is reversible.
📌10. Sidecar and Service Mesh
A sidecar is a helper container deployed alongside each service instance that handles infrastructure concerns — mTLS, retries, traffic shifting, metrics. A service mesh (Istio, Linkerd) manages a fleet of sidecar proxies such as Envoy, moving networking logic out of application code. Valuable at scale; overkill for a handful of services.
📌11. Externalised Configuration
Keep configuration out of the build artifact so the same image runs in every environment: Spring Cloud Config, environment variables, or Kubernetes ConfigMaps and Secrets.
📌12. Observability: Tracing, Logs and Metrics
A single request may cross ten services. Propagate a trace ID with Micrometer Tracing and OpenTelemetry, ship structured logs with the trace ID to a central store, and expose metrics through Spring Boot Actuator and Prometheus. Without this, debugging microservices is guesswork.
📌Choosing the Right Patterns
Start simple: a gateway, discovery (or Kubernetes DNS), circuit breakers with timeouts, database per service and tracing. Add saga and outbox when you have cross-service workflows, and CQRS or event sourcing only when the domain really needs them. Over-engineering is the most common microservices failure.
These patterns come up constantly in microservices interview questions and system design rounds.
📌Learn It Live
Prakalpana's Microservices Architecture course builds a complete Spring Cloud system with these patterns, then runs it on Docker and Kubernetes — live online with an optional 1-on-1 track. WhatsApp or call +91 9243078181 for a free demo.