Microservices dengan Claude Code: boundary, API, Compose, dan testing
Microservices dengan Claude Code: boundary, kontrak API, Compose, test, observability, dan rollout.
Microservices adalah desain yang memecah aplikasi besar menjadi layanan kecil yang independen dan saling terhubung lewat API atau event. Claude Code berguna jika dipakai untuk meninjau service boundary, kontrak API, kepemilikan database, Docker Compose lokal, gateway, observability, testing, dan rollout checklist sekaligus.
Namun microservices bukan jalan pintas otomatis. Setelah sistem dipecah, ada biaya baru: kegagalan jaringan, kompatibilitas API, transaksi terdistribusi, korelasi log, dan rollback yang lebih rumit. Kalau domain produk masih berubah setiap hari, modular monolith sering lebih aman. Fondasi terkait bisa dibaca di Claude Code API development, Docker Compose, logging dan monitoring, serta event-driven architecture.
Gunakan referensi resmi sebagai jangkar review: Anthropic Claude Code overview, Docker Compose documentation, dan OpenAPI Specification 3.1. Ini membantu memisahkan saran Claude Code dari kontrak yang benar-benar akan dioperasikan.
Mulai Dari Boundary
Prompt awal sebaiknya meminta alasan pemisahan, bukan hanya daftar service.
Anda adalah reviewer arsitektur untuk memecah aplikasi e-commerce menjadi microservices.
Konteks:
- Flow order sering berubah.
- Inventory harus bisa dirilis terpisah karena integrasi gudang.
- Payment dan notification tidak boleh memblokir katalog.
Keluarkan:
1. Kandidat service dan tanggung jawabnya.
2. Data yang dimiliki setiap service.
3. Interaksi synchronous API.
4. Interaksi asynchronous event.
5. Minimum scope untuk sprint pertama dengan Docker Compose.
Aturan:
- Tidak ada shared database table.
- Nama tabel internal tidak muncul di API.
- Gateway tidak berisi business logic.
Rujukan resmi yang relevan: Microservices architecture guide, AKS microservices reference architecture, dan API Management gateway overview.
Kontrak API Dan Kepemilikan Data
Tulis kontrak sebelum 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
| Service | Memiliki | Boleh memanggil | Tidak boleh |
|---|---|---|---|
| gateway | tidak ada data bisnis | order, inventory | menghitung stok atau diskon |
| order-service | orders, order_items | inventory API, order-events | membaca tabel inventory |
| inventory-service | stock, reservations | belum ada | membaca tabel orders |
| notification-service | delivery logs | order-events | mengubah status order |
Jika satu layar butuh order dan stok, jangan JOIN lintas database service. Gunakan API composition, read model, search index, atau cache yang diisi oleh event.
Untuk review, simpan service-inventory.json kecil di repositori. Manusia menentukan boundary; Claude Code mengecek apakah perubahan melanggar boundary itu.
{
"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"
]
}
Contoh Lokal Yang Bisa Dijalankan
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();
Jalankan:
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
Kriteria Sebelum Go Live
Sebelum rilis, jangan hanya menghitung jumlah service. Pastikan setiap service punya pemilik data, kontrak API, contract test, endpoint health, structured logs, dan jalur rollback. Minta Claude Code mengubah satu alur bisnis menjadi checklist dan menandai bagian yang bisa di-retry atau harus ditangani manusia.
Observability, Testing, Dan Rollout
Sejak sprint pertama, sertakan x-request-id, structured logs, nama service, ID bisnis, /health, error rate, dan latency. Minta Claude Code mengecek kompatibilitas API, ownership migration, gateway yang bersih dari business logic, jalur 400/409/500, kegagalan Redis, feature flags, canary, dan rollback.
Use case yang cocok: e-commerce dengan order, inventory, payment, notification; B2B SaaS dengan billing, permission, audit log; platform konten dengan ingestion, transformasi, search, dan delivery. Pitfall umum: membagi berdasarkan tabel, shared database, shared domain library yang terlalu besar, business rule di gateway, dan rilis tanpa observability.
Kalau butuh snippet CLAUDE.md, checklist review, dan template kontrak API yang bisa dipakai ulang, mulai dari produk praktis ClaudeCodeLab.
Jika ingin meninjau boundary, kontrak API, Compose, dan monitoring pada proyek nyata, mulai dari Claude Code training and consultation dengan satu workflow bisnis dulu.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Permission receipt Claude Code: mencatat scope, bukti, dan rollback
Pola permission receipt untuk Claude Code: aksi yang diizinkan, batas approval, command verifikasi, rollback, dan cek CTA revenue.
Agent Harness Aman untuk Claude Code dan Codex: Permission, Verifikasi, dan Rollback
Rancang Agent Harness praktis untuk Claude Code dan Codex dengan policy, plan, verification, dan recovery layer.
Subagent Claude Code: panduan praktis untuk delegasi artikel dan kode
Panduan subagent Claude Code untuk membagi pekerjaan artikel dan kode: aturan delegasi, prompt, risiko, dan checklist.