Chuyển đến nội dung chính

レッスン 22: 契約のテスト — Pact と API の互換性

Pact による消費者主導の契約テストマイクロサービスにとって契約テストが重要な理由。プロバイダーの検証。パクトブローカー。スキーマの進化と下位互換性。 CI/CD パイプラインへの統合。

🏗️ アーキテクチャ — レッスン 22 レッスン 22: 契約のテスト — Pact と API 互換性

マイクロサービスとマイクロ フロントエンドのシステム設計 — 基本から運用まで

パート 7: フルスタック マイクロサービスとマイクロ フロントエンドのテスト

xdev.asia

はじめに

20 個のサービスが相互に通信する場合、サービス A の API を変更してもサービス B が中断されないようにするにはどうすればよいでしょうか? Contract Testing は、すべてのサービスを同時に実行することなく、この問題を解決します。


1. 問題: API の重大な変更

Service A (Consumer) gọi Service B (Provider):

Service B thay đổi response:
Before: { "price": 999 }         ← number
After:  { "price": "999.00" }    ← string!

→ Service A parse Price as number → crash!
→ Integration test phát hiện? Chỉ nếu chạy cả A + B cùng lúc
→ E2E test? Chậm, flaky, expensive
→ Contract test? ✅ Phát hiện sớm, CI tốc độ cao

2. 消費者主導の契約テスト

2.1 概念

Consumer (Order Service):
"Tôi expect response từ Product Service có format:
 { id: string, name: string, price: number }"
→ Ghi thành Pact (contract file)

Provider (Product Service):
"Để tôi verify rằng API của tôi satisfy contract này"
→ Run Pact verification against real API

Nếu fail → Provider biết thay đổi sẽ break Consumer
→ Fix API hoặc coordinate migration

2.2 フロー

┌──────────────┐  1. Generate    ┌──────────────┐
│   Consumer   │────Pact File───►│  Pact Broker │
│ (Order Svc)  │    (contract)   │  (central    │
└──────────────┘                 │   registry)  │
                                 └──────┬───────┘
                                        │
                                 2. Verify│
                                        ▼
                                 ┌──────────────┐
                                 │   Provider   │
                                 │ (Product Svc)│
                                 └──────────────┘

3. 協定の履行

3.1 消費者側 (注文サービス)

// order-service/tests/pact/product.consumer.test.js
const { PactV3 } = require('@pact-foundation/pact');

describe('Product Service Contract', () => {
  const provider = new PactV3({
    consumer: 'OrderService',
    provider: 'ProductService',
  });
  
  it('get product by id', async () => {
    // Define expected interaction
    provider
      .given('product 123 exists')
      .uponReceiving('a request for product 123')
      .withRequest({
        method: 'GET',
        path: '/api/products/123',
      })
      .willRespondWith({
        status: 200,
        body: {
          id: '123',
          name: like('Laptop'),
          price: like(999),
          inStock: like(true),
        },
      });
    
    // Execute test against Pact mock
    await provider.executeTest(async (mockService) => {
      const product = await fetchProduct(mockService.url, '123');
      expect(product.id).toBe('123');
      expect(typeof product.price).toBe('number');
    });
  });
});

3.2 プロバイダーの検証 (製品サービス)

// product-service/tests/pact/provider.verify.test.js
const { Verifier } = require('@pact-foundation/pact');

describe('Pact Verification', () => {
  it('validates OrderService contract', async () => {
    const verifier = new Verifier({
      providerBaseUrl: 'http://localhost:8080',
      pactBrokerUrl: 'https://pact-broker.company.com',
      provider: 'ProductService',
      providerVersion: process.env.GIT_SHA,
      publishVerificationResult: true,
      stateHandlers: {
        'product 123 exists': async () => {
          await seedDB({ id: '123', name: 'Laptop', price: 999 });
        },
      },
    });
    
    await verifier.verifyProvider();
  });
});

4. CI/CD の統合

# Consumer CI (Order Service)
consumer-contract-test:
  steps:
    - run: npm test -- --testPathPattern=pact
    - run: npx pact-broker publish pacts/ 
        --consumer-app-version=$GIT_SHA
        --branch=$BRANCH

# Provider CI (Product Service)  
provider-contract-verify:
  steps:
    - run: npm run pact:verify
    # can-i-deploy check before deploymentt
    - run: npx pact-broker can-i-deploy
        --pacticipant=ProductService
        --version=$GIT_SHA
        --to-environment=production

4.1 デプロイできるか

Before deploying Product Service v2.1:

$ pact-broker can-i-deploy \
    --pacticipant ProductService \
    --version 2.1 \
    --to production

✅ OrderService (1.5) → ProductService (2.1): VERIFIED
✅ CartService (2.0) → ProductService (2.1): VERIFIED
Result: Safe to deploy!

5. スキーマ進化のベストプラクティス

タイプの変更壊れる?戦略
フィールドを追加いいえ追加するだけです (消費者は不明を無視します)
フィールドを削除するはい非推奨にする → 消費者が使用していないことを確認する → 削除
フィールドの名前を変更はい新しい追加 → コンシューマの移行 → 古い
タイプの変更はい新しいフィールド → 移行 → 古いフィールドを削除
エンドポイントを追加いいえ
エンドポイントを削除はい契約テストで使用状況を把握

概要

  • コントラクト テスト = サービス間の API コントラクトを検証する
  • 消費者主導: 消費者が期待を定義し、プロバイダーが検証します
  • Pact: 業界標準ツール、多くの言語をサポート
  • Pact Broker: 中央レジストリ + 安全性チェックを導入可能
  • CI/CD: すべての PR で実行され、契約が違反した場合はデプロイをブロックします
  • 統合テスト前に重大な変更をキャッチします

次の記事: レッスン 23: モノリポジトリとマルチリポジトリ — ソース コードの構成