Zum Hauptinhalt springen

🧩 Microservices-Architektur

📖 Definition

Die Microservices-Architektur (MSA) ist ein Architekturmuster, bei dem eine große Anwendung in mehrere kleine und unabhängige Services aufgeteilt wird. Jeder Service ist für eine spezifische Geschäftsfunktion verantwortlich und kann unabhängig bereitgestellt und skaliert werden. Im Gegensatz zur monolithischen Architektur bietet sie durch lose Kopplung zwischen Services Flexibilität und Skalierbarkeit.

🎯 Verstehen durch Analogien

Großunternehmen vs Startups

Monolithisch = Großunternehmen
├─ Alle Abteilungen in einem Gebäude
├─ Zentralisierte Verwaltung
├─ Problem in einer Abteilung → Betrifft alles
├─ Änderungen schwierig
└─ Langsame Entscheidungen

Microservices = Startup-Föderation
├─ Jedes Team hat ein unabhängiges Büro
├─ Autonome Entscheidungen
├─ Problem in einem Team → Andere funktionieren normal
├─ Schnelle Änderungen
└─ Flexible Skalierung

LEGO vs Ton

Monolithisch = Tonblock
┌──────────────────────────────┐
│ Benutzer │ Produkt │ Bestellung │
│ Verwaltung│Verwaltung│Verwaltung│
│ Alles in einem │
└──────────────────────────────┘
- Muss komplett neu gemacht werden
- Eine Änderung → Betrifft alles
- Schwer zu skalieren

Microservices = LEGO-Bausteine
┌─────┐ ┌─────┐ ┌─────┐
│Benutzer│ │Produkt│ │Bestellung│
│Service│ │Service│ │Service│
└─────┘ └─────┘ └─────┘
- Einfacher Austausch von Bausteinen
- Unabhängige Änderungen
- Nur benötigte Teile skalieren

⚙️ Funktionsweise

1. Monolithisch vs Microservices

========== Monolithisch ==========
┌─────────────────────────────────────┐
│ Eine Anwendung │
│ │
│ ┌─────────────────────────────┐ │
│ │ Benutzerverwaltung │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ Produktverwaltung │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ Bestellverwaltung │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ Zahlungsmodul │ │
│ └─────────────────────────────┘ │
│ │
│ Eine Datenbank │
│ Eine Codebasis │
│ Eine Bereitstellungseinheit │
└─────────────────────────────────────┘

Vorteile:
✅ Schnell zu Beginn der Entwicklung
✅ Einfache Tests
✅ Einfaches Deployment (nur eins)
✅ Einfaches Debugging

Nachteile:
❌ Komplex bei Wachstum
❌ Gesamte Unterbrechung beim Deployment
❌ Keine teilweise Skalierung möglich
❌ Technologie-Stack schwer zu ändern

========== Microservices ==========
┌──────────┐ ┌──────────┐ ┌──────────┐
│Benutzer │ │Produkt │ │Bestellung │
│Service │ │Service │ │Service │
│ │ │ │ │ │
│Node.js │ │Java │ │Go │
│MongoDB │ │MySQL │ │PostgreSQL │
└──────────┘ └──────────┘ └──────────┘
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│Zahlung │ │Benachrichtigung│ │Bewertung │
│Service │ │Service │ │Service │
│ │ │ │ │ │
│Python │ │Node.js │ │Ruby │
│Redis │ │Kafka │ │Cassandra │
└──────────┘ └──────────┘ └──────────┘

Vorteile:
✅ Unabhängiges Deployment
✅ Freie Wahl des Tech-Stacks
✅ Teilweise Skalierung möglich
✅ Team-Unabhängigkeit
✅ Fehler-Isolierung

Nachteile:
❌ Hohe Anfangskomplexität
❌ Netzwerkkommunikations-Overhead
❌ Verteilte Transaktionen schwierig
❌ Komplexe Tests
❌ Erhöhte Betriebskosten

2. Service-Kommunikation

========== Synchrone Kommunikation (HTTP/REST) ==========
Bestell-Service → Produkt-Service
"Gibt es Lagerbestand für Produkt 123?"

"Ja, 5 Stück verfügbar"

Bestell-Service → Zahlungs-Service
"Bitte Zahlung über 10.000 verarbeiten"

"Zahlung abgeschlossen"

Bestellung abgeschlossen

Vorteile: Einfach, intuitiv
Nachteile: Gesamtausfall bei Service-Fehler

========== Asynchrone Kommunikation (Message Queue) ==========
Bestell-Service → Message Queue
Nachricht "Bestellung erstellt" veröffentlichen

┌──────────────────┐
│ Message Queue │
│ (RabbitMQ, │
│ Kafka, etc.) │
└──────────────────┘

┌────┴────┬────────┐
↓ ↓ ↓
Zahlung Benachrichtigung Lagerbestand
Service Service Service
Jeder verarbeitet unabhängig

