Microservices introduce distributed systems complexity: network failures, partial outages, data consistency across services, service discovery, and observability that spans multiple processes. Claude Code handles microservice development well because it generates patterns that account for this complexity — circuit breakers, idempotent operations, and event-driven consistency rather than synchronous distributed transactions.
This guide covers microservice development with Claude Code: service design, inter-service communication, resilience patterns, and event-driven architecture.
Service Boundary Design
Before writing code, design is critical:
I'm splitting a monolith into microservices.
Current modules: users, orders, payments, inventory, notifications.
Where should the service boundaries be?
What should NOT be split?
Claude provides bounded context analysis:
- Users service: authentication, profiles, preferences — low change rate, shared across all domains
- Orders service + Inventory service: keep together initially — they share data and have high coupling; splitting too early causes distributed transactions
- Payments service: should be isolated — regulatory compliance, audit requirements, different SLA (99.99%)
- Notifications service: separate — async, different scaling pattern, failure doesn’t affect core flow
The key insight Claude provides: “Split by business capability and change frequency, not by technical layer. If two ‘services’ always change together, they’re one service.”
REST Communication Between Services
The Orders service needs to check inventory before confirming an order.
Write the HTTP client code with: timeout, retry with backoff,
and circuit breaker.
import { CircuitBreaker } from 'opossum';
class InventoryClient {
private circuitBreaker: CircuitBreaker;
constructor(private baseUrl: string) {
const request = async (productId: string, quantity: number) => {
const response = await fetch(`${baseUrl}/api/inventory/${productId}/reserve`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ quantity }),
signal: AbortSignal.timeout(2000), // 2s timeout
});
if (!response.ok) {
throw new Error(`Inventory service error: ${response.status}`);
}
return response.json();
};
this.circuitBreaker = new CircuitBreaker(request, {
timeout: 2000, // Fail if request > 2s
errorThresholdPercentage: 50, // Open circuit at 50% failure rate
resetTimeout: 30000, // Try again after 30s
volumeThreshold: 10, // Minimum 10 requests before tracking
});
this.circuitBreaker.fallback(() => {
throw new ServiceUnavailableError('Inventory service is unavailable');
});
}
async reserveStock(productId: string, quantity: number): Promise<ReservationResult> {
return this.circuitBreaker.fire(productId, quantity);
}
}
The circuit breaker prevents cascading failures: if Inventory is down, Orders stops waiting 2s per request and immediately throws, so the Orders service stays responsive. After 30 seconds, it half-opens to test if Inventory recovered.
Service Registry and Discovery
How do my services find each other in Kubernetes?
Service A needs to call Service B. How should I configure this?
Claude explains Kubernetes DNS resolution (no service registry needed in k8s — services resolve via http://service-name.namespace.svc.cluster.local) and generates the client configuration:
const INVENTORY_URL = process.env.INVENTORY_SERVICE_URL
?? 'http://inventory.production.svc.cluster.local';
Environment variable for flexibility (local dev points to localhost, production points to k8s DNS), no hardcoded internal URLs.
gRPC for Internal Services
The Payment service makes thousands of calls per second to the
Fraud Detection service. HTTP/JSON has too much overhead.
Set up gRPC communication.
// fraud.proto
syntax = "proto3";
package fraud;
service FraudDetection {
rpc CheckTransaction (TransactionRequest) returns (FraudCheckResult);
rpc BatchCheck (stream TransactionRequest) returns (stream FraudCheckResult);
}
message TransactionRequest {
string transaction_id = 1;
string user_id = 2;
int64 amount_cents = 3;
string merchant_id = 4;
string country_code = 5;
}
message FraudCheckResult {
string transaction_id = 1;
bool is_fraud = 2;
float risk_score = 3;
string reason_code = 4;
}
// Generated TypeScript client
import { FraudDetectionClient } from './generated/fraud_grpc_pb';
import * as grpc from '@grpc/grpc-js';
const client = new FraudDetectionClient(
process.env.FRAUD_SERVICE_ADDR ?? 'fraud-detection:50051',
grpc.credentials.createInsecure(), // Use SSL in production
);
async function checkFraud(transaction: TransactionRequest): Promise<FraudCheckResult> {
return new Promise((resolve, reject) => {
client.checkTransaction(transaction, (err, response) => {
if (err) reject(err);
else resolve(response);
});
});
}
gRPC advantages: binary protocol (smaller payload), streaming support, strongly typed contracts from Proto files, multiplexed over HTTP/2.
Event-Driven Architecture
When an order is completed, multiple services need to react:
- Inventory service decrements stock
- Notification service sends confirmation email
- Analytics service records the conversion
Without creating coupling between these services.
// Orders service publishes an event
interface OrderCompletedEvent {
eventType: 'order.completed';
orderId: string;
userId: string;
totalCents: number;
items: OrderItem[];
timestamp: string;
}
// After completing an order:
await eventBus.publish('orders', {
eventType: 'order.completed',
orderId: order.id,
userId: order.userId,
totalCents: order.totalCents,
items: order.items,
timestamp: new Date().toISOString(),
});
Each downstream service subscribes independently:
// Inventory service (separate codebase)
eventBus.subscribe('orders', async (event: OrderCompletedEvent) => {
if (event.eventType !== 'order.completed') return;
for (const item of event.items) {
await inventory.decrementStock(item.productId, item.quantity);
}
});
// Notifications service (separate codebase)
eventBus.subscribe('orders', async (event: OrderCompletedEvent) => {
if (event.eventType !== 'order.completed') return;
await email.sendOrderConfirmation(event.userId, event.orderId);
});
Order service doesn’t know Inventory or Notifications exist — it publishes to a topic, any number of consumers can subscribe without changing Orders.
Idempotency for Event Processing
Network issues can delivery messages twice.
How do I ensure my event handler doesn't process the same order twice?
async function processOrderCompleted(event: OrderCompletedEvent): Promise<void> {
// Check if we already processed this event
const idempotencyKey = `order_completed:${event.orderId}`;
const alreadyProcessed = await redis.set(
idempotencyKey,
'1',
'EX', 86400, // Expire after 24h
'NX', // Only set if NOT exists
);
if (!alreadyProcessed) {
// Already processed — skip
return;
}
// Process the event
await decrementStock(event.items);
}
SET key value NX (only set if not exists) is atomic — safe under concurrent delivery. The 24-hour expiry prevents the idempotency key store from growing unboundedly.
Distributed Transactions (Saga Pattern)
An order requires: reserve inventory, charge payment,
confirm order. All three must succeed or none should.
How do I handle failure without distributed transactions?
Claude implements the Saga pattern:
class CreateOrderSaga {
async execute(orderId: string, items: OrderItem[], paymentMethod: string) {
const compensations: Array<() => Promise<void>> = [];
try {
// Step 1: Reserve inventory
const reservation = await inventoryService.reserve(items);
compensations.push(() => inventoryService.cancelReservation(reservation.id));
// Step 2: Charge payment
const charge = await paymentService.charge(paymentMethod, totalCents);
compensations.push(() => paymentService.refund(charge.id));
// Step 3: Confirm order
await orderService.confirm(orderId, { reservationId: reservation.id, chargeId: charge.id });
} catch (error) {
// Run compensating transactions in reverse order
for (const compensate of compensations.reverse()) {
await compensate().catch(err => {
// Log compensation failure but continue — all compensations must run
logger.error('Saga compensation failed', { error: err });
});
}
throw error;
}
}
}
Each successful step registers its compensation (undo) action. On failure, compensations run in reverse order. This avoids distributed locks and two-phase commit.
Health Checks and Readiness
Implement health and readiness endpoints for Kubernetes.
Health: is the service alive?
Readiness: can the service handle traffic? (DB connected, dependencies reachable)
app.get('/health/live', (req, res) => {
// Liveness: just check the process is running and not deadlocked
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
app.get('/health/ready', async (req, res) => {
const checks = await Promise.allSettled([
db.query('SELECT 1'), // Database reachable
redis.ping(), // Cache reachable
inventoryClient.healthCheck(), // Dependency reachable
]);
const results = {
database: checks[0].status === 'fulfilled',
cache: checks[1].status === 'fulfilled',
inventoryService: checks[2].status === 'fulfilled',
};
const ready = Object.values(results).every(Boolean);
res.status(ready ? 200 : 503).json({
status: ready ? 'ready' : 'not_ready',
checks: results,
});
});
Kubernetes uses /health/live to decide whether to restart the pod and /health/ready to decide whether to route traffic to it. Separating them prevents restart loops when a dependency is temporarily down.
Observability Across Services
For tracing requests across service boundaries, see the observability guide — OpenTelemetry’s trace context propagation (W3C trace context headers) automatically connects spans across services into a single distributed trace.
The Kubernetes guide covers the deployment side: resource limits, horizontal scaling, and traffic routing for microservice deployments. For containerizing each service for local development, the Docker guide covers Docker Compose for running multiple services together.
For production-ready microservice patterns — Kafka event streaming, service mesh configuration with Istio, and distributed caching patterns — the Claude Skills 360 bundle includes microservices skill sets. Start with the free tier to try the circuit breaker and event-driven patterns.