Microservices-Architekturen versprechen Skalierbarkeit, unabhängige Deployments und technologische Flexibilität. Doch mit diesen Vorteilen kommen komplexe Kommunikations- und Konsistenzprobleme. Bewährte Patterns wie API Gateway, Circuit Breaker, Saga und Distributed Tracing lösen diese Herausforderungen systematisch. Claude Code beschleunigt die Implementierung dieser Patterns erheblich — von der Boilerplate bis zum produktionsreifen Code.
Voraussetzungen: Node.js 20+, TypeScript 5.x, Grundkenntnisse in Microservices-Konzepten. Die Beispiele nutzen NestJS, Express und relevante npm-Packages.
1. API Gateway Pattern — Single Entry Point
Das API Gateway ist der zentrale Eintrittspunkt für alle Client-Anfragen. Es übernimmt Authentifizierung, Rate Limiting und Routing zu den Downstream-Services.
Das API Gateway Pattern bündelt alle externen Anfragen an einem zentralen Punkt. Clients sprechen nicht mehr direkt mit einzelnen Services — stattdessen validiert das Gateway Tokens, limitiert Request-Raten und leitet Anfragen weiter.
Reverse Proxy mit http-proxy-middleware
// gateway/src/main.ts
import express from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';
import rateLimit from 'express-rate-limit';
import { verifyJWT } from './middleware/auth';
const app = express();
// Rate Limiting: 100 Requests / 15 Minuten
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
message: { error: 'Too many requests', retryAfter: 900 },
});
const serviceRoutes: Record<string, string> = {
'/api/users': 'http://user-service:3001',
'/api/orders': 'http://order-service:3002',
'/api/payments': 'http://payment-service:3003',
'/api/catalog': 'http://catalog-service:3004',
};
// Auth Middleware
app.use('/api', limiter, verifyJWT);
// Dynamisches Routing
for (const [path, target] of Object.entries(serviceRoutes)) {
app.use(path, createProxyMiddleware({
target,
changeOrigin: true,
pathRewrite: { [`^${path}`]: '' },
on: {
error: (err, req, res) => {
console.error(`Proxy error for ${path}:`, err.message);
(res as any).status(502).json({ error: 'Service unavailable' });
},
},
}));
}
app.listen(8080, () => console.log('API Gateway running on :8080'));
JWT Auth-Middleware
// gateway/src/middleware/auth.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
interface JwtPayload {
sub: string;
roles: string[];
iat: number;
}
export const verifyJWT = (req: Request, res: Response, next: NextFunction) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) return res.status(401).json({ error: 'No token provided' });
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!) as JwtPayload;
req.headers['x-user-id'] = payload.sub;
req.headers['x-user-roles'] = payload.roles.join(',');
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
};
Claude Code generiert auf Basis des Routing-Schemas automatisch passende Error-Handler, Request-Logging mit Correlation-IDs und Health-Check-Endpoints für jeden Downstream-Service.
2. Service Discovery — Dynamische Service-Lokalisierung
Service Discovery löst das Problem der dynamischen Service-Lokalisierung in Container-Umgebungen wo IP-Adressen sich ständig ändern.
In Kubernetes-Umgebungen übernimmt das interne DNS die Service Discovery. Für Cloud-agnostische Setups bietet Consul eine mächtige Alternative mit Health Checks und Key-Value-Store.
NestJS Microservices mit TCP Transport
// user-service/src/main.ts
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options: {
host: '0.0.0.0',
port: 3001,
},
},
);
await app.listen();
console.log('User Service listening on TCP :3001');
}
bootstrap();
Consul Health Check Registration
// shared/consul-register.ts
import Consul from 'consul';
const consul = new Consul({ host: process.env.CONSUL_HOST ?? 'consul' });
export async function registerService(name: string, port: number) {
const serviceId = `${name}-${process.env.POD_NAME ?? 'local'}`;
await consul.agent.service.register({
id: serviceId,
name,
port,
check: {
http: `http://${process.env.POD_IP}:${port}/health`,
interval: '10s',
timeout: '3s',
deregistercriticalserviceafter: '30s',
},
tags: ['nodejs', 'v2', name],
});
console.log(`Registered: ${serviceId} at port ${port}`);
// Deregister on shutdown
process.on('SIGTERM', async () => {
await consul.agent.service.deregister(serviceId);
process.exit(0);
});
}
export async function discoverService(name: string): Promise<string> {
const result = await consul.health.service({ service: name, passing: true });
if (!result.length) throw new Error(`No healthy instances of ${name}`);
// Round-Robin Load Balancing
const idx = Math.floor(Math.random() * result.length);
const { Address, Port } = result[idx].Service;
return `http://${Address}:${Port}`;
}
In Kubernetes-Setups reicht der Service-DNS user-service.production.svc.cluster.local — Consul ist dort optional, bietet aber erweiterte Health-Check-Strategien und Multi-Datacenter-Support.
3. Circuit Breaker — Resiliente Service-Kommunikation
Der Circuit Breaker verhindert Cascade Failures: Wenn ein Downstream-Service ausfällt, wird der Schaltkreis geöffnet und Fallback-Logik greift sofort.
Das Circuit Breaker Pattern schützt Microservices vor Cascade Failures. Die drei Zustände — Closed (normal), Open (Fehler-Modus), Half-Open (Probe) — steuern automatisch, ob Requests durchgelassen werden.
opossum Circuit Breaker Implementation
// shared/circuit-breaker.ts
import CircuitBreaker from 'opossum';
import axios from 'axios';
interface BreakerOptions {
timeout: number;
errorThreshold: number;
resetTimeout: number;
volumeThreshold: number;
}
const defaultOptions: BreakerOptions = {
timeout: 3000, // 3s Timeout
errorThreshold: 50, // 50% Fehlerrate → öffnen
resetTimeout: 30000, // 30s → Half-Open Probe
volumeThreshold: 5, // Mindestens 5 Requests
};
function createBreaker(
fn: (...args: any[]) => Promise<any>,
opts: Partial<BreakerOptions> = {}
) {
const breaker = new CircuitBreaker(fn, { ...defaultOptions, ...opts });
breaker.on('open', () => console.warn('⚡ Circuit OPEN — downstream unhealthy'));
breaker.on('halfOpen', () => console.info('🔁 Circuit HALF-OPEN — probing...'));
breaker.on('close', () => console.info('✅ Circuit CLOSED — service recovered'));
breaker.on('fallback', (result) => console.info('Fallback used:', result));
return breaker;
}
// Payment Service mit Circuit Breaker
const callPaymentService = async (orderId: string, amount: number) =>
axios.post('http://payment-service:3003/charge', { orderId, amount });
export const paymentBreaker = createBreaker(callPaymentService, {
timeout: 5000,
errorThreshold: 40,
});
// Fallback: In Queue stellen statt direkt ablehnen
paymentBreaker.fallback(async (orderId, amount) => {
await enqueueForRetry({ orderId, amount, queuedAt: Date.now() });
return { status: 'queued', message: 'Payment queued for retry' };
});
// Metrics für Monitoring
export function getBreakerStats() {
return {
state: paymentBreaker.opened ? 'open' : paymentBreaker.halfOpen ? 'half-open' : 'closed',
stats: paymentBreaker.stats,
};
}
Breaker in Express-Route
// order-service/src/routes/orders.ts
import { Router } from 'express';
import { paymentBreaker, getBreakerStats } from '../../shared/circuit-breaker';
const router = Router();
router.post('/checkout', async (req, res) => {
const { orderId, amount } = req.body;
try {
const result = await paymentBreaker.fire(orderId, amount);
res.json(result.data ?? result);
} catch (err: any) {
res.status(503).json({ error: err.message, breaker: getBreakerStats() });
}
});
router.get('/breaker-stats', (_, res) => res.json(getBreakerStats()));
export default router;
4. Saga Pattern — Verteilte Transaktionen
Das Saga Pattern ersetzt ACID-Transaktionen über Service-Grenzen durch eine Sequenz lokaler Transaktionen mit kompensierende Rollback-Steps.
ACID-Transaktionen sind über Service-Grenzen nicht möglich. Das Saga Pattern definiert eine Sequenz lokaler Transaktionen. Schlägt ein Schritt fehl, werden Compensating Transactions rückwärts ausgeführt.
Orchestration-basierte Saga (mit Temporal-ähnlichem Ansatz)
// sagas/order-saga.ts
interface SagaStep<T> {
name: string;
execute: (ctx: T) => Promise<Partial<T>>;
compensate: (ctx: T) => Promise<void>;
}
async function runSaga<T>(steps: SagaStep<T>[], initial: T): Promise<T> {
let ctx = { ...initial };
const completed: SagaStep<T>[] = [];
for (const step of steps) {
try {
console.log(`Saga: executing ${step.name}`);
const patch = await step.execute(ctx);
ctx = { ...ctx, ...patch };
completed.push(step);
} catch (err) {
console.error(`Saga failed at ${step.name}, compensating...`);
for (const done of completed.reverse()) {
await done.compensate(ctx).catch(console.error);
}
throw err;
}
}
return ctx;
}
// Order Checkout Saga
interface OrderContext {
orderId: string;
userId: string;
amount: number;
paymentId?: string;
shipmentId?: string;
}
const orderCheckoutSteps: SagaStep<OrderContext>[] = [
{
name: 'reserveInventory',
execute: async (ctx) => { await inventoryService.reserve(ctx.orderId); return {}; },
compensate: async (ctx) => { await inventoryService.release(ctx.orderId); },
},
{
name: 'chargePayment',
execute: async (ctx) => {
const { paymentId } = await paymentService.charge(ctx.userId, ctx.amount);
return { paymentId };
},
compensate: async (ctx) => {
if (ctx.paymentId) await paymentService.refund(ctx.paymentId);
},
},
{
name: 'createShipment',
execute: async (ctx) => {
const { shipmentId } = await shipmentService.create(ctx.orderId);
return { shipmentId };
},
compensate: async (ctx) => {
if (ctx.shipmentId) await shipmentService.cancel(ctx.shipmentId);
},
},
];
// Aufruf
const result = await runSaga(orderCheckoutSteps, { orderId: 'ord-123', userId: 'usr-456', amount: 99.90 });
Claude Code kann auf Basis dieses generischen Saga-Frameworks automatisch spezifische Sagas für neue Business-Flows generieren — inklusive Idempotenz-Checks und Retry-Logik für nicht-idempotente externe Calls.
5. Event Sourcing und CQRS
Event Sourcing speichert den Zustand als unveränderliche Sequenz von Events statt als aktuellen Snapshot — perfekt für Audit Trails und zeitbasierte Queries.
Statt den aktuellen Zustand zu speichern, werden alle Domain Events sequenziell im Event Store abgelegt. Der aktuelle Zustand ergibt sich durch das Replay aller Events. CQRS trennt dabei Schreib- (Command) und Lesepfade (Query).
Event Store mit EventEmitter2
// event-sourcing/event-store.ts
import { EventEmitter2 } from 'eventemitter2';
interface DomainEvent {
id: string;
aggregateId: string;
type: string;
payload: Record<string, any>;
version: number;
occurredAt: Date;
}
export class EventStore {
private events: DomainEvent[] = [];
private emitter = new EventEmitter2({ wildcard: true });
async append(event: Omit<DomainEvent, 'id' | 'occurredAt'>) {
const stored: DomainEvent = {
...event,
id: crypto.randomUUID(),
occurredAt: new Date(),
};
this.events.push(stored);
// Publish für Projektionen (Read-Side)
this.emitter.emit(`${event.type}`, stored);
return stored;
}
getByAggregate(aggregateId: string): DomainEvent[] {
return this.events
.filter(e => e.aggregateId === aggregateId)
.sort((a, b) => a.version - b.version);
}
on(eventType: string, handler: (e: DomainEvent) => void) {
this.emitter.on(eventType, handler);
}
}
// Order Aggregate
export class OrderAggregate {
id!: string;
status!: string;
total!: number;
private version = 0;
static fromEvents(events: DomainEvent[]): OrderAggregate {
const agg = new OrderAggregate();
for (const event of events) agg.apply(event);
return agg;
}
private apply(event: DomainEvent) {
this.version = event.version;
switch (event.type) {
case 'OrderCreated':
this.id = event.aggregateId;
this.status = 'pending';
this.total = event.payload.total;
break;
case 'OrderPaid':
this.status = 'paid';
break;
case 'OrderShipped':
this.status = 'shipped';
break;
}
}
}
CQRS Command & Query Handler
// cqrs/handlers.ts
import { EventStore, OrderAggregate } from './event-store';
const store = new EventStore();
// COMMAND: Schreibpfad
export async function handleCreateOrder(cmd: { orderId: string; total: number }) {
await store.append({
aggregateId: cmd.orderId,
type: 'OrderCreated',
payload: { total: cmd.total },
version: 1,
});
}
// QUERY: Lesepfad — Aggregate rekonstruieren
export function queryOrder(orderId: string): OrderAggregate {
const events = store.getByAggregate(orderId);
if (!events.length) throw new Error(`Order ${orderId} not found`);
return OrderAggregate.fromEvents(events);
}
// Projektion: Read-Model für schnelle Queries aufbauen
store.on('OrderCreated', (event) => {
readModelCache.set(event.aggregateId, {
id: event.aggregateId,
status: 'pending',
total: event.payload.total,
});
});
Für Produktionssysteme empfiehlt sich Apache Kafka oder RabbitMQ als persistenter Event Store statt dem In-Memory-Array. Claude Code generiert die vollständige Kafka-Integration inklusive Consumer Groups, Offset Management und Dead Letter Topics.
6. Distributed Tracing mit OpenTelemetry
Distributed Tracing macht den Weg eines Requests durch alle Microservices sichtbar — essentiell für Performance-Analyse und Fehlerdiagnose in verteilten Systemen.
OpenTelemetry ist der De-facto-Standard für Distributed Tracing. Jeder Service instrumentiert seine Operationen mit Spans — zusammen ergeben sie einen Trace, der den kompletten Request-Weg visualisiert.
OpenTelemetry Setup mit Jaeger Export
// tracing/setup.ts — MUSS VOR allen Imports geladen werden!
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { Resource } from '@opentelemetry/resources';
import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
const sdk = new NodeSDK({
resource: new Resource({
[SEMRESATTRS_SERVICE_NAME]: process.env.SERVICE_NAME ?? 'unknown-service',
}),
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? 'http://jaeger:4318/v1/traces',
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
console.log('OpenTelemetry tracing initialized');
Manuelle Spans und Context Propagation
// tracing/tracer.ts
import { trace, context, propagation, SpanStatusCode } from '@opentelemetry/api';
import { W3CTraceContextPropagator } from '@opentelemetry/core';
import axios from 'axios';
const tracer = trace.getTracer(process.env.SERVICE_NAME ?? 'service');
propagation.setGlobalPropagator(new W3CTraceContextPropagator());
// Manueller Span für kritische Operationen
export async function tracedOperation<T>(
name: string,
fn: () => Promise<T>,
attributes: Record<string, string | number> = {}
): Promise<T> {
return tracer.startActiveSpan(name, async (span) => {
span.setAttributes(attributes);
try {
const result = await fn();
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (err: any) {
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
span.recordException(err);
throw err;
} finally {
span.end();
}
});
}
// TraceId via HTTP-Header propagieren (W3C traceparent)
export async function httpWithTrace(url: string, data?: any) {
const headers: Record<string, string> = {};
propagation.inject(context.active(), headers);
return axios.post(url, data, { headers });
}
// Express Middleware: TraceId aus Header extrahieren
export const traceMiddleware = (req: any, res: any, next: any) => {
const extracted = propagation.extract(context.active(), req.headers);
context.with(extracted, next);
};
Jaeger Docker Compose
# docker-compose.tracing.yml
services:
jaeger:
image: jaegertracing/all-in-one:1.55
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
environment:
COLLECTOR_OTLP_ENABLED: "true"
restart: unless-stopped
Die Jaeger UI auf Port 16686 zeigt Flame Graphs aller Service-Calls — Latenz-Bottlenecks und fehlerhafte Spans sind auf einen Blick erkennbar. Claude Code kann die vollständige OTel-Instrumentierung für alle bestehenden Express/Fastify-Routes automatisch nachziehen.
Microservices mit Claude Code beschleunigen
Claude Code generiert produktionsreife Microservice-Patterns in Minuten — von API Gateway bis Distributed Tracing. Starte jetzt deinen kostenlosen Trial.
Kostenlosen Trial starten →Fazit: Patterns als Fundament
Microservices-Patterns sind kein Nice-to-have — sie sind das Fundament skalierbarer, resilienter Architekturen. API Gateway zentralisiert Querschnittsthemen, Circuit Breaker verhindert Cascade Failures, Saga löst verteilte Transaktionen, Event Sourcing schafft lückenlosen Audit Trail, und Distributed Tracing macht das System beobachtbar.
Claude Code beschleunigt die Implementierung dieser Patterns erheblich: TypeScript-Interfaces, Fehlerbehandlung, Tests und Konfiguration entstehen in einem Bruchteil der Zeit. Der Fokus bleibt auf der Architektur — die Boilerplate übernimmt die KI.