Vorteile: Lose Kopplung, Fehler-Isolierung
Nachteile: Erhöhte Komplexität, schwieriges Debugging

========== API Gateway ==========
Client (Mobil/Web)

┌──────────────────┐
│ API Gateway │
│ - Routing │
│ - Authentifizierung│
│ - Load Balancing│
│ - Logging │
└──────────────────┘
┌────┴────┬────────┐
↓ ↓ ↓
ServiceA ServiceB ServiceC

Rollen:
- Einzelner Einstiegspunkt
- Client-Vereinfachung
- Verarbeitung gemeinsamer Funktionen

3. Datenverwaltung

========== Monolithisch: Gemeinsame Datenbank ==========
┌─────────────────────────────────────┐
│ Anwendung │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ModulA│ │ModulB│ │ModulC│ │
│ └──┬──┘ └──┬──┘ └──┬──┘ │
└─────┼────────┼───────┼──────────────┘
└────────┼───────┘

┌──────────────────┐
│ Eine Datenbank │
│ ┌────┬────┬────┐│
│ │TA│TB│TC││
│ └────┴────┴────┘│
└──────────────────┘

Vorteile: Einfache JOINs, Konsistenz garantiert
Nachteile: Hohe Kopplung, schwer zu skalieren

========== Microservices: DB pro Service ==========
┌──────────┐ ┌──────────┐ ┌──────────┐
│Service A │ │Service B │ │Service C │
└────┬─────┘ └────┬─────┘ └────┬─────┘
↓ ↓ ↓
┌─────────┐ ┌─────────┐ ┌─────────┐
│ DB A │ │ DB B │ │ DB C │
│ (MySQL) │ │(MongoDB)│ │(PostgreSQL)│
└─────────┘ └─────────┘ └─────────┘

Vorteile: Unabhängigkeit, freie Technologiewahl
Nachteile: Keine JOINs, schwierige Konsistenz

========== Datenkonsistenz ==========
// Saga-Muster
1. Bestell-Service: Bestellung erstellen
2. Zahlungs-Service: Zahlung verarbeiten
3. Lagerbestands-Service: Bestand reduzieren
4. Versand-Service: Versand starten

Was passiert bei Fehler in Schritt 3?
→ Kompensierende Transaktion
4. Bestandsreduzierung fehlgeschlagen
3. Zahlung stornieren ← Kompensation
2. Bestellung stornieren ← Kompensation

💡 Praktische Beispiele

Monolithisches Beispiel (Express.js)

// ========== Monolithische Anwendung ==========
// server.js - Eine Datei mit allen Funktionen

const express = require('express');
const app = express();

app.use(express.json());

// Eine Datenbank
const db = require('./database');

// ========== Benutzerverwaltung ==========
app.post('/api/users', async (req, res) => {
const { username, email, password } = req.body;
const user = await db.users.create({ username, email, password });
res.json(user);
});

app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json(user);
});

// ========== Produktverwaltung ==========
app.post('/api/products', async (req, res) => {
const { name, price, stock } = req.body;
const product = await db.products.create({ name, price, stock });
res.json(product);
});

app.get('/api/products', async (req, res) => {
const products = await db.products.findAll();
res.json(products);
});

// ========== Bestellverwaltung ==========
app.post('/api/orders', async (req, res) => {
const { userId, productId, quantity } = req.body;

// Transaktion für Konsistenz
const transaction = await db.sequelize.transaction();

try {
// 1. Bestand prüfen
const product = await db.products.findById(productId, { transaction });
if (product.stock < quantity) {
throw new Error('Unzureichender Bestand');
}

// 2. Bestand reduzieren
await product.update(
{ stock: product.stock - quantity },
{ transaction }
);

// 3. Bestellung erstellen
const order = await db.orders.create(
{ userId, productId, quantity, total: product.price * quantity },
{ transaction }
);

// 4. Zahlung verarbeiten
await processPayment(order.total);

await transaction.commit();
res.json(order);
} catch (error) {
await transaction.rollback();
res.status(400).json({ error: error.message });
}
});

// ========== Zahlungsabwicklung ==========
app.post('/api/payments', async (req, res) => {
const { orderId, amount } = req.body;
const payment = await db.payments.create({ orderId, amount });
res.json(payment);
});

// Mit einem Server ausführen
app.listen(3000, () => {
console.log('Monolithischer Server: http://localhost:3000');
});

/*
Vorteile:
- Code an einem Ort
- Schnelle Entwicklung
- Einfaches Debugging
- Einfache Transaktionen

Nachteile:
- Komplex bei Wachstum
- Kompletter Neustart beim Deployment
- Keine teilweise Skalierung
- Ein Funktionsfehler → Betrifft alles
*/

Microservices-Beispiel

// ========== 1. Benutzer-Service (user-service.js) ==========
// Port: 3001
const express = require('express');
const app = express();
const mongoose = require('mongoose');

