Topic Overview
Microservices Architecture
Master microservices architecture: service decomposition, communication patterns, data management, deployment strategies, and challenges like distributed transactions.
Microservices architecture is an approach to building applications as a collection of small, independent services that communicate over well-defined APIs. Each service is independently deployable and scalable.
What are Microservices?
Microservices are:
- Small, independent services: Each service has a single responsibility
- Loosely coupled: Services communicate via APIs
- Independently deployable: Deploy services separately
- Technology agnostic: Each service can use different technologies
- Own data: Each service manages its own database
Benefits:
- Scalability: Scale services independently
- Technology diversity: Use best technology for each service
- Fault isolation: Failure in one service doesn't bring down entire system
- Team autonomy: Teams can work independently
- Faster deployment: Deploy services independently
Challenges:
- Distributed system complexity: Network calls, failures, latency
- Data consistency: Distributed transactions are complex
- Service discovery: Finding and communicating with services
- Testing: More complex than monolithic testing
- Deployment: More services to deploy and manage
Monolith vs Microservices
Monolithic Architecture
┌─────────────────────────────────┐
│ Monolithic Application │
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ User │ │ Order │ │
│ │ Service │ │ Service │ │
│ └─────────┘ └─────────┘ │
│ │
│ ┌─────────────────────────┐ │
│ │ Shared Database │ │
│ └─────────────────────────┘ │
└─────────────────────────────────┘
Characteristics:
- Single deployable unit
- Shared database
- Tightly coupled
- Single technology stack
Microservices Architecture
┌──────────┐ ┌──────────┐ ┌──────────┐
│ User │ │ Order │ │ Payment │
│ Service │ │ Service │ │ Service │
│ │ │ │ │ │
│ ┌──────┐ │ │ ┌──────┐ │ │ ┌──────┐ │
│ │ DB │ │ │ │ DB │ │ │ │ DB │ │
│ └──────┘ │ │ └──────┘ │ │ └──────┘ │
└──────────┘ └──────────┘ └──────────┘
│ │ │
└───────────────┴───────────────┘
API Gateway
Characteristics:
- Multiple independent services
- Each service has its own database
- Loosely coupled
- Technology diversity
Service Decomposition
Decomposition Strategies
1. By Business Capability
Services:
- User Service (user management)
- Order Service (order processing)
- Payment Service (payment processing)
- Inventory Service (inventory management)
2. By Domain (DDD)
Bounded Contexts → Services:
- User Domain → User Service
- Order Domain → Order Service
- Payment Domain → Payment Service
3. By Data
Services own their data:
- User Service → User Database
- Order Service → Order Database
- Payment Service → Payment Database
Service Size
Guidelines:
- Small enough: Can be rewritten in 2-3 weeks
- Large enough: Has meaningful business value
- Team size: Owned by small team (2-pizza team)
Communication Patterns
1. Synchronous Communication
HTTP/REST, gRPC
Service A → HTTP Request → Service B
Service A ← HTTP Response ← Service B
Characteristics:
- Request-response pattern
- Immediate response
- Tight coupling (service must be available)
Use when:
- Need immediate response
- Simple request-response
- Real-time operations
2. Asynchronous Communication
Message Queues, Event Streaming
Service A → Message Queue → Service B (eventually)
Service A → Event Stream → Service B (eventually)
Characteristics:
- Fire-and-forget
- Eventual consistency
- Loose coupling
Use when:
- Don't need immediate response
- High throughput
- Decoupled services
Data Management
Database per Service
Principle: Each service has its own database.
User Service → User Database
Order Service → Order Database
Payment Service → Payment Database
Benefits:
- Independence: Services don't interfere
- Technology choice: Different databases per service
- Scalability: Scale databases independently
Challenges:
- Distributed transactions: Complex
- Data consistency: Eventual consistency
- Cross-service queries: Difficult
Saga Pattern
Solution for distributed transactions:
Order Service:
1. Create order
2. Reserve inventory (Inventory Service)
3. Process payment (Payment Service)
If payment fails:
- Compensate: Release inventory
- Compensate: Cancel order
Examples
Microservices with API Gateway
// API Gateway
class APIGateway {
private services: Map<string, Service>;
async route(request: Request): Promise<Response> {
const service = this.getService(request.path);
// Authentication
const user = await this.authenticate(request);
// Rate limiting
if (!await this.rateLimit(user)) {
return { status: 429 };
}
// Route to service
return await service.handle(request);
}
}
// User Service
class UserService {
async getUser(userId: string): Promise<User> {
return await this.database.getUser(userId);
}
async createUser(userData: UserData): Promise<User> {
return await this.database.createUser(userData);
}
}
// Order Service
class OrderService {
async createOrder(orderData: OrderData): Promise<Order> {
// Saga: Distributed transaction
const order = await this.database.createOrder(orderData);
// Reserve inventory
await this.inventoryService.reserve(order.items);
// Process payment
await this.paymentService.charge(order.total);
return order;
}
}
Service Discovery
class ServiceDiscovery {
private registry: Map<string, ServiceInstance[]>;
async register(serviceName: string, instance: ServiceInstance): Promise<void> {
if (!this.registry.has(serviceName)) {
this.registry.set(serviceName, []);
}
this.registry.get(serviceName).push(instance);
// Health check
this.startHealthCheck(instance);
}
async discover(serviceName: string): Promise<ServiceInstance> {
const instances = this.registry.get(serviceName) || [];
const healthy = instances.filter(i => i.healthy);
// Load balance
return this.loadBalancer.select(healthy);
}
}
Event-Driven Communication
class EventBus {
async publish(event: Event): Promise<void> {
await this.messageQueue.publish(event.topic, event);
}
async subscribe(topic: string, handler: Function): Promise<void> {
await this.messageQueue.subscribe(topic, handler);
}
}
// Order Service publishes event
class OrderService {
async createOrder(orderData: OrderData): Promise<Order> {
const order = await this.database.createOrder(orderData);
// Publish event
await this.eventBus.publish({
topic: 'order.created',
data: order
});
return order;
}
}
// Inventory Service subscribes
class InventoryService {
constructor() {
this.eventBus.subscribe('order.created', async (event) => {
await this.reserveInventory(event.data.items);
});
}
}
Common Pitfalls
- Over-microservicing: Too many small services. Fix: Start with fewer, larger services, split when needed
- Distributed transactions: Using 2PC for everything. Fix: Use saga pattern, eventual consistency
- Shared databases: Services sharing database. Fix: Database per service
- Synchronous calls everywhere: Creating call chains. Fix: Use async messaging, event-driven
- No service discovery: Hardcoding service locations. Fix: Use service registry, DNS
- No circuit breakers: Cascading failures. Fix: Implement circuit breakers, timeouts
- Inconsistent data: No strategy for consistency. Fix: Use saga pattern, eventual consistency
Interview Questions
Beginner
Q: What are microservices and how do they differ from monolithic architecture?
A:
Microservices are small, independent services that work together to form an application.
Differences:
| Aspect | Monolith | Microservices |
|---|---|---|
| Deployment | Single unit | Independent services |
| Database | Shared | Database per service |
| Coupling | Tightly coupled | Loosely coupled |
| Technology | Single stack | Multiple stacks |
| Scaling | Scale entire app | Scale services independently |
| Failure | Entire app fails | Isolated failures |
Benefits of microservices:
- Scalability: Scale services independently
- Technology diversity: Use best tool for each service
- Fault isolation: Failure in one service doesn't bring down system
- Team autonomy: Teams work independently
- Faster deployment: Deploy services separately
Challenges:
- Complexity: Distributed system complexity
- Data consistency: Distributed transactions
- Service discovery: Finding services
- Testing: More complex testing
Intermediate
Q: How do microservices communicate? Explain synchronous vs asynchronous communication.
A:
Synchronous Communication:
HTTP/REST, gRPC
Service A → Request → Service B
Service A ← Response ← Service B
Characteristics:
- Request-response pattern
- Immediate response
- Service must be available
- Tight coupling
Use when:
- Need immediate response
- Simple operations
- Real-time requirements
Example:
// User Service calls Order Service
const orders = await orderService.getUserOrders(userId);
Asynchronous Communication:
Message Queues, Event Streaming
Service A → Message Queue → Service B (eventually)
Service A → Event Stream → Service B (eventually)
Characteristics:
- Fire-and-forget
- Eventual consistency
- Loose coupling
- Service can be unavailable
Use when:
- Don't need immediate response
- High throughput
- Decoupled services
Example:
// Order Service publishes event
await eventBus.publish('order.created', order);
// Inventory Service subscribes
eventBus.subscribe('order.created', async (order) => {
await reserveInventory(order.items);
});
When to use:
- Synchronous: Need immediate response, simple operations
- Asynchronous: High throughput, decoupled, eventual consistency OK
Senior
Q: Design a microservices architecture for an e-commerce platform. How do you handle service decomposition, data management, distributed transactions, and deployment?
A:
class ECommerceMicroservices {
private services: Map<string, Service>;
private apiGateway: APIGateway;
private serviceMesh: ServiceMesh;
private eventBus: EventBus;
constructor() {
// Service decomposition
this.services = new Map([
['user', new UserService()],
['product', new ProductService()],
['order', new OrderService()],
['payment', new PaymentService()],
['inventory', new InventoryService()],
['shipping', new ShippingService()],
['notification', new NotificationService()]
]);
this.apiGateway = new APIGateway();
this.serviceMesh = new ServiceMesh();
this.eventBus = new EventBus();
}
// 1. Service Decomposition
class UserService {
private database: UserDatabase;
async getUser(userId: string): Promise<User> {
return await this.database.getUser(userId);
}
async createUser(userData: UserData): Promise<User> {
const user = await this.database.createUser(userData);
// Publish event
await this.eventBus.publish('user.created', user);
return user;
}
}
class OrderService {
private database: OrderDatabase;
async createOrder(orderData: OrderData): Promise<Order> {
// Saga pattern for distributed transaction
return await this.saga.createOrder(orderData);
}
// Saga: Distributed transaction
async createOrderSaga(orderData: OrderData): Promise<Order> {
const saga = new Saga();
// Step 1: Create order
saga.addStep(
async () => await this.database.createOrder(orderData),
async (order) => await this.database.deleteOrder(order.id)
);
// Step 2: Reserve inventory
saga.addStep(
async (order) => await this.inventoryService.reserve(order.items),
async (order) => await this.inventoryService.release(order.items)
);
// Step 3: Process payment
saga.addStep(
async (order) => await this.paymentService.charge(order.total),
async (order) => await this.paymentService.refund(order.total)
);
return await saga.execute();
}
}
// 2. Data Management (Database per Service)
class DataManagement {
// Each service has its own database
userService: UserDatabase;
orderService: OrderDatabase;
productService: ProductDatabase;
paymentService: PaymentDatabase;
// Eventual consistency via events
async syncData(): Promise<void> {
// Services publish events, others subscribe
// Data synced eventually
}
}
// 3. Service Communication
class ServiceCommunication {
// Synchronous: gRPC for real-time
async getUserOrders(userId: string): Promise<Order[]> {
return await this.orderService.getUserOrders(userId);
}
// Asynchronous: Events for decoupled
async handleOrderCreated(order: Order): Promise<void> {
// Inventory service subscribes
await this.inventoryService.reserve(order.items);
// Notification service subscribes
await this.notificationService.sendEmail(order.userId);
}
}
// 4. API Gateway
class APIGateway {
async route(request: Request): Promise<Response> {
// Authentication
const user = await this.authenticate(request);
// Rate limiting
if (!await this.rateLimit(user)) {
return { status: 429 };
}
// Route to service
const service = this.getService(request.path);
return await service.handle(request);
}
}
// 5. Service Mesh
class ServiceMesh {
// Handles cross-cutting concerns
- Load balancing
- Service discovery
- Circuit breaking
- Retries
- Observability
}
// 6. Deployment
class Deployment {
// Container-based deployment
async deployService(service: Service): Promise<void> {
// Build container
const image = await this.buildImage(service);
// Deploy to Kubernetes
await this.k8s.deploy(service.name, image);
// Health check
await this.healthCheck(service);
}
// Blue-green deployment
async blueGreenDeploy(service: Service): Promise<void> {
// Deploy new version (green)
await this.deploy(service, 'green');
// Route traffic gradually
await this.switchTraffic('blue', 'green');
// Monitor
if (this.isHealthy('green')) {
await this.decommission('blue');
} else {
await this.rollback('green', 'blue');
}
}
}
// 7. Observability
class Observability {
// Distributed tracing
async trace(request: Request): Promise<Trace> {
const traceId = this.generateTraceId();
// Track request across services
return trace;
}
// Metrics
async recordMetric(service: string, metric: Metric): Promise<void> {
// Record to metrics system
}
// Logging
async log(service: string, log: Log): Promise<void> {
// Centralized logging
}
}
}
Architecture:
Clients → API Gateway → Services
├─ User Service
├─ Product Service
├─ Order Service
├─ Payment Service
└─ ...
Services communicate via:
- Synchronous: gRPC
- Asynchronous: Event Bus
Features:
- Service decomposition: By business capability
- Database per service: Each service owns its data
- Saga pattern: Distributed transactions
- Event-driven: Asynchronous communication
- API Gateway: Single entry point
- Service mesh: Cross-cutting concerns
- Observability: Tracing, metrics, logging
Key Takeaways
- Microservices: Small, independent services that work together
- Decomposition: By business capability, domain, or data
- Communication: Synchronous (HTTP/gRPC) or asynchronous (events)
- Data management: Database per service, eventual consistency
- Distributed transactions: Use saga pattern, avoid 2PC
- Service discovery: Registry for finding services
- API Gateway: Single entry point, handles cross-cutting concerns
- Service mesh: Handles load balancing, circuit breaking, observability
- Deployment: Container-based, independent deployment
- Best practices: Start with fewer services, use events for decoupling, implement observability