Microservices mit Claude Code: Grenzen, APIs, Compose und Tests
Microservices mit Claude Code: Grenzen, API-Verträge, Compose, Tests, Observability und Rollout.
Microservices sind ein Architekturansatz, bei dem eine große Anwendung in kleine unabhängige Services aufgeteilt wird, die über APIs oder Events zusammenarbeiten. Claude Code hilft dabei nur dann wirklich, wenn es nicht bloß Ordner erzeugt, sondern Servicegrenzen, API-Verträge, Datenhoheit, lokale Compose-Umgebung, Gateway, Observability, Tests und Rollout gemeinsam prüft.
Der wichtigste Punkt: Microservices sind kein automatischer Performancegewinn. Nach der Aufteilung entstehen neue Probleme wie Netzwerkfehler, API-Kompatibilität, verteilte Transaktionen, Log-Korrelation und Rollback-Reihenfolgen. Wenn das Produkt noch täglich seine Domäne ändert, ist ein modularer Monolith oft die bessere erste Stufe. Für Grundlagen passen auch API-Entwicklung mit Claude Code, Docker Compose, Logging und Monitoring und Event-driven Architecture.
Nutze offizielle Referenzen als Review-Anker: Anthropic Claude Code overview, Docker Compose documentation und OpenAPI Specification 3.1. So bleibt klar, was Claude Code vorschlägt und was dein Team wirklich betreiben will.
Erst Grenzen, Dann Code
Ein guter Prompt zwingt Claude Code, über fachliche Fähigkeiten statt über Tabellen nachzudenken.
Du bist Architektur-Reviewer für die Aufteilung einer E-Commerce-Anwendung in Microservices.
Kontext:
- Der Bestellprozess ändert sich häufig.
- Lagerbestand muss wegen Warehouse-Integration separat deployt werden.
- Payment und Notifications dürfen den Katalog nicht blockieren.
Liefere:
1. Servicekandidaten und Verantwortlichkeiten.
2. Daten, die jeder Service besitzt.
3. Synchrone APIs und asynchrone Events.
4. Funktionen, die vorerst im Monolith bleiben sollten.
5. Minimaler erster Sprint mit Docker Compose.
Regeln:
- Keine gemeinsam genutzten Datenbanktabellen.
- Keine internen Tabellennamen in APIs.
- Keine Geschäftslogik im Gateway.
Als Referenz eignen sich Microsofts Microservices architecture guide, die AKS microservices reference architecture und der Überblick zu API Management gateways.
Vertrag Und Datenhoheit
Der API-Vertrag kommt vor dem Handler-Code.
openapi: 3.1.0
info:
title: Order Service API
version: 1.0.0
paths:
/orders:
post:
summary: Create an order after reserving inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [customerId, items]
properties:
customerId:
type: string
items:
type: array
minItems: 1
items:
type: object
required: [sku, quantity]
properties:
sku:
type: string
quantity:
type: integer
minimum: 1
responses:
"201":
description: Order accepted
"409":
description: Inventory could not be reserved
| Service | Besitzt | Darf aufrufen | Darf nicht |
|---|---|---|---|
| gateway | keine Fachdaten | order, inventory | Bestand oder Rabatte berechnen |
| order-service | orders, order_items | inventory API, order-events | inventory-Tabellen lesen |
| inventory-service | stock, reservations | zunächst nichts | orders-Tabellen lesen |
| notification-service | delivery logs | order-events | Bestellstatus ändern |
Wenn eine UI Bestell- und Bestandsdaten zusammen braucht, ist ein Cross-Database-Join trotzdem die falsche Abkürzung. Nutze API-Komposition, Read Models, Suchindex oder Event-getriebene Caches.
Für Reviews lohnt sich ein kleines service-inventory.json im Repository. Menschen entscheiden die Grenze; Claude Code prüft, ob eine Änderung gegen diese Grenze verstößt.
{
"services": [
{
"name": "gateway",
"owns": [],
"mayCall": ["order-service", "inventory-service"],
"mustNot": ["store business data", "calculate discounts"]
},
{
"name": "order-service",
"owns": ["orders", "order_items"],
"mayCall": ["inventory-service"],
"mustNot": ["read inventory tables directly"]
},
{
"name": "inventory-service",
"owns": ["stock", "reservations"],
"mayCall": [],
"mustNot": ["change order status"]
}
],
"releaseRules": [
"no shared database tables",
"public APIs hide internal table names",
"every service has healthcheck, logs, tests, and rollback notes"
]
}
Minimal Laufendes Beispiel
mkdir microservices-demo
cd microservices-demo
mkdir services
npm init -y
npm pkg set type=module
npm install express zod pino redis undici
compose.yaml:
services:
gateway:
image: node:22-alpine
working_dir: /workspace
command: node services/service.mjs
environment:
SERVICE: gateway
PORT: 3000
ORDER_URL: http://order-service:3000
INVENTORY_URL: http://inventory-service:3000
ports:
- "8080:3000"
volumes:
- .:/workspace
depends_on:
- order-service
- inventory-service
order-service:
image: node:22-alpine
working_dir: /workspace
command: node services/service.mjs
environment:
SERVICE: order
PORT: 3000
INVENTORY_URL: http://inventory-service:3000
REDIS_URL: redis://redis:6379
volumes:
- .:/workspace
depends_on:
redis:
condition: service_healthy
inventory-service:
condition: service_started
inventory-service:
image: node:22-alpine
working_dir: /workspace
command: node services/service.mjs
environment:
SERVICE: inventory
PORT: 3000
volumes:
- .:/workspace
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
services/service.mjs:
import express from "express";
import pino from "pino";
import { createClient } from "redis";
import { request } from "undici";
import { z } from "zod";
import { randomUUID } from "node:crypto";
const service = process.env.SERVICE ?? "inventory";
const port = Number(process.env.PORT ?? 3000);
const log = pino({ name: service });
function base(name) {
const app = express();
app.use(express.json());
app.use((req, res, next) => {
req.requestId = req.header("x-request-id") ?? randomUUID();
res.setHeader("x-request-id", req.requestId);
next();
});
app.get("/health", (_req, res) => res.json({ ok: true, service: name }));
return app;
}
function inventory() {
const app = base("inventory");
const stock = new Map([["sku-1", 5], ["sku-2", 2]]);
const Reserve = z.object({ sku: z.string().min(1), quantity: z.number().int().positive() });
app.get("/inventory/:sku", (req, res) => res.json({ sku: req.params.sku, quantity: stock.get(req.params.sku) ?? 0 }));
app.post("/inventory/reservations", (req, res) => {
const parsed = Reserve.safeParse(req.body);
if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
const available = stock.get(parsed.data.sku) ?? 0;
if (available < parsed.data.quantity) return res.status(409).json({ error: "insufficient_stock", available });
stock.set(parsed.data.sku, available - parsed.data.quantity);
log.info({ requestId: req.requestId, sku: parsed.data.sku }, "reserved");
res.status(201).json({ sku: parsed.data.sku, remaining: stock.get(parsed.data.sku) });
});
app.listen(port, () => log.info({ port }, "inventory started"));
}
async function order() {
const app = base("order");
const redis = createClient({ url: process.env.REDIS_URL ?? "redis://localhost:6379" });
await redis.connect();
const Order = z.object({
customerId: z.string().min(1),
items: z.array(z.object({ sku: z.string().min(1), quantity: z.number().int().positive() })).min(1),
});
app.post("/orders", async (req, res) => {
const parsed = Order.safeParse(req.body);
if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
for (const item of parsed.data.items) {
const response = await request(`${process.env.INVENTORY_URL}/inventory/reservations`, {
method: "POST",
headers: { "content-type": "application/json", "x-request-id": req.requestId },
body: JSON.stringify(item),
});
if (response.statusCode >= 400) return res.status(response.statusCode).json(await response.body.json());
}
const order = { id: randomUUID(), ...parsed.data, status: "accepted" };
await redis.xAdd("order-events", "*", { type: "OrderAccepted", payload: JSON.stringify(order) });
res.status(201).json(order);
});
app.listen(port, () => log.info({ port }, "order started"));
}
function gateway() {
const app = base("gateway");
async function forward(req, res, url) {
const response = await request(url, {
method: req.method,
headers: { "content-type": "application/json", "x-request-id": req.requestId },
body: req.method === "GET" ? undefined : JSON.stringify(req.body),
});
res.status(response.statusCode).send(await response.body.text());
}
app.post("/orders", (req, res) => forward(req, res, `${process.env.ORDER_URL}/orders`));
app.get("/inventory/:sku", (req, res) => forward(req, res, `${process.env.INVENTORY_URL}/inventory/${encodeURIComponent(req.params.sku)}`));
app.listen(port, () => log.info({ port }, "gateway started"));
}
if (service === "inventory") inventory();
else if (service === "order") await order();
else if (service === "gateway") gateway();
docker compose up
curl http://localhost:8080/inventory/sku-1
curl -X POST http://localhost:8080/orders -H "content-type: application/json" -d '{"customerId":"cust-1","items":[{"sku":"sku-1","quantity":2}]}'
docker compose down
Kriterien Vor Dem Go Live
Zaehle vor dem Start nicht die Anzahl der Services, sondern die nachweisbaren Verantwortlichkeiten. Jeder Service braucht Datenhoheit, einen stabilen API-Vertrag, Contract-Tests, Healthcheck, Logs und einen Rollback-Pfad. Lass Claude Code einen konkreten Business-Flow in diese Pruefliste zerlegen.
Observability, Tests Und Rollout
Mindestens erforderlich sind x-request-id, strukturierte Logs, Servicename, fachliche IDs, /health, Fehlerrate und Latenz. Ergänze einen kleinen Contract-Test für die Order-Anfrage, bevor du Integrationstests ausbaust. Claude Code sollte vor dem Rollout API-Kompatibilität, Migration Ownership, Gateway-Logik, 400/409/500-Pfade, Redis-Ausfall, Feature Flags, Canary und Rollback prüfen.
Gute Anwendungsfälle sind E-Commerce mit Order, Inventory, Payment und Notification, B2B-SaaS mit Billing, Berechtigungen und Audit Logs sowie Content-Plattformen mit Ingestion, Transformation, Suche und Auslieferung. Typische Fallen sind Servicegrenzen nach Tabellen, Shared Database, zu große Shared Libraries, Geschäftslogik im Gateway und ein Release ohne Observability.
Ein guter use case ist eine fachliche Fähigkeit, die sich anders ändert, skaliert oder ausfällt als der Rest des Produkts. Der typische pitfall ist dagegen, viele Service-Ordner zu erzeugen, ohne Ownership, Datenhoheit und Rollback zu klären. Lass Claude Code pro Kandidat eine kurze Service-Karte schreiben: Owner, API-Vertrag, besessene Tabellen, erzeugte Events, konsumierte Events, Dashboard, SLO, Deployment-Befehl, Rollback-Befehl und Verhalten bei Ausfall einer Abhängigkeit.
Wenn du wiederverwendbare CLAUDE.md-Bausteine, Review-Checklisten und API-Vertragsvorlagen brauchst, kannst du mit den ClaudeCodeLab Praxis-Templates starten.
Für eine konkrete Review von Servicegrenzen, API-Verträgen, Compose-Setup und Monitoring kann dein Team über Claude Code training and consultation mit einem einzelnen Workflow starten.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude-Code-Permission-Receipt: Scope, Beweis und Rollback festhalten
Permission-Receipt für Claude Code: erlaubte Aktionen, Freigabegrenzen, Prüfbefehle, Rollback und Umsatz-CTA-Prüfung.
Sicheres Agent Harness fur Claude Code und Codex: Rechte, Prufung und Rollback
Ein praktisches Agent Harness fur Claude Code und Codex mit Policy, Plan, Verifikation und Recovery.
Claude Code Subagents: Praxisleitfaden für sichere Agent-Delegation
Claude Code Subagents praktisch nutzen: Artikel- und Codearbeit sicher aufteilen, Prompts einsetzen, Fehler vermeiden.