app.use(express.json());

// Unabhängige Datenbank
mongoose.connect('mongodb://localhost/users-db');

const User = mongoose.model('User', {
username: String,
email: String,
password: String
});

// Benutzer erstellen
app.post('/users', async (req, res) => {
const { username, email, password } = req.body;

try {
const user = new User({ username, email, password });
await user.save();

// Event veröffentlichen (andere Services benachrichtigen)
await publishEvent('user.created', { userId: user._id, email });

res.json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
});

// Benutzer abfragen
app.get('/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
res.json(user);
});

app.listen(3001, () => {
console.log('Benutzer-Service: http://localhost:3001');
});

// ========== 2. Produkt-Service (product-service.js) ==========
// Port: 3002
const express = require('express');
const app = express();
const { Pool } = require('pg');

app.use(express.json());

// PostgreSQL verwenden (andere DB!)
const pool = new Pool({
host: 'localhost',
database: 'products-db',
port: 5432
});

// Produktliste
app.get('/products', async (req, res) => {
const result = await pool.query('SELECT * FROM products');
res.json(result.rows);
});

// Produktdetails
app.get('/products/:id', async (req, res) => {
const result = await pool.query(
'SELECT * FROM products WHERE id = $1',
[req.params.id]
);
res.json(result.rows[0]);
});

// Bestand prüfen
app.get('/products/:id/stock', async (req, res) => {
const result = await pool.query(
'SELECT stock FROM products WHERE id = $1',
[req.params.id]
);
res.json({ stock: result.rows[0].stock });
});

// Bestand reduzieren
app.post('/products/:id/decrease-stock', async (req, res) => {
const { quantity } = req.body;

const client = await pool.connect();
try {
await client.query('BEGIN');

// Aktuellen Bestand prüfen
const result = await client.query(
'SELECT stock FROM products WHERE id = $1 FOR UPDATE',
[req.params.id]
);

const currentStock = result.rows[0].stock;
if (currentStock < quantity) {
throw new Error('Unzureichender Bestand');
}

// Bestand reduzieren
await client.query(
'UPDATE products SET stock = stock - $1 WHERE id = $2',
[quantity, req.params.id]
);

await client.query('COMMIT');
res.json({ success: true });
} catch (error) {
await client.query('ROLLBACK');
res.status(400).json({ error: error.message });
} finally {
client.release();
}
});

app.listen(3002, () => {
console.log('Produkt-Service: http://localhost:3002');
});

// ========== 3. Bestell-Service (order-service.js) ==========
// Port: 3003
const express = require('express');
const axios = require('axios');
const app = express();

app.use(express.json());

const orders = []; // Sollte eigentlich Datenbank verwenden

// Bestellung erstellen
app.post('/orders', async (req, res) => {
const { userId, productId, quantity } = req.body;

try {
// 1. Benutzer prüfen (Benutzer-Service aufrufen)
const userResponse = await axios.get(
`http://localhost:3001/users/${userId}`
);
const user = userResponse.data;

if (!user) {
return res.status(404).json({ error: 'Benutzer nicht gefunden' });
}

// 2. Produktinfo abfragen (Produkt-Service aufrufen)
const productResponse = await axios.get(
`http://localhost:3002/products/${productId}`
);
const product = productResponse.data;

// 3. Bestand prüfen
const stockResponse = await axios.get(
`http://localhost:3002/products/${productId}/stock`
);
const { stock } = stockResponse.data;

if (stock < quantity) {
return res.status(400).json({ error: 'Unzureichender Bestand' });
}

// 4. Bestand reduzieren
await axios.post(
`http://localhost:3002/products/${productId}/decrease-stock`,
{ quantity }
);

// 5. Zahlung verarbeiten (Zahlungs-Service aufrufen)
const total = product.price * quantity;
const paymentResponse = await axios.post(
'http://localhost:3004/payments',
{ userId, amount: total }
);

// 6. Bestellung erstellen
const order = {
id: orders.length + 1,
userId,
productId,
quantity,
total,
status: 'completed',
createdAt: new Date()
};
orders.push(order);

// 7. Event veröffentlichen
await publishEvent('order.created', order);

res.json(order);
} catch (error) {
// Saga-Muster: Kompensierende Transaktion
console.error('Bestellfehler:', error.message);

// Bestand wiederherstellen
try {
await axios.post(
`http://localhost:3002/products/${productId}/increase-stock`,
{ quantity }
);
} catch (rollbackError) {
console.error('Bestandswiederherstellung fehlgeschlagen:', rollbackError.message);
}

res.status(500).json({ error: 'Bestellverarbeitung fehlgeschlagen' });
}
});

// Bestellung abfragen
app.get('/orders/:id', (req, res) => {
const order = orders.find(o => o.id === parseInt(req.params.id));
res.json(order);
});

app.listen(3003, () => {
console.log('Bestell-Service: http://localhost:3003');
});

