Claude Code × マイクロサービス設計実践ガイド|DDD分割・Connect RPC・Turborepoモノレポ・Pact契約テスト

Claude Code × マイクロサービス設計実践ガイド|DDD分割・Connect RPC・Turborepoモノレポ・Pact契約テスト AI開発

マイクロサービスアーキテクチャは設計の選択肢が多く、サービス分割・通信プロトコル・共有型管理・テスト戦略のどれをとっても正解が一つではありません。Claude Codeはこの複雑な設計判断を支援する強力なパートナーです。DDDに基づくBounded Context分析、gRPCスキーマの自動生成、サービス間の契約テスト設計まで、アーキテクチャレベルの意思決定から実装まで一気通貫でカバーできます。

この記事では、TypeScript + Connect RPC(gRPC互換)+ Turborepo + Pactを組み合わせたマイクロサービスの設計・実装をClaude Codeで行う実践的なワークフローを解説します。

スポンサーリンク

サービス分割をClaude Codeに設計させる

マイクロサービスで最も重要な設計判断は「どこでサービスを分割するか」です。Claude CodeのPlan Modeを使って、コードに触れずにアーキテクチャ分析を行います。

Bounded Context分析プロンプト(Plan Mode)
# Plan Modeで起動(Shift+Tab 2回)
> このモノリスのソースコードを分析し、DDDのBounded Contextを特定してください。

分析手順:
1. ドメインモデルの洗い出し(エンティティ・値オブジェクト・集約)
2. ユビキタス言語の検出(同じ用語が異なる意味で使われている箇所)
3. コンテキスト境界の特定(データの所有権、トランザクション境界)
4. コンテキスト間の関係(ACL、共有カーネル、公開ホストサービス等)

出力:
- 各Bounded Contextの名前・責務・所有エンティティ
- コンテキスト間の依存関係図
- 段階的な分割順序の提案(ストラングラーフィグパターン)
分割の優先順位:変更頻度が高い・他モジュールとの結合度が低い・スケーリング要件が異なる——この3条件を満たすモジュールから順に分離するのが安全です。

ストラングラーフィグパターンでの段階的分割

一度にすべてを分割するのではなく、モノリスの前にファサードを配置し、機能単位で段階的に新サービスへ移行します。問題が発生すれば即座にモノリスにフォールバックできます。

ストラングラーフィグの段階的移行プロンプト
既存モノリスのコードベースを分析し、
ストラングラーフィグパターンで段階的に分割する計画を立ててください。

分割候補の評価基準:
- 変更頻度(git logから分析)
- 結合度(import/require の依存分析)
- スケーリング要件の違い

出力: 分割フェーズ(Phase 1〜3)とそれぞれのリスク・ロールバック手順

Plan Modeの詳しい使い方はClaude Code Plan Mode完全ガイドをご覧ください。

Connect RPC(gRPC互換)でサービス間通信を実装する

サービス間通信にはConnect RPC(gRPC互換のTypeScriptフレームワーク)を使います。.protoファイルから型安全なクライアント・サーバーコードを自動生成でき、HTTP/JSONとgRPCの両方をサポートします。

.protoファイルの定義

.proto生成プロンプト
注文サービスのgRPCスキーマ(.protoファイル)を定義してください。

エンドポイント:
- CreateOrder: 注文作成
- GetOrder: 注文取得
- ListOrders: 注文一覧(ページネーション付き)
- UpdateOrderStatus: ステータス更新

要件:
- proto3構文
- パッケージ: acme.order.v1
- ページネーション: cursor方式
packages/proto/order/v1/order.proto
syntax = "proto3";
package acme.order.v1;

message Order {
  string order_id = 1;
  string customer_id = 2;
  repeated OrderItem items = 3;
  OrderStatus status = 4;
  string created_at = 5;
}

message OrderItem {
  string product_id = 1;
  int32 quantity = 2;
  int64 price_cents = 3;
}

enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0;
  ORDER_STATUS_PENDING = 1;
  ORDER_STATUS_CONFIRMED = 2;
  ORDER_STATUS_SHIPPED = 3;
  ORDER_STATUS_CANCELLED = 4;
}

