Microservicios con Claude Code: límites, APIs, Compose y pruebas
Diseña microservicios con Claude Code: límites, contratos API, datos, Docker Compose, observabilidad, tests y rollout.
Los microservicios son una forma de diseñar una aplicación grande como servicios pequeños e independientes que colaboran mediante APIs o eventos. Claude Code ayuda cuando se usa para revisar límites de servicio, contratos API, propiedad de bases de datos, entorno local con Docker Compose, gateway, observabilidad, pruebas y checklist de despliegue.
No conviene venderlos como una mejora automática. Al dividir un sistema aparecen fallos de red, compatibilidad de APIs, consistencia eventual, correlación de logs y rollbacks más difíciles. Si el dominio todavía cambia cada semana, un monolito modular suele ser mejor punto de partida. Para completar la base, revisa desarrollo de APIs con Claude Code, Docker Compose, logging y monitoring y arquitectura orientada a eventos.
Usa referencias oficiales como anclas de revisión: Anthropic Claude Code overview, Docker Compose documentation y OpenAPI Specification 3.1. Así separas las sugerencias de Claude Code del contrato que realmente vas a operar.
Primero Los Límites
Pide a Claude Code que explique la frontera de cada servicio antes de escribir código.
Actúa como revisor de arquitectura para migrar un e-commerce a microservicios.
Contexto:
- El flujo de pedidos cambia con frecuencia.
- Inventario debe evolucionar separado por integración con almacenes.
- Pago y notificaciones no deben bloquear el catálogo.
Entrega:
1. Servicios candidatos y responsabilidades.
2. Datos que posee cada servicio.
3. APIs síncronas y eventos asíncronos.
4. Funciones que deben quedarse en el monolito por ahora.
5. Primer sprint mínimo con Docker Compose.
Reglas:
- Sin tablas compartidas.
- Sin nombres internos de tablas en APIs.
- Sin lógica de negocio en el gateway.
Como referencias oficiales, usa Microservices architecture guide, AKS microservices reference architecture y API Management gateway overview.
Contrato API Y Propiedad De Datos
El contrato debe existir antes del handler.
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
| Servicio | Posee | Puede llamar | No debe hacer |
|---|---|---|---|
| gateway | ningún dato de negocio | order, inventory | calcular stock o descuentos |
| order-service | orders, order_items | inventory API, order-events | leer tablas de inventario |
| inventory-service | stock, reservations | ninguno al inicio | leer tablas de pedidos |
| notification-service | delivery logs | order-events | cambiar estado de pedidos |
Si una pantalla necesita pedidos e inventario juntos, no hagas JOIN entre bases de servicios. Usa composición de APIs, modelos de lectura, índices de búsqueda o caché alimentada por eventos.
Para las revisiones, guarda un service-inventory.json pequeño en el repositorio. Las personas deciden los límites; Claude Code verifica si un cambio los rompe.
{
"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"
]
}
Ejemplo Local Ejecutable
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
Criterios Antes De Produccion
Antes de lanzar, no cuentes servicios: cuenta responsabilidades verificables. Cada microservicio debe tener propietario de datos, contrato API versionado, prueba de contrato, endpoint de salud y plan de rollback. Pide a Claude Code convertir un flujo de negocio en una tabla con estos puntos.
Observabilidad, Pruebas Y Rollout
Incluye x-request-id, logs estructurados, nombre de servicio, IDs de negocio, /health, tasa de errores y latencia desde el primer sprint. Pide a Claude Code revisar compatibilidad API, ownership de migraciones, ausencia de lógica de negocio en gateway, rutas 400/409/500, caída de Redis, feature flags, canary y rollback.
Buenos casos de uso: e-commerce con pedidos, inventario, pagos y notificaciones; SaaS B2B con facturación, permisos y auditoría; plataformas de contenido con ingesta, transformación, búsqueda y entrega. Errores típicos: dividir por tablas, compartir base de datos, crear una librería de dominio común enorme, meter reglas en el gateway y desplegar sin observabilidad.
En términos prácticos, un buen use case no es “queremos arquitectura moderna”, sino “esta capacidad cambia, escala o falla de forma distinta al resto del producto”. Un pitfall frecuente es generar muchos repositorios sin ownership real. Pide a Claude Code una ficha por servicio candidato: propietario, contrato API, tablas que posee, eventos publicados, eventos consumidos, dashboard, SLO, comando de deploy, comando de rollback y comportamiento cuando una dependencia falla.
También conviene incluir la ruta de negocio. Si el servicio toca checkout, alta de trial, emails, anuncios, reportes o formularios de consulta, el checklist debe decir cómo se valida esa conversión. Una división técnicamente limpia que rompe atribución, notificaciones o seguimiento de ingresos sigue siendo mala ingeniería. Para el primer PR, suele ser mejor crear inventario y pruebas de contrato que abrir diez carpetas nuevas.
Si necesitas snippets de CLAUDE.md, checklists de review y plantillas de contrato API para reutilizar, empieza por los productos prácticos de ClaudeCodeLab.
Para revisar límites, contratos API, Compose y monitoreo en un proyecto real, puedes empezar con Claude Code training and consultation sobre un único flujo de negocio.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Permission receipt para Claude Code: alcance, prueba y rollback
Patrón de permission receipt para Claude Code: acciones permitidas, aprobación, pruebas, rollback y CTA de ingresos.
Agent Harness seguro para Claude Code y Codex: permisos, verificacion y rollback
Diseña un Agent Harness seguro para Claude Code y Codex con permisos, plan, verificaciones y rollback.
Subagentes de Claude Code: guía práctica para delegar trabajo de forma segura
Guía práctica de subagentes en Claude Code para dividir artículos y código: reglas, prompts, riesgos y checklist.