// ========== 4. Zahlungs-Service (payment-service.js) ==========
// Port: 3004
const express = require('express');
const app = express();

app.use(express.json());

const payments = [];

app.post('/payments', async (req, res) => {
const { userId, amount } = req.body;

// Externe Zahlungs-API aufrufen (z.B.: Stripe, Toss Payments)
try {
// Tatsächliche Zahlungsverarbeitung
const payment = {
id: payments.length + 1,
userId,
amount,
status: 'success',
createdAt: new Date()
};
payments.push(payment);

// Event veröffentlichen
await publishEvent('payment.completed', payment);

res.json(payment);
} catch (error) {
res.status(400).json({ error: 'Zahlung fehlgeschlagen' });
}
});

app.listen(3004, () => {
console.log('Zahlungs-Service: http://localhost:3004');
});

// ========== 5. API Gateway (gateway.js) ==========
// Port: 3000
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();

// Authentifizierungs-Middleware
function authenticate(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Authentifizierung erforderlich' });
}
// JWT-Verifikation usw.
next();
}

// Logging-Middleware
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`);
next();
});

// Benutzer-Service Proxy
app.use('/api/users', authenticate, createProxyMiddleware({
target: 'http://localhost:3001',
pathRewrite: { '^/api/users': '/users' },
changeOrigin: true
}));

// Produkt-Service Proxy
app.use('/api/products', createProxyMiddleware({
target: 'http://localhost:3002',
pathRewrite: { '^/api/products': '/products' },
changeOrigin: true
}));

// Bestell-Service Proxy
app.use('/api/orders', authenticate, createProxyMiddleware({
target: 'http://localhost:3003',
pathRewrite: { '^/api/orders': '/orders' },
changeOrigin: true
}));

// Zahlungs-Service Proxy
app.use('/api/payments', authenticate, createProxyMiddleware({
target: 'http://localhost:3004',
pathRewrite: { '^/api/payments': '/payments' },
changeOrigin: true
}));

app.listen(3000, () => {
console.log('API Gateway: http://localhost:3000');
});

// ========== 6. Event Bus (event-bus.js) ==========
const amqp = require('amqplib');

let connection, channel;

// RabbitMQ-Verbindung
async function connect() {
connection = await amqp.connect('amqp://localhost');
channel = await connection.createChannel();
}

// Event veröffentlichen
async function publishEvent(eventType, data) {
await channel.assertQueue(eventType);
channel.sendToQueue(
eventType,
Buffer.from(JSON.stringify(data))
);
console.log(`Event veröffentlicht: ${eventType}`, data);
}

// Event abonnieren
async function subscribeEvent(eventType, callback) {
await channel.assertQueue(eventType);
channel.consume(eventType, (msg) => {
const data = JSON.parse(msg.content.toString());
console.log(`Event empfangen: ${eventType}`, data);
callback(data);
channel.ack(msg);
});
}

connect();

module.exports = { publishEvent, subscribeEvent };

Microservices mit Docker Compose ausführen

# docker-compose.yml
version: '3.8'

services:
# API Gateway
gateway:
build: ./gateway
ports:
- "3000:3000"
depends_on:
- user-service
- product-service
- order-service
- payment-service

# Benutzer-Service
user-service:
build: ./user-service
ports:
- "3001:3001"
environment:
- MONGO_URL=mongodb://mongo:27017/users
depends_on:
- mongo
- rabbitmq

# Produkt-Service
product-service:
build: ./product-service
ports:
- "3002:3002"
environment:
- POSTGRES_URL=postgres://postgres:password@postgres:5432/products
depends_on:
- postgres
- rabbitmq

# Bestell-Service
order-service:
build: ./order-service
ports:
- "3003:3003"
environment:
- MYSQL_URL=mysql://root:password@mysql:3306/orders
depends_on:
- mysql
- rabbitmq

# Zahlungs-Service
payment-service:
build: ./payment-service
ports:
- "3004:3004"
depends_on:
- rabbitmq

# Datenbanken
mongo:
image: mongo:6
volumes:
- mongo-data:/data/db

postgres:
image: postgres:15
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=products
volumes:
- postgres-data:/var/lib/postgresql/data

mysql:
image: mysql:8
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=orders
volumes:
- mysql-data:/var/lib/mysql

# Message Queue
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672" # Management UI

volumes:
mongo-data:
postgres-data:
mysql-data:

Service Mesh (Istio-Beispiel)

# istio-config.yaml
# Service Mesh - Verwaltung der Service-Kommunikation

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: order-service
spec:
hosts:
- order-service
http:
# Traffic-Verteilung (Canary-Deployment)
- match:
- headers:
user-type:
exact: beta
route:
- destination:
host: order-service
subset: v2 # Neue Version
weight: 20
- destination:
host: order-service
subset: v1 # Bestehende Version
weight: 80

# Retry-Richtlinie
- route:
- destination:
host: order-service
retries:
attempts: 3
perTryTimeout: 2s

# Timeout
timeout: 10s

---
# Circuit Breaker
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: payment-service
spec:
host: payment-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 2
outlierDetection:
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50

🤔 Häufig gestellte Fragen

F1. Wann sollte ich Microservices verwenden?

A:

✅ Geeignet für Microservices:

1. Große Anwendung
- Team: Mehr als 10 Personen
- Code: Mehr als 100.000 Zeilen
- Benutzer: Hunderttausende oder mehr

2. Bedarf an schnellem Deployment
- Mehrfache Deployments pro Tag
- Unabhängige Feature-Releases
- Häufige A/B-Tests

3. Bedarf an diversem Tech-Stack
- Optimale Technologie für jeden Service wählen
- Integration mit Legacy-Systemen

4. Bedarf an unabhängiger Skalierung
- Hoher Traffic bei spezifischen Funktionen
- Unterschiedliche Ressourcenanforderungen pro Service

5. Team-Unabhängigkeit wichtig
- Gleichzeitige Entwicklung durch mehrere Teams
- Minimierung der Team-Abhängigkeiten

Beispiele:
- Netflix: Hunderte von Microservices
- Amazon: 2-Pizza-Team (Team pro Service)
- Uber: Services nach Region und Funktion getrennt

❌ Geeignet für Monolith:

1. Kleine Anwendung
- Team: 5 Personen oder weniger
- Funktionen: Klar und einfach
- Traffic: Gering

2. Frühes Startup
- Schnelle MVP-Entwicklung erforderlich
- Anforderungen ändern sich häufig
- Begrenzte Ressourcen

3. Einfaches CRUD
- Keine komplexe Geschäftslogik
- Service-Grenzen unklar

4. Mangelnde Betriebserfahrung
- Kein DevOps-Team
- Keine Erfahrung mit verteilten Systemen

Beispiele:
- Blog, Portfolio
- Kleiner E-Commerce
- Interne Tools

📊 Entscheidungs-Checkliste:

□ Team mit mehr als 10 Personen?
□ Codebasis mit mehr als 100.000 Zeilen?
□ Häufiger Bedarf an unabhängigem Deployment?
□ Bedarf an teilweiser Skalierung?
□ DevOps-Team vorhanden?
□ Erfahrung mit verteilten Systemen?

3 oder mehr angekreuzt → Microservices erwägen
2 oder weniger → Monolith beibehalten

F2. Was ist die größte Herausforderung bei Microservices?

A:

// ========== 1. Verteilte Transaktionen ==========

// Monolithisch: Einfache Transaktion
await db.transaction(async (t) => {
await createOrder(data, t);
await decreaseStock(productId, t);
await processPayment(amount, t);
// Bei Fehler komplettes Rollback
});

// Microservices: Komplexes Saga-Muster
async function createOrderSaga(data) {
try {
// Schritt 1
const order = await orderService.create(data);

// Schritt 2
await productService.decreaseStock(data.productId);

// Schritt 3
await paymentService.process(order.total);

return order;
} catch (error) {
// Kompensierende Transaktionen (rückwärts)
await paymentService.refund(order.total);
await productService.increaseStock(data.productId);
await orderService.cancel(order.id);

throw error;
}
}

// ========== 2. Datenkonsistenz ==========

// Problem: Daten verteilt auf mehrere Services
// Benutzer-Service: userId, name
// Bestell-Service: userId, orders
// Zahlungs-Service: userId, payments

// Lösung 1: Event Sourcing
eventBus.on('user.updated', async (event) => {
// Bei Änderung der Benutzerinfo andere Services aktualisieren
await orderService.updateUserInfo(event.userId, event.name);
await paymentService.updateUserInfo(event.userId, event.name);
});

// Lösung 2: CQRS (Command Query Responsibility Segregation)
// Schreiben und Lesen trennen
// Schreiben: Jeder Service unabhängig
// Lesen: Integrierte Ansicht (Read Model)

// ========== 3. Netzwerklatenz ==========

// Monolithisch: Funktionsaufruf (schnell)
const user = getUser(userId); // 1ms

// Microservices: HTTP-Anfrage (langsam)
const user = await axios.get(`http://user-service/users/${userId}`); // 50ms

