Microservices Architecture: Design Patterns and Implementation Strategies

February 14, 20244 min read
MicroservicesArchitectureDistributed SystemsDesign Patterns
# Microservices Architecture: Design Patterns and Implementation Strategies Microservices architecture has become the standard for building large-scale, distributed applications. This architectural style breaks down applications into small, independent services that communicate over well-defined APIs. ## Core Principles ### Service Independence Each microservice should be independently deployable and scalable: ```typescript // User Service @Module({ imports: [DatabaseModule], controllers: [UserController], providers: [UserService], }) export class UserModule {} // Order Service @Module({ imports: [DatabaseModule], controllers: [OrderController], providers: [OrderService], }) export class OrderModule {} ``` ### Database Per Service Each service owns its database: ```typescript // User Service Database interface User { id: string; email: string; name: string; } // Order Service Database interface Order { id: string; userId: string; // Reference, not foreign key items: OrderItem[]; } ``` ## Communication Patterns ### Synchronous Communication (REST) Use REST for request-response patterns: ```typescript @Injectable() export class OrderService { constructor( private readonly httpService: HttpService, @Inject('USER_SERVICE_URL') private readonly userServiceUrl: string, ) {} async createOrder(orderData: CreateOrderDto): Promise<Order> { // Verify user exists const user = await this.httpService .get(`${this.userServiceUrl}/users/${orderData.userId}`) .toPromise(); if (!user) { throw new NotFoundException('User not found'); } return this.orderRepository.create(orderData); } } ``` ### Asynchronous Communication (Message Queue) Use message queues for event-driven communication: ```typescript // Publisher @Injectable() export class OrderService { constructor( @Inject('MESSAGE_QUEUE') private readonly messageQueue: MessageQueue, ) {} async createOrder(orderData: CreateOrderDto): Promise<Order> { const order = await this.orderRepository.create(orderData); // Publish event await this.messageQueue.publish('order.created', { orderId: order.id, userId: order.userId, amount: order.total, }); return order; } } // Subscriber @Injectable() export class NotificationService { @MessagePattern('order.created') async handleOrderCreated(data: OrderCreatedEvent) { // Send notification to user await this.sendEmail(data.userId, 'Order confirmed'); } } ``` ## Service Discovery ### Service Registry Pattern Implement service discovery for dynamic service location: ```typescript @Injectable() export class ServiceRegistry { private services: Map<string, ServiceInfo> = new Map(); register(serviceName: string, url: string) { this.services.set(serviceName, { url, lastHeartbeat: Date.now() }); } discover(serviceName: string): string | null { const service = this.services.get(serviceName); if (!service || Date.now() - service.lastHeartbeat > 30000) { return null; // Service unavailable } return service.url; } } ``` ## API Gateway Pattern Implement an API Gateway for unified entry point: ```typescript @Controller() export class ApiGatewayController { constructor( private readonly userService: UserService, private readonly orderService: OrderService, private readonly productService: ProductService, ) {} @Get('users/:id/orders') async getUserOrders(@Param('id') userId: string) { const [user, orders] = await Promise.all([ this.userService.findById(userId), this.orderService.findByUserId(userId), ]); return { user, orders }; } } ``` ## Resilience Patterns ### Circuit Breaker Implement circuit breaker for fault tolerance: ```typescript class CircuitBreaker { private failures = 0; private state: 'closed' | 'open' | 'half-open' = 'closed'; private readonly threshold = 5; private readonly timeout = 60000; async execute<T>(fn: () => Promise<T>): Promise<T> { if (this.state === 'open') { if (Date.now() - this.lastFailure > this.timeout) { this.state = 'half-open'; } else { throw new Error('Circuit breaker is open'); } } try { const result = await fn(); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } } private onSuccess() { this.failures = 0; this.state = 'closed'; } private onFailure() { this.failures++; if (this.failures >= this.threshold) { this.state = 'open'; this.lastFailure = Date.now(); } } } ``` ### Retry Pattern Implement exponential backoff retry: ```typescript async function retryWithBackoff<T>( fn: () => Promise<T>, maxRetries = 3, ): Promise<T> { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { if (i === maxRetries - 1) throw error; await sleep(Math.pow(2, i) * 1000); // Exponential backoff } } throw new Error('Max retries exceeded'); } ``` ## Data Consistency ### Saga Pattern Implement saga for distributed transactions: ```typescript class OrderSaga { async execute(orderData: CreateOrderDto) { const steps = [ { name: 'reserveInventory', compensate: 'releaseInventory' }, { name: 'chargePayment', compensate: 'refundPayment' }, { name: 'createOrder', compensate: 'cancelOrder' }, ]; const executedSteps: string[] = []; try { for (const step of steps) { await this.executeStep(step.name, orderData); executedSteps.push(step.name); } } catch (error) { // Compensate in reverse order for (const step of executedSteps.reverse()) { await this.compensate(step, orderData); } throw error; } } } ``` ## Monitoring and Observability ### Distributed Tracing Implement distributed tracing: ```typescript import { trace, context } from '@opentelemetry/api'; @Injectable() export class OrderService { async createOrder(orderData: CreateOrderDto) { const tracer = trace.getTracer('order-service'); return tracer.startActiveSpan('createOrder', async (span) => { try { span.setAttribute('user.id', orderData.userId); const order = await this.orderRepository.create(orderData); span.setAttribute('order.id', order.id); return order; } finally { span.end(); } }); } } ``` ## Conclusion Microservices architecture provides scalability and flexibility but requires careful design. By implementing proper communication patterns, resilience strategies, and observability, you can build robust distributed systems that scale effectively.
Microservices Architecture: Design Patterns and Implementation Strategies - Blog - Websezma LLC