Microservices Architecture: When to Use and When to Avoid
Backend26. November 202520 Min. Lesezeit1 Aufrufe
MicroservicesArchitectureBackendDevOpsSystem DesignScalability
Teilen:
Microservices Architecture: When to Use and When to Avoid
Microservices are everywhere, but are they right for your project? Let's explore the architecture, patterns, and real-world considerations.
What Are Microservices?
Microservices architecture breaks down applications into small, independent services that:
- Run in their own process
- Communicate via APIs (HTTP, gRPC, message queues)
- Can be deployed independently
- Are organized around business capabilities
Monolith vs Microservices
Monolithic Architecture
βββββββββββββββββββββββββββββββ
β Single Application β
β βββββββββββββββββββββββ β
β β User Management β β
β βββββββββββββββββββββββ€ β
β β Order Processing β β
β βββββββββββββββββββββββ€ β
β β Payment Processing β β
β βββββββββββββββββββββββ€ β
β β Inventory β β
β βββββββββββββββββββββββ β
β Single Database β
βββββββββββββββββββββββββββββββ
Pros:
- β Simple to develop
- β Easy to test
- β Simple deployment
- β No network overhead
Cons:
- β Tight coupling
- β Difficult to scale
- β Technology lock-in
- β Large codebase
Microservices Architecture
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β User β β Order β β Payment β
β Service β β Service β β Service β
β ββββββββββ β β ββββββββββ β β ββββββββββ β
β β DB β β β β DB β β β β DB β β
β ββββββββββ β β ββββββββββ β β ββββββββββ β
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β β β
βββββββββββββββββββ΄βββββββββββββββββββ
API Gateway
Pros:
- β Independent deployment
- β Technology flexibility
- β Easy to scale
- β Fault isolation
Cons:
- β Complex infrastructure
- β Network latency
- β Data consistency challenges
- β Testing complexity
Microservices Patterns
1. API Gateway Pattern
// API Gateway (Express.js) const express = require('express'); const axios = require('axios'); const app = express(); // Route requests to appropriate services app.get('/api/users/:id', async (req, res) => { const user = await axios.get(`http://user-service:3001/users/${req.params.id}`); res.json(user.data); }); app.get('/api/orders/:id', async (req, res) => { const order = await axios.get(`http://order-service:3002/orders/${req.params.id}`); res.json(order.data); }); app.listen(3000);
2. Service Discovery Pattern
// Using Consul for service discovery const Consul = require('consul'); const consul = new Consul({ host: 'consul-server', port: 8500 }); // Register service consul.agent.service.register({ name: 'user-service', address: 'localhost', port: 3001, check: { http: 'http://localhost:3001/health', interval: '10s' } }); // Discover service async function getServiceUrl(serviceName) { const services = await consul.health.service(serviceName); return `http://${services[0].Service.Address}:${services[0].Service.Port}`; }
3. Circuit Breaker Pattern
const CircuitBreaker = require('opossum'); // Protect against cascading failures const options = { timeout: 3000, errorThresholdPercentage: 50, resetTimeout: 30000 }; const breaker = new CircuitBreaker(callExternalService, options); breaker.fallback(() => ({ status: 'error', message: 'Service temporarily unavailable' })); async function callExternalService(data) { const response = await axios.post('http://payment-service/charge', data); return response.data; } // Use the circuit breaker const result = await breaker.fire({ amount: 100, currency: 'USD' });
4. Event-Driven Architecture
// Publisher (Order Service) const amqp = require('amqplib'); async function publishOrderCreated(order) { const connection = await amqp.connect('amqp://rabbitmq'); const channel = await connection.createChannel(); await channel.assertExchange('orders', 'topic', { durable: true }); channel.publish( 'orders', 'order.created', Buffer.from(JSON.stringify(order)) ); console.log('Published order created event'); } // Subscriber (Inventory Service) async function subscribeToOrderEvents() { const connection = await amqp.connect('amqp://rabbitmq'); const channel = await connection.createChannel(); await channel.assertExchange('orders', 'topic', { durable: true }); const queue = await channel.assertQueue('', { exclusive: true }); await channel.bindQueue(queue.queue, 'orders', 'order.created'); channel.consume(queue.queue, (msg) => { const order = JSON.parse(msg.content.toString()); console.log('Processing order:', order); // Update inventory updateInventory(order.items); channel.ack(msg); }); }
5. Saga Pattern (Distributed Transactions)
// Orchestration-based Saga class OrderSaga { async execute(orderData) { try { // Step 1: Create order const order = await this.orderService.create(orderData); // Step 2: Reserve inventory await this.inventoryService.reserve(order.items); // Step 3: Process payment await this.paymentService.charge(order.total); // Step 4: Confirm order await this.orderService.confirm(order.id); return { success: true, orderId: order.id }; } catch (error) { // Compensating transactions (rollback) await this.inventoryService.unreserve(order.items); await this.orderService.cancel(order.id); return { success: false, error: error.message }; } } }
Database Strategies
Database per Service
ββββββββββββββββ ββββββββββββββββ
βUser Service β βOrder Service β
β ββββββββββ β β ββββββββββ β
β β Users β β β β Orders β β
β β DB β β β β DB β β
β ββββββββββ β β ββββββββββ β
ββββββββββββββββ ββββββββββββββββ
Pros:
- β Loose coupling
- β Independent scaling
Cons:
- β No ACID transactions
- β Data duplication
Shared Database (Anti-pattern)
ββββββββββββββββ ββββββββββββββββ
βUser Service β βOrder Service β
ββββββββ¬ββββββββ ββββββββ¬ββββββββ
β β
ββββββββββ¬ββββββββββββ
β
ββββββββΌβββββββ
β Shared β
β Database β
βββββββββββββββ
Why avoid?
- β Tight coupling
- β Schema changes affect all services
- β Defeats microservices purpose
Communication Patterns
Synchronous (REST/gRPC)
// REST API call const user = await fetch('http://user-service/users/123') .then(res => res.json()); // gRPC call const grpc = require('@grpc/grpc-js'); const protoLoader = require('@grpc/proto-loader'); const packageDefinition = protoLoader.loadSync('user.proto'); const userProto = grpc.loadPackageDefinition(packageDefinition).user; const client = new userProto.UserService( 'user-service:50051', grpc.credentials.createInsecure() ); client.getUser({ id: 123 }, (error, user) => { console.log(user); });
Asynchronous (Message Queue)
// RabbitMQ const amqp = require('amqplib'); // Send message async function sendMessage(queue, message) { const connection = await amqp.connect('amqp://rabbitmq'); const channel = await connection.createChannel(); await channel.assertQueue(queue); channel.sendToQueue(queue, Buffer.from(JSON.stringify(message))); } // Receive message async function receiveMessages(queue, handler) { const connection = await amqp.connect('amqp://rabbitmq'); const channel = await connection.createChannel(); await channel.assertQueue(queue); channel.consume(queue, (msg) => { handler(JSON.parse(msg.content.toString())); channel.ack(msg); }); }
Deployment Strategies
Docker Compose (Development)
version: '3.8' services: user-service: build: ./user-service ports: - "3001:3001" environment: - DATABASE_URL=postgres://db:5432/users order-service: build: ./order-service ports: - "3002:3002" environment: - DATABASE_URL=postgres://db:5432/orders api-gateway: build: ./api-gateway ports: - "3000:3000" depends_on: - user-service - order-service
Kubernetes (Production)
apiVersion: apps/v1 kind: Deployment metadata: name: user-service spec: replicas: 3 selector: matchLabels: app: user-service template: metadata: labels: app: user-service spec: containers: - name: user-service image: user-service:latest ports: - containerPort: 3001 env: - name: DATABASE_URL valueFrom: secretKeyRef: name: db-secrets key: user-db-url --- apiVersion: v1 kind: Service metadata: name: user-service spec: selector: app: user-service ports: - port: 80 targetPort: 3001
When to Use Microservices
β Use when:
- Large, complex application
- Multiple independent teams
- Different scaling requirements per feature
- Need for technology diversity
- High availability requirements
β Avoid when:
- Small application or startup
- Small team (< 5 developers)
- Tight deadlines
- Limited DevOps expertise
- Simple CRUD application
Migration Strategy
Strangler Fig Pattern
Phase 1: Monolith
βββββββββββββββββββ
β Monolith β
βββββββββββββββββββ
Phase 2: Extract first service
βββββββββββββββ ββββββββββββ
β Monolith β β User β
β (minus β β Service β
β users) β ββββββββββββ
βββββββββββββββ
Phase 3: Extract more services
βββββββββββ ββββββββββ βββββββββββ
βMonolith β β User β β Order β
β (core) β βService β β Service β
βββββββββββ ββββββββββ βββββββββββ
Conclusion
Microservices aren't a silver bullet:
- Start simple with monolith
- Extract services when needed
- Use proven patterns (API Gateway, Circuit Breaker, Saga)
- Invest in DevOps (CI/CD, monitoring, logging)
- Plan for failure (resilience, fallbacks)
Remember: Complexity is a feature, not a bug. Only add it when you need it! π―