// Lösung: Caching
const redis = require('redis');
const cache = redis.createClient();

async function getUser(userId) {
// 1. Cache prüfen
const cached = await cache.get(`user:${userId}`);
if (cached) return JSON.parse(cached);

// 2. Service aufrufen
const response = await axios.get(`http://user-service/users/${userId}`);
const user = response.data;

// 3. Im Cache speichern
await cache.setex(`user:${userId}`, 3600, JSON.stringify(user));

return user;
}

// ========== 4. Service-Fehlerbehandlung ==========

// Circuit Breaker-Muster
const CircuitBreaker = require('opossum');

const options = {
timeout: 3000, // 3 Sekunden Timeout
errorThresholdPercentage: 50, // Bei 50% Fehler
resetTimeout: 30000 // Nach 30 Sekunden erneut versuchen
};

const breaker = new CircuitBreaker(async (userId) => {
return await axios.get(`http://user-service/users/${userId}`);
}, options);

breaker.fallback(() => ({
id: userId,
name: 'Unbekannt', // Fallback-Daten
cached: true
}));

// Verwenden
breaker.fire(userId)
.then(console.log)
.catch(console.error);

// ========== 5. Monitoring und Debugging ==========

// Distributed Tracing
// Jaeger, Zipkin verwenden

const tracer = require('jaeger-client').initTracer(config);

