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:

AspectMonolithMicroservices
DeploymentSingle unitIndependent services
DatabaseSharedDatabase per service
CouplingTightly coupledLoosely coupled
TechnologySingle stackMultiple stacks
ScalingScale entire appScale services independently
FailureEntire app failsIsolated 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:

  1. Service decomposition: By business capability
  2. Database per service: Each service owns its data
  3. Saga pattern: Distributed transactions
  4. Event-driven: Asynchronous communication
  5. API Gateway: Single entry point
  6. Service mesh: Cross-cutting concerns
  7. 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

About the author

InterviewCrafted helps you master system design with patience. We believe in curiosity-led engineering, reflective writing, and designing systems that make future changes feel calm.