Microservices¶
Microservices is an architecture where the application is split into small, independently deployable services, each owning its data and business logic. Key patterns: API Gateway (single entry point), Service Discovery (Eureka), Circuit Breaker (fault tolerance), Saga (distributed transactions), CQRS (separate read/write models). Trade-offs vs monolith: more operational complexity, but better scalability and team independence.
Monolith vs Microservices¶
A monolith deploys as one unit — simple but hard to scale independently. Microservices split into independent services — each deployable, scalable, and owned by a small team. Trade-offs: microservices add network latency, distributed data challenges, and operational complexity. Start with a monolith, extract microservices as needed.
Deep Dive: Comparison
| Aspect | Monolith | Microservices |
|---|---|---|
| Deployment | One unit | Independent services |
| Scaling | Scale entire app | Scale individual services |
| Technology | One stack | Polyglot |
| Team structure | One team | Small, autonomous teams |
| Complexity | Code complexity | Operational complexity |
| Data | Shared database | Database per service |
| Latency | In-process calls | Network calls |
| Testing | Easier (one process) | Harder (distributed) |
API Gateway & Service Discovery¶
API Gateway — single entry point for all clients. Handles routing, authentication, rate limiting, load balancing, and SSL termination. Tools: Spring Cloud Gateway, Kong, AWS API Gateway. Service Discovery — services register themselves; others discover via registry instead of hardcoded URLs. Tools: Eureka, Consul, Kubernetes Service. Load Balancer — distributes traffic across instances (client-side with @LoadBalanced or server-side with Nginx/ALB).
Deep Dive: Architecture
Client → API Gateway → Load Balancer → Service Instance 1
→ Service Instance 2
→ Service Instance 3
↕
Service Registry (Eureka)
API Gateway responsibilities:
- Request routing (path → service)
- Authentication / Authorization
- Rate limiting
- Load balancing (lb://service-name)
- Circuit breaking (fallback routes)
- Request/response transformation
Service Discovery flow: 1. Service starts → registers with Eureka 2. Other services query Eureka for available instances 3. Client-side load balancing picks an instance 4. Services send heartbeats; Eureka removes dead instances
Resilience Patterns¶
Circuit Breaker — prevents cascading failures (CLOSED → OPEN → HALF_OPEN). Retry — retry with exponential backoff. Rate Limiter — limit requests per time window. Bulkhead — isolate resources so one failing service doesn't exhaust all threads. Time Limiter — set timeout for slow calls. Tools: Resilience4j, Spring Cloud CircuitBreaker.
Deep Dive: Circuit Breaker States & Config
CLOSED → (failures reach threshold) → OPEN
↑ │
│ (wait duration) ↓
└──── CLOSED ←── (success) ←── HALF_OPEN
@CircuitBreaker(name = "userService", fallbackMethod = "fallback")
public User getUser(Long userId) {
return restTemplate.getForObject("http://user-service/api/users/" + userId, User.class);
}
private User fallback(Long userId, Throwable ex) {
return cachedUserService.getCachedUser(userId); // Graceful degradation
}
| Pattern | Purpose | Tool |
|---|---|---|
| Circuit Breaker | Stop calls to failing service | Resilience4j |
| Retry | Retry with backoff | Resilience4j |
| Rate Limiter | Limit request rate | Resilience4j / Bucket4j |
| Bulkhead | Isolate thread pools | Resilience4j |
| Time Limiter | Timeout slow calls | Resilience4j |
Saga Pattern (Distributed Transactions)¶
In microservices, there's no single database transaction spanning services. The Saga pattern breaks a transaction into a sequence of local transactions — each publishes an event or calls the next step. If any step fails, compensating transactions undo previous steps. Two approaches: Choreography (event-driven, decentralized) and Orchestration (central coordinator).
Deep Dive: Choreography vs Orchestration
Order Service → Payment Service → Inventory Service
│ │
└── If any fails → compensating transactions (rollback)
Choreography — each service listens for events and reacts:
OrderService publishes OrderCreated
→ PaymentService listens, charges, publishes PaymentCompleted
→ InventoryService listens, deducts stock
❌ Hard to track flow, debug
Orchestration — central Saga orchestrator controls flow:
SagaOrchestrator:
1. Call OrderService.create()
2. Call PaymentService.charge()
3. Call InventoryService.deduct()
On failure at step 3:
→ PaymentService.refund()
→ OrderService.cancel()
❌ Central point of failure
CQRS (Command Query Responsibility Segregation)¶
CQRS separates the write model (commands — normalized, consistent) from the read model (queries — denormalized, optimized for reads). Useful when read and write patterns are very different (e.g., complex writes but high-volume simple reads). Often paired with Event Sourcing where state is stored as a sequence of events.
Deep Dive: Architecture
Write Side: Read Side:
┌──────────┐ ┌──────────────┐
│ Command │──event──→│ Event Handler│
│ Service │ │ (projector) │
│ │ └──────┬───────┘
│ (normalized DB) │
└──────────┘ ┌─────↓───────┐
│ Query Model │
│ (denormalized│
│ view DB) │
└──────────────┘
When to use CQRS: - Read and write loads are very different - Complex domain logic on writes - Need different data models for reads vs writes - High read throughput (denormalized views)
When NOT to use: Simple CRUD applications — adds unnecessary complexity.
Inter-Service Communication¶
Synchronous — REST (simple, ubiquitous) or gRPC (fast, typed, streaming). Request-reply, caller blocks. Asynchronous — message queues (Kafka, RabbitMQ, SQS) for event-driven communication. Caller doesn't block, services are decoupled. Use sync for real-time queries, async for background processing, event-driven flows, and fault tolerance.
Deep Dive: Sync vs Async Comparison
| Synchronous | Asynchronous | |
|---|---|---|
| Coupling | Tighter | Looser |
| Latency | Request blocks | Non-blocking |
| Failure handling | Cascading | Isolated |
| Complexity | Simpler | More complex |
| Use case | Real-time queries | Background processing |
Messaging tools: - Kafka — high-throughput, log-based, replay, ordering - RabbitMQ — flexible routing, dead-letter queues - AWS SQS/SNS — managed, SQS for queues, SNS for pub-sub
Database Per Service¶
Each microservice owns its database — no shared databases, no cross-service JOINs. This enforces loose coupling but creates challenges: no distributed JOINs (use API composition), distributed transactions (use Saga), and data duplication (accept eventual consistency).
Deep Dive: Example
User Service → PostgreSQL (users)
Order Service → MongoDB (orders)
Payment Service → PostgreSQL (payments)
Search Service → Elasticsearch (search index)
Challenges & solutions: - No cross-service JOINs → API composition or event-driven sync - Distributed transactions → Saga pattern - Data duplication → eventual consistency (acceptable in most cases)
Observability (Logging, Metrics, Tracing)¶
Three pillars: Logging (what happened — structured JSON logs, ELK/Kibana, Loki), Metrics (how it's performing — Prometheus + Grafana, Datadog), Distributed Tracing (request flow across services — Zipkin, Jaeger, Micrometer). Every request gets a trace ID propagated across all services.
Deep Dive: Tools & Examples
1. Logging — structured, centralized:
Tools: ELK Stack (Elasticsearch, Logstash, Kibana), Loki2. Metrics — request rates, latency, error rates:
Tools: Prometheus + Grafana, Datadog3. Distributed Tracing — follow a request across services:
Tools: Jaeger, Zipkin, Micrometer TracingCommon Interview Questions¶
Common Interview Questions
- What are microservices? How are they different from a monolith?
- When should you use microservices vs a monolith?
- What is the Saga pattern? Choreography vs orchestration?
- What is CQRS? When would you use it?
- What is a circuit breaker? Explain its states.
- What resilience patterns do you know? (retry, bulkhead, rate limiter)
- How do microservices communicate? Sync vs async?
- What is the database-per-service pattern?
- How do you handle distributed transactions?
- What is observability? What are the three pillars?
- What is event sourcing?
- How do you handle cascading failures?