app.use((req, res, next) => {
const span = tracer.startSpan('http_request');
span.setTag('http.method', req.method);
span.setTag('http.url', req.url);

req.span = span;
next();
});

// Trace-ID bei Service-Aufrufen weitergeben
await axios.get('http://order-service/orders', {
headers: {
'x-trace-id': req.span.context().toTraceId()
}
});

F3. Was ist die Rolle des API Gateways?

A:

// ========== Hauptfunktionen des API Gateway ==========

const express = require('express');
const rateLimit = require('express-rate-limit');
const jwt = require('jsonwebtoken');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

// ========== 1. Routing ==========
// Client muss nur einen Endpunkt kennen
app.use('/api/users', createProxyMiddleware({
target: 'http://user-service:3001',
changeOrigin: true
}));

app.use('/api/products', createProxyMiddleware({
target: 'http://product-service:3002',
changeOrigin: true
}));

// ========== 2. Authentifizierung und Autorisierung ==========
function authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];

if (!token) {
return res.status(401).json({ error: 'Token erforderlich' });
}

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Ungültiger Token' });
}
}

app.use('/api/orders', authenticate, createProxyMiddleware({
target: 'http://order-service:3003'
}));

// ========== 3. Rate Limiting ==========
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 Minuten
max: 100 // Maximal 100 Anfragen
});

app.use('/api/', limiter);

// ========== 4. Load Balancing ==========
const productServiceInstances = [
'http://product-service-1:3002',
'http://product-service-2:3002',
'http://product-service-3:3002'
];

let currentIndex = 0;

app.use('/api/products', createProxyMiddleware({
target: productServiceInstances[currentIndex],
router: () => {
// Round Robin
const target = productServiceInstances[currentIndex];
currentIndex = (currentIndex + 1) % productServiceInstances.length;
return target;
}
}));

// ========== 5. Request/Response-Transformation ==========
app.use('/api/legacy', createProxyMiddleware({
target: 'http://legacy-service:8080',
onProxyReq: (proxyReq, req) => {
// Request transformieren
proxyReq.setHeader('X-API-Version', '2.0');
},
onProxyRes: (proxyRes, req, res) => {
// Response transformieren
proxyRes.headers['X-Custom-Header'] = 'Gateway';
}
}));

// ========== 6. Caching ==========
const redis = require('redis');
const cache = redis.createClient();

app.get('/api/products/:id', async (req, res) => {
const cacheKey = `product:${req.params.id}`;

// Cache prüfen
const cached = await cache.get(cacheKey);
if (cached) {
return res.json(JSON.parse(cached));
}

// Service aufrufen
const response = await axios.get(
`http://product-service:3002/products/${req.params.id}`
);

// Im Cache speichern
await cache.setex(cacheKey, 3600, JSON.stringify(response.data));

res.json(response.data);
});

// ========== 7. Logging und Monitoring ==========
app.use((req, res, next) => {
const start = Date.now();

res.on('finish', () => {
const duration = Date.now() - start;
console.log({
method: req.method,
path: req.path,
status: res.statusCode,
duration: `${duration}ms`,
user: req.user?.id
});
});

next();
});

// ========== 8. Fehlerbehandlung ==========
app.use((err, req, res, next) => {
console.error('Gateway Error:', err);

if (err.code === 'ECONNREFUSED') {
return res.status(503).json({
error: 'Service nicht verfügbar'
});
}

res.status(500).json({
error: 'Es ist ein Serverfehler aufgetreten'
});
});

// ========== 9. Service Discovery ==========
const consul = require('consul')();

async function getServiceUrl(serviceName) {
const result = await consul.health.service({
service: serviceName,
passing: true // Nur Instanzen, die Health Check bestehen
});

if (result.length === 0) {
throw new Error(`Service ${serviceName} nicht gefunden`);
}

// Zufällig auswählen
const instance = result[Math.floor(Math.random() * result.length)];
return `http://${instance.Service.Address}:${instance.Service.Port}`;
}

app.listen(3000);

F4. Was sind Microservices-Deployment-Strategien?

A:

# ========== 1. Blue-Green-Deployment ==========
# Neue Version (Green) bereitstellen und Traffic auf einmal umschalten