message CreateOrderRequest {
  string customer_id = 1;
  repeated OrderItem items = 2;
}

message CreateOrderResponse {
  Order order = 1;
}

message GetOrderRequest {
  string order_id = 1;
}

message ListOrdersRequest {
  string customer_id = 1;
  string cursor = 2;
  int32 limit = 3;
}

message ListOrdersResponse {
  repeated Order orders = 1;
  string next_cursor = 2;
}

service OrderService {
  rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc ListOrders(ListOrdersRequest) returns (ListOrdersResponse);
}

bufでTypeScriptコードを自動生成する

packages/proto/buf.gen.yaml(Connect-ES 2.0)
version: v2
inputs:
  - directory: .
plugins:
  - local: protoc-gen-es
    out: src/gen
    include_imports: true
    opt: target=ts
コード生成の実行
# buf でTypeScriptコードを生成
cd packages/proto
npx buf generate

# 生成されるファイル:
# src/gen/acme/order/v1/order_pb.ts  ← メッセージ型 + サービス定義
Connect-ES 2.0ではprotoc-gen-es1つでメッセージ定義とサービス定義の両方を生成します。旧来のprotoc-gen-connect-esは廃止されました。

サーバー実装(Fastify + Connect)

apps/order-service/src/index.ts
import { fastify } from "fastify";
import { fastifyConnectPlugin } from "@connectrpc/connect-fastify";
import type { ConnectRouter } from "@connectrpc/connect";
import { ConnectError, Code } from "@connectrpc/connect";
import { OrderService } from "@acme/proto/acme/order/v1/order_pb";
import { db } from "./db";

const routes = (router: ConnectRouter) =>
  router.service(OrderService, {
    async createOrder(req) {
      const orderId = crypto.randomUUID();
      const order = await db.insert("orders", {
        orderId,
        customerId: req.customerId,
        items: req.items,
        status: 1, // PENDING
        createdAt: new Date().toISOString(),
      });
      return { order };
    },

    async getOrder(req) {
      const order = await db.findById("orders", req.orderId);
      if (!order) throw new ConnectError("注文が見つかりません", Code.NotFound);
      return order;
    },

    async listOrders(req) {
      const { items, nextCursor } = await db.paginate("orders", {
        customerId: req.customerId,
        cursor: req.cursor,
        limit: req.limit || 20,
      });
      return { orders: items, nextCursor };
    },
  });

const server = fastify();
await server.register(fastifyConnectPlugin, { routes });
await server.listen({ host: "0.0.0.0", port: 50052 });
console.log("Order service running on :50052");

クライアント実装(他サービスからの呼び出し)

