Microservices com Claude Code: limites, APIs, Compose e testes
Guia de microservices com Claude Code: limites, contratos API, dados, Docker Compose, observabilidade, testes e rollout.
Microservices são uma forma de dividir uma aplicação grande em serviços pequenos e independentes que se comunicam por APIs ou eventos. Claude Code ajuda quando é usado para revisar limites de serviço, contratos de API, propriedade de dados, ambiente local com Docker Compose, gateway, observabilidade, testes e plano de rollout.
O cuidado é não transformar isso em complexidade gratuita. Ao dividir o sistema, surgem falhas de rede, compatibilidade de APIs, consistência eventual, correlação de logs e rollback mais difícil. Se o domínio ainda muda toda semana, um monólito modular pode ser a escolha mais produtiva. Para bases complementares, veja desenvolvimento de APIs com Claude Code, Docker Compose, logging e monitoring e arquitetura orientada a eventos.
Use referências oficiais como âncoras de revisão: Anthropic Claude Code overview, Docker Compose documentation e OpenAPI Specification 3.1. Isso separa sugestões do Claude Code do contrato que você realmente vai operar.
Comece Pelos Limites
Peça ao Claude Code para justificar a separação antes de gerar código.
Você é revisor de arquitetura para migrar um e-commerce para microservices.
Contexto:
- O fluxo de pedidos muda com frequência.
- Inventário precisa evoluir separado por causa da integração com armazém.
- Pagamento e notificações não devem bloquear o catálogo.
Entregue:
1. Serviços candidatos e responsabilidades.
2. Dados possuídos por cada serviço.
3. APIs síncronas e eventos assíncronos.
4. O que deve ficar no monólito por enquanto.
5. Primeiro sprint mínimo com Docker Compose.
Regras:
- Sem tabelas compartilhadas.
- Sem nomes internos de tabelas nas APIs.
- Sem regra de negócio no gateway.
Use como referência o Microservices architecture guide, a AKS microservices reference architecture e o API Management gateway overview.
Contrato API E Propriedade Dos Dados
Defina o contrato antes dos handlers.
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
| Serviço | Possui | Pode chamar | Não deve fazer |
|---|---|---|---|
| gateway | nenhum dado de negócio | order, inventory | calcular estoque ou descontos |
| order-service | orders, order_items | inventory API, order-events | ler tabelas de inventário |
| inventory-service | stock, reservations | nada no início | ler tabelas de pedidos |
| notification-service | delivery logs | order-events | alterar estado de pedidos |
Quando a tela precisar de pedido e estoque juntos, evite JOIN entre bancos de serviços. Prefira composição de APIs, modelo de leitura, índice de busca ou cache alimentado por eventos.
Para revisões, mantenha um pequeno service-inventory.json no repositório. Pessoas decidem os limites; Claude Code verifica se uma mudança os viola.
{
"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"
]
}
Exemplo Local Executável
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
Critérios Antes Do Go Live
Antes de publicar, não conte apenas quantos serviços existem. Verifique se cada serviço tem dono dos dados, contrato API, teste de contrato, healthcheck, logs estruturados e caminho de rollback. Peça ao Claude Code para transformar um fluxo de negócio em checklist.
Observabilidade, Testes E Rollout
Inclua x-request-id, logs estruturados, nome do serviço, IDs de negócio, /health, taxa de erro e latência desde o início. Peça ao Claude Code para revisar compatibilidade de API, ownership de migrações, ausência de regra de negócio no gateway, caminhos 400/409/500, falha do Redis, feature flags, canary e rollback.
Bons casos de uso: e-commerce com pedidos, estoque, pagamento e notificações; SaaS B2B com billing, permissões e auditoria; plataformas de conteúdo com ingestão, transformação, busca e entrega. Armadilhas comuns: dividir por tabela, compartilhar banco, criar biblioteca de domínio compartilhada demais, colocar regra no gateway e lançar sem observabilidade.
Na prática, um bom use case não é “queremos microservices modernos”, mas “esta capacidade muda, escala ou falha de forma diferente do resto do produto”. O pitfall clássico é criar diretórios de serviço sem ownership real. Peça ao Claude Code uma ficha curta por candidato: dono, contrato API, tabelas possuídas, eventos publicados, eventos consumidos, dashboard, SLO, comando de deploy, comando de rollback e comportamento quando uma dependência cai.
Inclua também o caminho de negócio. Se o serviço afeta checkout, trial, e-mails, anúncios, relatórios ou formulários de consulta, o checklist deve dizer como essa conversão será validada. Uma separação tecnicamente bonita que quebra atribuição, notificação ou medição de receita continua sendo uma má decisão. No primeiro PR, normalmente vale mais criar inventário e teste de contrato do que gerar muitos serviços vazios.
Se você precisa reutilizar snippets de CLAUDE.md, checklists de review e templates de contrato API, comece pelos produtos práticos do ClaudeCodeLab.
Para revisar limites, contratos API, Compose e monitoramento em um projeto real, comece com Claude Code training and consultation em um único fluxo de negócio.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Permission receipt no Claude Code: escopo, prova e rollback
Padrão de permission receipt para Claude Code: ações permitidas, limites de aprovação, comandos de prova, rollback e CTA de receita.
Agent Harness seguro para Claude Code e Codex: permissoes, verificacao e rollback
Monte uma base segura para agentes com Claude Code e Codex usando politicas, plano, verificacao e recuperacao.
Subagentes no Claude Code: guia prático para delegar trabalho com segurança
Guia prático de subagentes no Claude Code para dividir artigos e código: regras, prompts, riscos e checklist.