# Blue (aktuelle Version)
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
version: blue # Aktueller Traffic
ports:
- port: 80

---
# Green (neue Version) bereitstellen
kubectl apply -f order-service-green.yaml

# Nach Test Traffic umschalten
kubectl patch service order-service -p '{"spec":{"selector":{"version":"green"}}}'

# Bei Problemen sofort zurückrollen
kubectl patch service order-service -p '{"spec":{"selector":{"version":"blue"}}}'

# ========== 2. Canary-Deployment ==========
# Nur Teil des Traffics zur neuen Version, schrittweise erweitern

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: order-service
spec:
hosts:
- order-service
http:
- route:
- destination:
host: order-service
subset: v1 # Bestehende Version
weight: 90 # 90% Traffic
- destination:
host: order-service
subset: v2 # Neue Version
weight: 10 # 10% Traffic

# Schrittweise erhöhen
# 10% → 25% → 50% → 75% → 100%

# ========== 3. Rolling Update ==========
# Kubernetes-Standardstrategie

apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Maximal 1 zusätzlicher
maxUnavailable: 1 # Maximal 1 nicht verfügbar
template:
spec:
containers:
- name: order-service
image: order-service:v2

# Reihenfolge:
# 1. Einen neuen Pod starten
# 2. Einen bestehenden Pod beenden, wenn Health Check bestanden
# 3. Wiederholen (bis alle 5 ersetzt sind)

# ========== 4. Docker Compose-Deployment ==========
# docker-compose.yml

version: '3.8'

services:
order-service:
image: order-service:latest
deploy:
replicas: 3
update_config:
parallelism: 1 # Einer nach dem anderen
delay: 10s # 10 Sekunden Intervall
failure_action: rollback # Bei Fehler zurückrollen
restart_policy:
condition: on-failure

# Bereitstellen
docker stack deploy -c docker-compose.yml myapp

# ========== 5. CI/CD-Pipeline ==========
# .github/workflows/deploy.yml

name: Deploy Microservices

on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
# Jeden Service unabhängig bereitstellen
- name: Geänderte Services erkennen
uses: dorny/paths-filter@v2
id: changes
with:
filters: |
user-service:
- 'services/user/**'
product-service:
- 'services/product/**'

- name: Benutzer-Service bereitstellen
if: steps.changes.outputs.user-service == 'true'
run: |
docker build -t user-service:${{ github.sha }} services/user
docker push user-service:${{ github.sha }}
kubectl set image deployment/user-service user-service=user-service:${{ github.sha }}

- name: Produkt-Service bereitstellen
if: steps.changes.outputs.product-service == 'true'
run: |
docker build -t product-service:${{ github.sha }} services/product
docker push product-service:${{ github.sha }}
kubectl set image deployment/product-service product-service=product-service:${{ github.sha }}

F5. Wie migriert man von Monolith zu Microservices?

A:

// ========== Schrittweise Migrationsstrategie ==========

// ========== Phase 1: Strangler Fig-Muster ==========
// Neue Features als Microservices, bestehende beibehalten

┌─────────────────────────────────┐
Monolithische Anwendung
│ ┌──────────┬──────────┬──────┐ │
│ │Benutzer- │Produkt- │Bestell│ │
│ │verwaltung│verwaltung│verwaltung│ │
│ └──────────┴──────────┴──────┘ │
└─────────────────────────────────┘

// Schritt 1: Neue Funktion (Benachrichtigungen) als Microservice
┌─────────────────────┐ ┌──────────────┐
Monolith │ │Benachrichtigungs-
Benutzer│Produkt│Bestellung│ Service
└─────────────────────┘ └──────────────┘

// Schritt 2: Bestellfunktion trennen
┌─────────────────────┐ ┌──────────────┐
Monolith │ │Bestell-
Benutzer│Produkt │ │Service │
└─────────────────────┘ └──────────────┘
┌──────────────┐
│Benachrichtigungs-
│Service │
└──────────────┘

// Schritt 3: Alle Funktionen trennen
┌──────────┐ ┌──────────┐ ┌──────────┐
│Benutzer- │ │Produkt- │ │Bestell-
│Service │ │Service │ │Service │
└──────────┘ └──────────┘ └──────────┘
┌──────────┐
│Benachrichtigungs-
│Service │
└──────────┘

// ========== Phase 2: API Gateway einführen ==========

// Vorher: Client ruft Monolith direkt auf
const response = await fetch('http://monolith/api/orders');

// Nachher: Über API Gateway aufrufen
const response = await fetch('http://api-gateway/api/orders');

// API Gateway führt Routing durch
if (route === '/api/orders') {
// Zu neuem Service routen
proxy('http://order-service/orders');
} else {
// Noch zu Monolith
proxy('http://monolith/api');
}

// ========== Phase 3: Datenbank trennen ==========

