Skip to main content
Microservices Architecture: When to Use and When to Avoid

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! 🎯

Kontakt aufnehmen

Bereit, gemeinsam etwas Großartiges zu bauen?

Sende uns eine Nachricht

πŸš€

Lass uns chatten.

ErzΓ€hl mir von deinem Projekt.

Lass uns gemeinsam etwas erschaffen 🀝

Visit my social profile and get connected