Microservice Course
Microservice architecture is built on the following principles:
- Scalability
- Availability
- Resiliency
- Flexibility
- Independence and autonomy
- Decentralized governance
- Failure isolation
- Auto-provisioning
- Continuous delivery through DevOps
Adhering to these principles introduces a set of well-known challenges. The good news is that these challenges are common across virtually every microservices implementation, and the industry has developed proven design patterns to address them. These patterns are typically grouped into five categories:

1. Decomposition Patterns
How do you break a monolith into the right microservices? Getting service boundaries wrong is the most expensive architectural mistake — it causes tight coupling between services that defeats the entire point of microservices.
Decompose by Business Capability: Organize services around what the business does (e.g., Order Management, Billing, Inventory, Customer). Boundaries align with organizational teams (Conway's Law), which reduces inter-team coordination for each deployment.
Decompose by Subdomain (Domain-Driven Design): Use DDD's bounded contexts to identify service boundaries. Each bounded context maps naturally to one or more microservices and has its own ubiquitous language. This is the most theoretically sound approach and often produces the cleanest boundaries for complex domains.
Strangler Fig Pattern: When decomposing an existing monolith, don't rewrite it all at once — incrementally replace pieces of it with microservices while the monolith keeps running. Traffic is gradually redirected to the new services. This reduces risk significantly and allows teams to validate each extracted service independently before removing the corresponding code from the monolith.
2. Communication Patterns
Synchronous Communication
The consumer sends a request and waits for a response. Both reads and writes happen in-band, against the same data source. Typically implemented with HTTP/REST or gRPC.
Benefits:
- Simple to implement and reason about — the caller knows immediately whether the call succeeded or failed.
- Consistent reads — the caller sees the latest state.
- Easier debugging — the call chain is visible in a single trace.
Drawbacks:
- Temporal coupling — if the downstream service is slow or unavailable, the caller is directly affected.
- Cascading failures — a slow downstream can exhaust the caller's thread pool, causing it to fail too (addressed by the circuit breaker pattern).
- Lower availability — the overall availability of a synchronous call chain is the product of each service's availability (0.99 × 0.99 × 0.99 = 0.97 for three 99%-available services).
Asynchronous Communication
Services communicate via events or messages (Kafka, RabbitMQ, AWS SQS). The publisher emits an event and moves on; consumers process it in their own time.
Benefits:
- No temporal coupling — the publisher is unaffected if a consumer is temporarily down; messages queue up and are processed when the consumer recovers.
- Higher availability — services are independent at runtime.
- Natural fit for event-driven workflows and eventual consistency models.
- Each service can scale its consumers independently based on message backlog.
Drawbacks:
- Eventual consistency — different services may see different states of the same data at the same point in time.
- Harder to debug — you cannot follow a single synchronous call chain; distributed tracing (Sleuth/Micrometer) is essential.
- More complex error handling — dead-letter queues, idempotency, message ordering, and poison-pill messages all need explicit handling.
- Data duplication across service databases when each service stores its own projection of shared data.
In practice, most production microservice systems use both: synchronous (REST/gRPC) for real-time queries where an immediate response is needed, and asynchronous (events/messages) for cross-service state changes and long-running workflows.
3. Database Patterns
Database per Service: Each microservice owns its own database (schema, instance, or even engine type). No direct database sharing between services. This is the canonical microservices data pattern — it enforces the bounded context boundary at the data layer, allows each service to choose the most appropriate storage technology (relational, document, key-value, time-series), and allows independent schema evolution.
The challenge: queries that previously joined across tables now require service-to-service calls or event-driven data replication.
Saga Pattern: Manages distributed transactions across multiple services without a two-phase commit (2PC). A saga is a sequence of local transactions, each publishing an event that triggers the next step. If a step fails, compensating transactions are run in reverse to undo completed steps. Two flavors: choreography-based (each service reacts to events independently) and orchestration-based (a central saga orchestrator issues commands to each participant).
CQRS (Command Query Responsibility Segregation): Separates the write model (commands that change state) from the read model (queries that return state). This allows each model to be optimized independently — the write side can use a normalized relational store; the read side can use a denormalized, pre-projected store (Elasticsearch, Redis, a read replica) optimized for the exact queries the UI needs. See the dedicated CQRS article.
Event Sourcing: Instead of storing only the current state of an entity, store the full sequence of state-changing events. The current state is derived by replaying events. Pairs naturally with CQRS. See the dedicated CQRS and Event Sourcing article.
4. Cross-Cutting / Integration Patterns
API Gateway Pattern: A single entry point for all external clients. The gateway handles routing, authentication and authorization, SSL termination, rate limiting, request/response transformation, and aggregation of multiple service responses. In the Spring Cloud ecosystem, this is Spring Cloud Gateway. Without a gateway, clients would need to know the address of every microservice — impractical and insecure.
Backend for Frontend (BFF): A variation of the API Gateway pattern where a separate gateway is created for each type of client (mobile app, web app, third-party API). Each BFF can aggregate and tailor responses exactly for its client, rather than forcing all clients to work with a one-size-fits-all API. Reduces over-fetching and under-fetching.
Service Mesh: An infrastructure layer (Istio, Linkerd) that handles service-to-service communication concerns (mutual TLS, load balancing, circuit breaking, retries, distributed tracing, observability) transparently at the platform level, without requiring changes to application code. Increasingly common in Kubernetes-based deployments.
5. Observability Patterns
Health Check API: Every service exposes a /actuator/health endpoint. The service registry (Eureka), load balancer, and container orchestrator (Kubernetes) use it to determine whether instances are fit to receive traffic. Spring Boot Actuator provides this out of the box.
Log Aggregation: Each service writes structured logs to stdout. A log shipper (Fluentd, Filebeat) collects and forwards them to a centralized log aggregation platform (ELK Stack, Splunk, Datadog, CloudWatch). Essential for cross-service debugging since individual service logs in isolation are rarely enough.
Distributed Tracing: Assigns a trace ID to each request and propagates it across service boundaries so the full end-to-end path can be visualized. Spring Cloud Sleuth (Boot 2.x) / Micrometer Tracing (Boot 3.x) with Zipkin or Jaeger. Covered in detail in the Sleuth article.
Circuit Breaker: Covered in the Hystrix/Resilience4j article. Prevents cascading failures and provides fast-fail behaviour when downstream services are unhealthy.