Comprehensive guide to designing, implementing, and managing microservices architectures with real-world patterns and examples

Microservices Architecture Patterns: Building Scalable Distributed Systemsh1
Hello! I’m Ahmet Zeybek, a full stack developer with extensive experience in designing and implementing microservices architectures for large-scale applications. Microservices have transformed how we build software, enabling teams to develop, deploy, and scale services independently. In this comprehensive guide, I’ll share the patterns and practices that have helped me build resilient, scalable distributed systems.
Why Microservices?h2
Microservices architecture addresses the limitations of monolithic applications:
- Independent Deployment: Deploy services without affecting others
- Technology Diversity: Use different tech stacks for different services
- Scalability: Scale individual services based on demand
- Team Autonomy: Teams can work independently
- Fault Isolation: Failures are contained to specific services
- Faster Development: Smaller codebases are easier to understand and maintain
Service Design Patternsh2
1. Domain-Driven Design (DDD) for Service Boundariesh3
Design services around business domains:
// User domaininterface UserService { createUser(userData: CreateUserRequest): Promise<User> getUser(userId: string): Promise<User> updateUser(userId: string, updates: UpdateUserRequest): Promise<User> deleteUser(userId: string): Promise<void> authenticate(credentials: LoginRequest): Promise<AuthToken>}
// Order domaininterface OrderService { createOrder(orderData: CreateOrderRequest): Promise<Order> getOrder(orderId: string): Promise<Order> updateOrderStatus(orderId: string, status: OrderStatus): Promise<Order> cancelOrder(orderId: string): Promise<void> getUserOrders(userId: string): Promise<Order[]>}
// Payment domaininterface PaymentService { processPayment(paymentData: PaymentRequest): Promise<PaymentResult> refundPayment(paymentId: string): Promise<RefundResult> getPaymentStatus(paymentId: string): Promise<PaymentStatus>}
// Inventory domaininterface InventoryService { checkStock(productId: string): Promise<StockInfo> reserveStock(productId: string, quantity: number): Promise<Reservation> releaseStock(reservationId: string): Promise<void> updateStock(productId: string, quantity: number): Promise<void>}
2. API Gateway Patternh3
Central entry point for client requests:
// API Gateway implementationclass APIGateway { private serviceRegistry: Map<string, ServiceInfo> = new Map()
constructor(private circuitBreaker: CircuitBreaker) {}
async routeRequest(request: APIRequest): Promise<APIResponse> { // 1. Authentication & Authorization const user = await this.authenticate(request)
// 2. Rate Limiting await this.checkRateLimit(user.id)
// 3. Request Routing const targetService = this.determineTargetService(request.path)
// 4. Circuit Breaker Check if (this.circuitBreaker.isOpen(targetService.name)) { throw new ServiceUnavailableError(`${targetService.name} is currently unavailable`) }
// 5. Load Balancing const serviceInstance = await this.loadBalancer.selectInstance(targetService)
// 6. Request Transformation const transformedRequest = this.transformRequest(request, targetService)
// 7. Service Call const response = await this.callService(serviceInstance, transformedRequest)
// 8. Response Transformation return this.transformResponse(response) }
private determineTargetService(path: string): ServiceInfo { const routeMappings: Record<string, string> = { '/api/users': 'user-service', '/api/orders': 'order-service', '/api/payments': 'payment-service', '/api/inventory': 'inventory-service', }
for (const [route, service] of Object.entries(routeMappings)) { if (path.startsWith(route)) { return this.serviceRegistry.get(service)! } }
throw new NotFoundError('Route not found') }}
// Load balancing strategiesclass LoadBalancer { async selectInstance(service: ServiceInfo): Promise<ServiceInstance> { switch (service.loadBalancingStrategy) { case 'ROUND_ROBIN': return this.roundRobin(service.instances)
case 'LEAST_CONNECTIONS': return this.leastConnections(service.instances)
case 'WEIGHTED_RESPONSE_TIME': return this.weightedResponseTime(service.instances)
default: return service.instances[0] } }}
3. Service Mesh Patternh3
Handle service-to-service communication:
# Istio VirtualService configurationapiVersion: networking.istio.io/v1beta1kind: VirtualServicemetadata: name: user-servicespec: hosts: - user-service http: - match: - headers: x-api-version: exact: 'v2' route: - destination: host: user-service subset: v2 - route: - destination: host: user-service subset: v1---# Istio DestinationRuleapiVersion: networking.istio.io/v1beta1kind: DestinationRulemetadata: name: user-servicespec: host: user-service subsets: - name: v1 labels: version: v1 - name: v2 labels: version: v2 trafficPolicy: tls: mode: ISTIO_MUTUAL loadBalancer: simple: ROUND_ROBIN
Communication Patternsh2
4. Synchronous Communication (REST/GraphQL)h3
Direct service-to-service calls:
// Order service calling User serviceclass OrderService { constructor( private userServiceClient: UserServiceClient, private paymentServiceClient: PaymentServiceClient, private inventoryServiceClient: InventoryServiceClient ) {}
async createOrder(orderRequest: CreateOrderRequest): Promise<Order> { // 1. Validate user exists and is active const user = await this.userServiceClient.getUser(orderRequest.userId) if (!user) { throw new ValidationError('User not found') }
if (!user.isActive) { throw new ValidationError('User account is inactive') }
// 2. Check inventory availability for (const item of orderRequest.items) { const stockInfo = await this.inventoryServiceClient.checkStock(item.productId)
if (stockInfo.available < item.quantity) { throw new ValidationError(`Insufficient stock for ${item.productId}`) } }
// 3. Reserve inventory const reservations = [] for (const item of orderRequest.items) { const reservation = await this.inventoryServiceClient.reserveStock(item.productId, item.quantity) reservations.push(reservation) }
try { // 4. Process payment const paymentResult = await this.paymentServiceClient.processPayment({ orderId: orderRequest.orderId, amount: orderRequest.totalAmount, userId: orderRequest.userId, paymentMethod: orderRequest.paymentMethod, })
if (!paymentResult.success) { throw new PaymentError('Payment processing failed') }
// 5. Create order const order = await this.orderRepository.create({ ...orderRequest, status: OrderStatus.PAID, paymentId: paymentResult.paymentId, })
return order } catch (error) { // Release reserved inventory on failure for (const reservation of reservations) { await this.inventoryServiceClient.releaseStock(reservation.id) } throw error } }}
5. Asynchronous Communication (Event-Driven)h3
Decoupled communication using events:
// Event definitionsinterface OrderCreatedEvent { type: 'ORDER_CREATED' data: { orderId: string userId: string totalAmount: number items: OrderItem[] } metadata: { timestamp: Date correlationId: string userId: string }}
interface PaymentProcessedEvent { type: 'PAYMENT_PROCESSED' data: { orderId: string paymentId: string amount: number status: PaymentStatus } metadata: { timestamp: Date correlationId: string }}
// Event publisherclass EventPublisher { constructor(private eventBus: EventBus) {}
async publish(event: DomainEvent): Promise<void> { await this.eventBus.publish({ topic: this.getTopicForEvent(event.type), event: { id: uuidv4(), type: event.type, data: event.data, metadata: { ...event.metadata, publishedAt: new Date(), source: 'order-service', }, }, }) }
private getTopicForEvent(eventType: string): string { const topicMapping: Record<string, string> = { ORDER_CREATED: 'orders', PAYMENT_PROCESSED: 'payments', INVENTORY_UPDATED: 'inventory', } return topicMapping[eventType] || 'default' }}
// Event handlersclass InventoryEventHandler { constructor( private inventoryService: InventoryService, private eventBus: EventBus ) {}
async handleOrderCreated(event: OrderCreatedEvent): Promise<void> { // Reserve inventory for new order for (const item of event.data.items) { await this.inventoryService.reserveStock(item.productId, item.quantity) } }
async handlePaymentProcessed(event: PaymentProcessedEvent): Promise<void> { if (event.data.status === PaymentStatus.COMPLETED) { // Confirm inventory reservation await this.inventoryService.confirmReservation(event.data.orderId) } else if (event.data.status === PaymentStatus.FAILED) { // Release inventory reservation await this.inventoryService.releaseReservation(event.data.orderId) } }}
// Event bus implementationclass EventBus { private subscribers: Map<string, EventHandler[]> = new Map()
subscribe(topic: string, handler: EventHandler): void { if (!this.subscribers.has(topic)) { this.subscribers.set(topic, []) } this.subscribers.get(topic)!.push(handler) }
async publish(message: EventMessage): Promise<void> { const handlers = this.subscribers.get(message.topic) || []
await Promise.all(handlers.map((handler) => handler.handle(message.event))) }}
Data Management Patternsh2
6. Database per Serviceh3
Each service owns its data:
// User service database schemaconst userSchema = { users: { id: 'UUID PRIMARY KEY', email: 'VARCHAR(255) UNIQUE NOT NULL', password_hash: 'VARCHAR(255) NOT NULL', first_name: 'VARCHAR(100)', last_name: 'VARCHAR(100)', created_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP', updated_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP', },
user_profiles: { id: 'UUID PRIMARY KEY', user_id: 'UUID REFERENCES users(id)', display_name: 'VARCHAR(100)', bio: 'TEXT', avatar_url: 'VARCHAR(500)', location: 'VARCHAR(200)', website: 'VARCHAR(500)', },}
// Order service database schemaconst orderSchema = { orders: { id: 'UUID PRIMARY KEY', user_id: 'UUID NOT NULL', // Reference to user (no FK constraint) total_amount: 'DECIMAL(10,2) NOT NULL', status: 'VARCHAR(50) NOT NULL', created_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP', updated_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP', },
order_items: { id: 'UUID PRIMARY KEY', order_id: 'UUID REFERENCES orders(id)', product_id: 'UUID NOT NULL', product_name: 'VARCHAR(255) NOT NULL', // Denormalized data quantity: 'INTEGER NOT NULL', unit_price: 'DECIMAL(10,2) NOT NULL', },}
7. CQRS (Command Query Responsibility Segregation)h3
Separate read and write operations:
// Command side (write operations)class OrderCommandService { constructor( private orderRepository: OrderRepository, private eventPublisher: EventPublisher ) {}
async createOrder(command: CreateOrderCommand): Promise<Order> { // Business logic validation const order = new Order(command.userId, command.items, command.shippingAddress)
// Save to write database const savedOrder = await this.orderRepository.save(order)
// Publish domain event await this.eventPublisher.publish(new OrderCreatedEvent(savedOrder))
return savedOrder }
async updateOrderStatus(command: UpdateOrderStatusCommand): Promise<Order> { const order = await this.orderRepository.findById(command.orderId)
if (!order) { throw new NotFoundError('Order not found') }
order.updateStatus(command.status)
const updatedOrder = await this.orderRepository.save(order)
await this.eventPublisher.publish(new OrderStatusUpdatedEvent(updatedOrder))
return updatedOrder }}
// Query side (read operations)class OrderQueryService { constructor(private readDatabase: ReadDatabase) {}
async getOrder(orderId: string): Promise<OrderView> { return await this.readDatabase.queryOne( ` SELECT o.*, c.name as customer_name, c.email as customer_email FROM orders o JOIN customers c ON o.customer_id = c.id WHERE o.id = $1 `, [orderId] ) }
async getUserOrders(userId: string, filters: OrderFilters): Promise<OrderListView> { let whereClause = 'WHERE o.customer_id = $1' const params = [userId] let paramIndex = 2
if (filters.status) { whereClause += ` AND o.status = $${paramIndex}` params.push(filters.status) paramIndex++ }
if (filters.dateFrom) { whereClause += ` AND o.created_at >= $${paramIndex}` params.push(filters.dateFrom) paramIndex++ }
const orders = await this.readDatabase.query( ` SELECT o.id, o.total_amount, o.status, o.created_at, COUNT(oi.id) as item_count FROM orders o LEFT JOIN order_items oi ON o.id = oi.order_id ${whereClause} GROUP BY o.id, o.total_amount, o.status, o.created_at ORDER BY o.created_at DESC LIMIT $${paramIndex} OFFSET $${paramIndex + 1} `, [...params, filters.limit, filters.offset] )
const totalCount = await this.readDatabase.queryOne( ` SELECT COUNT(*) as count FROM orders o ${whereClause} `, params.slice(0, -2) )
return { orders, totalCount: parseInt(totalCount.count), hasNextPage: filters.offset + orders.length < parseInt(totalCount.count), } }}
Observability Patternsh2
8. Distributed Tracingh3
Track requests across services:
import { Tracer } from 'opentelemetry'
// Initialize tracingconst tracer = new Tracer({ serviceName: 'order-service', serviceVersion: '1.0.0',})
// Traced service methodclass OrderService { async createOrder(orderRequest: CreateOrderRequest): Promise<Order> { return tracer.startActiveSpan('createOrder', async (span) => { try { span.setAttribute('user.id', orderRequest.userId) span.setAttribute('order.items.count', orderRequest.items.length)
// Validate user const userSpan = tracer.startSpan('validateUser') const user = await this.userServiceClient.getUser(orderRequest.userId) userSpan.end()
if (!user) { throw new ValidationError('User not found') }
// Process payment const paymentSpan = tracer.startSpan('processPayment') const paymentResult = await this.paymentServiceClient.processPayment({ orderId: orderRequest.orderId, amount: orderRequest.totalAmount, userId: orderRequest.userId, }) paymentSpan.end()
// Create order const orderSpan = tracer.startSpan('createOrderInDB') const order = await this.orderRepository.create(orderRequest) orderSpan.end()
span.setStatus({ code: 1 }) // OK return order } catch (error) { span.setStatus({ code: 2, message: error.message }) // ERROR throw error } finally { span.end() } }) }}
9. Centralized Loggingh3
Aggregate logs from all services:
// Structured logging with correlation IDsclass Logger { private correlationId: string
constructor(correlationId?: string) { this.correlationId = correlationId || uuidv4() }
info(message: string, context?: any): void { this.log('INFO', message, context) }
error(message: string, error?: Error, context?: any): void { this.log('ERROR', message, { ...context, error: error?.message, stack: error?.stack }) }
private log(level: string, message: string, context?: any): void { const logEntry = { timestamp: new Date().toISOString(), level, message, correlationId: this.correlationId, service: 'order-service', version: process.env.npm_package_version, ...context, }
// Send to centralized logging service console.log(JSON.stringify(logEntry)) }}
// Usage in service methodsclass OrderService { async createOrder(orderRequest: CreateOrderRequest): Promise<Order> { const logger = new Logger(orderRequest.correlationId)
logger.info('Creating order', { userId: orderRequest.userId })
try { const order = await this.orderRepository.create(orderRequest) logger.info('Order created successfully', { orderId: order.id }) return order } catch (error) { logger.error('Failed to create order', error, { orderData: orderRequest }) throw error } }}
Deployment Patternsh2
10. Container Orchestrationh3
Deploy microservices with Kubernetes:
apiVersion: apps/v1kind: Deploymentmetadata: name: order-service labels: app: order-servicespec: replicas: 3 selector: matchLabels: app: order-service template: metadata: labels: app: order-service spec: containers: - name: order-service image: myregistry/order-service:latest ports: - containerPort: 3000 env: - name: DATABASE_URL valueFrom: secretKeyRef: name: order-service-secrets key: database-url - name: REDIS_URL valueFrom: secretKeyRef: name: order-service-secrets key: redis-url - name: USER_SERVICE_URL valueFrom: configMapKeyRef: name: service-config key: user-service-url livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 3000 initialDelaySeconds: 5 periodSeconds: 5 resources: requests: memory: '128Mi' cpu: '100m' limits: memory: '512Mi' cpu: '500m'
---# order-service-service.ymlapiVersion: v1kind: Servicemetadata: name: order-servicespec: selector: app: order-service ports: - name: http port: 80 targetPort: 3000 type: ClusterIP
---# order-service-configmap.ymlapiVersion: v1kind: ConfigMapmetadata: name: service-configdata: user-service-url: 'http://user-service:80' payment-service-url: 'http://payment-service:80' inventory-service-url: 'http://inventory-service:80'
Security Patternsh2
11. Service-to-Service Authenticationh3
Secure inter-service communication:
// JWT for service authenticationclass ServiceAuthenticator { private serviceToken: string
constructor() { this.serviceToken = jwt.sign( { service: 'order-service', permissions: ['read:users', 'write:orders'], }, process.env.SERVICE_SECRET!, { expiresIn: '1h' } ) }
getAuthHeaders(): Record<string, string> { return { 'Authorization': `Bearer ${this.serviceToken}`, 'X-Service-Name': 'order-service', 'X-Request-ID': uuidv4(), } }}
// API client with authenticationclass UserServiceClient { private baseURL: string private authenticator: ServiceAuthenticator
constructor(baseURL: string) { this.baseURL = baseURL this.authenticator = new ServiceAuthenticator() }
async getUser(userId: string): Promise<User> { const response = await fetch(`${this.baseURL}/users/${userId}`, { headers: { 'Content-Type': 'application/json', ...this.authenticator.getAuthHeaders(), }, })
if (!response.ok) { throw new Error(`User service error: ${response.status}`) }
return response.json() }}
Monitoring and Alertingh2
12. Health Checks and Metricsh3
Monitor service health:
// Health check endpointclass HealthController { constructor( private database: Database, private redis: RedisClient, private userServiceClient: UserServiceClient ) {}
async checkHealth(): Promise<HealthStatus> { const checks = await Promise.allSettled([ this.checkDatabase(), this.checkRedis(), this.checkUserService(), this.checkMemoryUsage(), this.checkDiskSpace(), ])
const results = checks.map((check) => (check.status === 'fulfilled' ? check.value : { status: 'error', error: check.reason }))
const overallStatus = results.every((r) => r.status === 'ok') ? 'healthy' : 'unhealthy'
return { status: overallStatus, timestamp: new Date().toISOString(), checks: results, version: process.env.npm_package_version, } }
private async checkDatabase(): Promise<HealthCheck> { try { await this.database.query('SELECT 1') return { name: 'database', status: 'ok' } } catch (error) { return { name: 'database', status: 'error', error: error.message } } }
private async checkRedis(): Promise<HealthCheck> { try { await this.redis.ping() return { name: 'redis', status: 'ok' } } catch (error) { return { name: 'redis', status: 'error', error: error.message } } }
private async checkUserService(): Promise<HealthCheck> { try { await this.userServiceClient.getUser('test') return { name: 'user-service', status: 'ok' } } catch (error) { return { name: 'user-service', status: 'error', error: error.message } } }}
// Metrics collectionclass MetricsCollector { private metrics = { requests_total: new Counter({ name: 'requests_total', help: 'Total number of requests', labelNames: ['method', 'endpoint', 'status_code'], }),
request_duration_seconds: new Histogram({ name: 'request_duration_seconds', help: 'Request duration in seconds', labelNames: ['method', 'endpoint'], buckets: [0.1, 0.5, 1, 2.5, 5, 10], }),
active_connections: new Gauge({ name: 'active_connections', help: 'Number of active connections', }), }
recordRequest(method: string, endpoint: string, statusCode: number, duration: number): void { this.metrics.requests_total.inc({ method, endpoint, status_code: statusCode }) this.metrics.request_duration_seconds.observe({ method, endpoint }, duration) }
updateActiveConnections(count: number): void { this.metrics.active_connections.set(count) }}
Conclusionh2
Microservices architecture offers tremendous benefits but requires careful planning and implementation. The patterns I’ve shared here provide a solid foundation for building scalable, maintainable distributed systems.
Key takeaways:
- Domain-driven design for service boundaries
- API Gateway for request routing and security
- Event-driven architecture for loose coupling
- Database per service for data ownership
- Observability for monitoring and debugging
- Security for inter-service communication
Start small, iterate, and continuously improve your microservices architecture. The investment in proper design pays dividends in scalability and maintainability.
What microservices challenges are you facing? Which patterns have worked best for your distributed systems? Share your experiences!
Further Readingh2
This post reflects my experience as of October 2025. Microservices technologies and best practices evolve rapidly, so always evaluate the latest solutions for your specific needs.