// Problem: Gemeinsame Datenbank
┌────────────────┐
Monolith-DB
│ ┌──────────┐ │
│ │Users │ │
│ │Products │ │
│ │Orders │ │
│ └──────────┘ │
└────────────────┘

// Lösung: Datenbank pro Service

// 1) Dual Write
async function createOrder(data) {
// In Monolith-DB schreiben
await monolithDB.orders.create(data);

// Auch in neue Service-DB schreiben
await orderServiceDB.orders.create(data);
}

// 2) Change Data Capture (CDC)
// Änderungen der Monolith-DB automatisch synchronisieren
const debezium = require('debezium');

debezium.on('orders.insert', async (change) => {
// In neue Service-DB übernehmen
await orderServiceDB.orders.create(change.data);
});

// 3) Vollständige Trennung
┌──────────┐ ┌──────────┐ ┌──────────┐
│Users DB │ │Products │ │Orders DB
(MongoDB) │ │DB(MySQL)(Postgres)
└──────────┘ └──────────┘ └──────────┘

// ========== Phase 4: Schrittweises Traffic-Umschalten ==========

// Im API Gateway Verhältnis anpassen
const MIGRATION_PERCENTAGE = 10; // Nur 10% zu neuem Service

app.use('/api/orders', (req, res, next) => {
if (Math.random() * 100 < MIGRATION_PERCENTAGE) {
// Zu neuem Service
proxy('http://order-service/orders')(req, res, next);
} else {
// Zu Monolith
proxy('http://monolith/api/orders')(req, res, next);
}
});

// Schrittweise erhöhen
// 10% → 25% → 50% → 75% → 100%

// ========== Phase 5: Monitoring und Rollback vorbereiten ==========

const NEW_SERVICE_ERROR_THRESHOLD = 0.05; // 5% Fehlerrate

async function monitorNewService() {
const errorRate = await getErrorRate('order-service');

if (errorRate > NEW_SERVICE_ERROR_THRESHOLD) {
// Bei hoher Fehlerrate zurückrollen
console.error('Hohe Fehlerrate! Rollback zu Monolith');
MIGRATION_PERCENTAGE = 0;

// Benachrichtigen
await sendAlert('Migrations-Rollback erfolgt');
}
}

// ========== Praktische Checkliste ==========

Migrations-Checkliste:

1. Grenzen identifizieren
- Domain Driven Design (DDD)
- Nach Geschäftsfunktion aufteilen

2. Mit unabhängigster Funktion beginnen
- Geringe Abhängigkeiten
- Geringer geschäftlicher Einfluss
- Z.B.: Benachrichtigungen, Logging, Suche

3. API Gateway einführen
- Schrittweises Traffic-Umschalten

4. Datenbank-Trennungsstrategie
- Dual WriteCDCVollständige Trennung

5. Monitoring verstärken
- Fehlerrate, Antwortzeit, Traffic
- Rollback vorbereiten

6. Team-Schulung
- Microservices-Architektur
- DevOps-Tools (Docker, Kubernetes)

7. Dokumentation
- Service-Katalog
- API-Dokumentation
- Deployment-Leitfaden

🎓 Nächste Schritte

Nach dem Verständnis der Microservices-Architektur lernen Sie:

  1. Was ist Docker? (Dokument in Entwicklung) - Containerisierung
  2. Was ist CI/CD? - Automatisiertes Deployment
  3. REST API vs GraphQL - API-Design

Praxis

# ========== 1. Einfache Microservices-Praxis ==========

# Projektstruktur
mkdir microservices-demo
cd microservices-demo

# Services erstellen
mkdir -p services/{user,product,order}
mkdir gateway

# Docker Compose schreiben
cat > docker-compose.yml

# Ausführen
docker-compose up -d

# Logs prüfen
docker-compose logs -f

# ========== 2. Kubernetes-Deployment ==========

# minikube installieren (lokales K8s)
brew install minikube
minikube start

# Bereitstellen
kubectl apply -f kubernetes/

# Services prüfen
kubectl get pods
kubectl get services

# Logs prüfen
kubectl logs <pod-name>

# ========== 3. Istio installieren (Service Mesh) ==========

# Istio installieren
istioctl install --set profile=demo -y

# Sidecar in Services injizieren
kubectl label namespace default istio-injection=enabled

# Istio-Dashboard
istioctl dashboard kiali

🎬 Fazit

Microservices-Architektur bietet Skalierbarkeit und Flexibilität:

  • Unabhängigkeit: Unabhängige Entwicklung, Deployment und Skalierung pro Service
  • Technologische Vielfalt: Beste Technologie für jeden Service wählen
  • Fehler-Isolierung: Ein Service-Fehler betrifft nicht das Gesamtsystem
  • Team-Autonomie: Teams verantwortlich für Services

Aber die Komplexität steigt, wählen Sie also basierend auf Projektgröße und Team-Fähigkeiten! 🧩