apps/payment-service/src/clients/order-client.ts
import { createClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-node";
import { OrderService } from "@acme/proto/acme/order/v1/order_pb";

const transport = createConnectTransport({
  baseUrl: process.env.ORDER_SERVICE_URL || "http://localhost:50052",
  httpVersion: "1.1",
});

export const orderClient = createClient(OrderService, transport);

// 使用例: 型安全にRPCを呼び出し
// const { order } = await orderClient.createOrder({
//   customerId: "cust-123",
//   items: [{ productId: "prod-456", quantity: 2, priceCents: 1500n }],
// });
通信パターン gRPC(Connect) REST
サービス間通信 最適(型安全・高速) OK
ブラウザ直接通信 Connect Protocolで可能 最適
ストリーミング ネイティブ対応 SSE/WebSocket別途
外部公開API 非推奨 最適
使い分け:サービス間はgRPC(Connect)、外部公開はREST + OpenAPI。この「二刀流」がマイクロサービスの標準パターンです。API開発の詳細はClaude Code × API開発自動化ガイドをご覧ください。

Turborepoモノレポでマイクロサービスを管理する

ディレクトリ構成
microservices-platform/
├── CLAUDE.md                    # システム全体のルール
├── turbo.json
├── pnpm-workspace.yaml
├── apps/
│   ├── user-service/            # 認証・ユーザー管理
│   │   ├── CLAUDE.md            # サービス固有ルール
│   │   ├── Dockerfile
│   │   └── src/
│   ├── order-service/           # 注文処理
│   │   ├── CLAUDE.md
│   │   ├── Dockerfile
│   │   └── src/
│   └── payment-service/         # 決済処理
│       ├── CLAUDE.md
│       ├── Dockerfile
│       └── src/
├── packages/
│   ├── proto/                   # 共有.protoファイル + 生成TypeScript
│   └── shared/                  # 共有ユーティリティ(ロガー、エラーハンドリング等)
└── infra/
    └── docker-compose.yml
モノレポの詳しい設計はClaude Code × モノレポ完全ガイドをご覧ください。CLAUDE.mdの階層設計・クロスパッケージ保護フック・CI/CD連携を解説しています。

Docker Composeでローカル開発環境を構築する

infra/docker-compose.yml
services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: dev
      POSTGRES_DB: platform
    ports: ["5432:5432"]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U dev"]
      interval: 5s
      timeout: 3s
      retries: 5

  user-service:
    build:
      context: ../
      dockerfile: apps/user-service/Dockerfile
    ports: ["50051:50051"]
    environment:
      DATABASE_URL: postgres://dev:dev@postgres:5432/platform
    depends_on:
      postgres:
        condition: service_healthy

  order-service:
    build:
      context: ../
      dockerfile: apps/order-service/Dockerfile
    ports: ["50052:50052"]
    environment:
      DATABASE_URL: postgres://dev:dev@postgres:5432/platform
      USER_SERVICE_URL: http://user-service:50051
    depends_on:
      user-service:
        condition: service_healthy
depends_oncondition: service_healthyを必ず指定してください。デフォルト(service_started)ではコンテナの起動のみでプロセスの準備完了を待ちません。ヘルスチェックなしだとサービスの起動順序が保証されません。

CLAUDE.mdテンプレート(マイクロサービス用)

CLAUDE.md(ルート)
# Platform - マイクロサービスアーキテクチャ

## サービス一覧
| サービス | ポート | 責務 |
|---------|--------|------|
| user-service | 50051 | 認証・ユーザー管理 |
| order-service | 50052 | 注文処理 |
| payment-service | 50053 | 決済処理 |

## 設計原則
- Database per Service: 各サービスが専用スキーマを持つ
- APIファースト: .protoファイルを先に定義し、実装は後
- サービス間通信: Connect RPC(gRPC互換)

## 禁止事項
- サービス間のDB直接参照
- 循環依存(A→B→A)
- 共有ライブラリへのビジネスロジック配置

## コマンド
- pnpm dev: 全サービス起動
- pnpm generate: proto → TypeScript生成
- pnpm test:contract: Pact契約テスト実行
- docker compose -f infra/docker-compose.yml up: Docker環境起動
apps/order-service/CLAUDE.md
# Order Service
## 責務: 注文の作成・更新・キャンセル
## Bounded Context: Ordering

## 依存サービス
- user-service: 注文者情報の参照
- payment-service: 決済処理の依頼

## DB: PostgreSQL (orderスキーマ)
主要テーブル: orders, order_items

## 作業スコープ
- 変更可能: apps/order-service/ のみ
- packages/proto/ の変更が必要な場合は確認を求める

Pactでサービス間の契約テストを自動化する

マイクロサービスでは、あるサービスのAPIを変更したとき、依存するサービスが壊れないことを保証する必要があります。Pact(Consumer-Driven Contract Testing)は、消費者側がテストでコントラクトを生成し、提供者側がそれを検証する仕組みです。

契約テスト生成プロンプト
PaymentServiceからOrderServiceへの契約テストを
Pact V4 + Vitest で実装してください。

テストケース:
1. 注文IDで注文を取得できる(正常系)
2. 存在しない注文IDで404エラー(異常系)

Consumer: PaymentService
Provider: OrderService
Consumer側テスト(PaymentServiceが書く)
import { describe, it, expect } from "vitest";
import { PactV4, SpecificationVersion } from "@pact-foundation/pact";

const provider = new PactV4({
  consumer: "PaymentService",
  provider: "OrderService",
  spec: SpecificationVersion.SPECIFICATION_VERSION_V4,
  dir: "./pacts",
});

describe("OrderService Contract", () => {
  it("注文詳細を取得できる", async () => {
    await provider
      .addInteraction()
      .given("注文ID order-123 が存在する")
      .uponReceiving("注文詳細の取得リクエスト")
      .withRequest("GET", "/orders/order-123")
      .willRespondWith(200, (builder) => {
        builder.jsonBody({
          orderId: "order-123",
          status: "CONFIRMED",
          totalAmount: 5000,
        });
      })
      .executeTest(async (mockserver) => {
        const res = await fetch(`${mockserver.url}/orders/order-123`);
        const order = await res.json();
        expect(order.orderId).toBe("order-123");
        expect(order.status).toBe("CONFIRMED");
      });
  });
});
gRPC(Connect Protocol)の場合、Connect ProtocolがHTTP/JSON互換のため、REST用のPactテストがそのまま利用できます。またbuf breaking --against .git#branch=mainで.protoの破壊的変更も検出できます。

テスト戦略全般はClaude Codeテスト完全ガイドをご覧ください。

よくある質問

Qマイクロサービスとモノリスのどちらを選ぶべきですか?
Aチームが5人以下・ドメインが明確に分離されていない場合はモノリスから始めてください。マイクロサービスは分散システムの複雑さ(ネットワーク障害、データ整合性、デプロイ管理)を伴うため、明確なスケーリング要件やチーム分割の必要性がない限りオーバーエンジニアリングになります。モノリスで始め、ボトルネックが特定されてからストラングラーフィグパターンで段階的に分割するのが安全です。
QgRPCとRESTのどちらをサービス間通信に使うべきですか?
ATypeScriptモノレポ内のサービス間通信にはConnect RPC(gRPC互換)が最適です。.protoファイルから型安全なクライアント・サーバーコードが自動生成され、スキーマの破壊的変更もbuf breakingで検出できます。外部に公開するAPIはREST + OpenAPIが適しています。両方を併用する「二刀流」がマイクロサービスの標準パターンです。
QDatabase per Serviceは本当に必要ですか?
Aマイクロサービスの独立性を保つためには原則として必要です。DB共有を許すとスキーマ変更が全サービスに影響し、独立デプロイのメリットが失われます。ただし、サービス数が少ない(3つ以下)初期段階では、同一DBの別スキーマで始めて後から分離する方法もあります。
QClaude Codeに複数サービスを同時に操作させるには?
Aサービスのディレクトリから起動して--add-dirで他サービスへのアクセスを追加します。例: cd apps/order-service && claude --add-dir ../payment-service --add-dir ../../packages/proto。CLAUDE.mdには作業スコープを明記して、他サービスへの意図しない変更を防ぎましょう。
Q契約テスト(Pact)と統合テストの違いは何ですか?
A統合テストはすべてのサービスを起動して実際に通信させるのに対し、契約テストは消費者と提供者を個別にテストします。Pactでは消費者が「期待するレスポンス」をコントラクトファイルに記録し、提供者がそのコントラクトを満たすか検証します。全サービスの起動が不要なため高速に実行でき、CIでのフィードバックループが短くなります。

まとめ

  • サービス分割: Plan ModeでDDD分析→Bounded Context特定→ストラングラーフィグで段階的分割
  • Connect RPC: .protoファイルから型安全なTypeScriptクライアント・サーバーを自動生成(Connect-ES 2.0 + buf)
  • モノレポ構成: Turborepo + packages/protoで共有型管理。CLAUDE.md階層設計でサービス単位のスコープを明示
  • Docker Compose: depends_on: condition: service_healthyでサービス起動順序を保証
  • 契約テスト: Pact V4で消費者駆動のコントラクト検証。buf breakingで.proto互換性チェック

モノレポの詳細はClaude Code × モノレポ完全ガイド、API開発はClaude Code × API開発自動化ガイド、IaCはClaude Code × Terraform完全ガイドもあわせてご覧ください。