プロデューサー側の契約を伴うコンシューマー主導契約 (CDC) のステップバイステップガイド

不正行為の検出とローン発行プロセスの例を考えてみましょう。ビジネスシナリオでは、人々に融資を実行したいが、人々が私たちからお金を盗むことは望んでいません。私たちのシステムの現在の導入では、誰でも融資が受けられます。

Loan Issuance が Fraud Detection サーバーのクライアントであると仮定します。現在のスプリントでは、新しい機能を開発する必要があります。クライアントがあまりにも多くのお金を借りたい場合、そのクライアントを詐欺師としてマークします。

技術的な解説

  • 不正検出には artifact-id または http-server があります。

  • ローン発行には artifact-id または http-client があります。

  • どちらも group-id または com.example を持ちます。

  • この例では、Stub Storage は Nexus/Artifactory です。

社会的発言

  • クライアント開発チームとサーバー開発チームは両方とも直接コミュニケーションをとり、プロセスの進行中に変更について話し合う必要があります。

  • CDC はコミュニケーションがすべてです。

サーバー側のコードは Spring Cloud Contract サンプル [GitHub] (英語) リポジトリの samples/standalone/dsl/http-server パスで入手でき、クライアント側のコードは Spring Cloud Contract のリポジトリの samples/standalone/dsl/http-client パスで入手できます。

この場合、プロデューサーが契約を所有します。物理的には、すべての契約はプロデューサーのリポジトリにあります。

テクニカルノート

重要: すべてのコードは、Spring Cloud Contract Samples リポジトリ [GitHub] (英語) で入手できます。

わかりやすくするために、次の頭字語を使用します。

  • 融資の発行 (LI): HTTP クライアント

  • 不正行為の検出 (FD): HTTP サーバー

  • SCC: Spring Cloud Contract

コンシューマー側 (融資の発行)

ローン発行サービスの開発者 (Fraud Detection サーバーのコンシューマー) は、次の手順を実行できます。

  1. 機能のテストを作成して TDD を開始します。

  2. 不足している実装を記述します。

  3. Fraud Detection サービスリポジトリのクローンをローカルに作成します。

  4. 不正検出サービスのリポジトリでローカルに契約を定義します。

  5. Spring Cloud Contract (SCC) プラグインを追加します。

  6. 統合テストを実行します。

  7. プルリクエストを提出します。

  8. 初期実装を作成します。

  9. プルリクエストを引き継ぎます。

  10. 不足している実装を記述します。

  11. アプリケーションをデプロイします。

  12. オンラインで作業します。

次の UML 図に示されているローン発行フローから始めます。

getting-started-cdc-client

機能のテストを作成して TDD を開始する

次のリストは、融資額が大きすぎるかどうかを確認するために使用できるテストを示しています。

新しい機能のテストを作成したと仮定します。高額の融資申請を受け取った場合、システムは何らかの説明を付けてその融資申請を拒否する必要があります。

不足している実装を書き込む

ある時点で、不正検出サービスにリクエストを送信する必要があります。クライアントの ID とクライアントが借りたい金額を含むリクエストを送信する必要があるとします。PUT メソッドを使用して、/fraudcheck URL に送信したいと考えています。これを行うには、次のようなコードを使用します。

わかりやすくするために、Fraud Detection サービスのポートは 8080 に設定され、アプリケーションは 8090 で実行されます。

この時点でテストを開始すると、現在ポート 8080 でサービスが実行されていないため、テストは中断されます。

Fraud Detection サービスリポジトリをローカルにクローン作成する

サーバー側の契約を試してみることから始めることができます。これを行うには、まず次のコマンドを実行してクローンを作成する必要があります。

$ git clone https://your-git-server.com/server-side.git local-http-server-repo

不正検出サービスのリポジトリでローカルに契約を定義する

コンシューマーとして、何を達成したいのかを正確に定義する必要があります。自分の期待を明確にする必要があります。そのためには、次の契約を作成します。

契約書を src/test/resources/contracts/fraud フォルダーに置きます。fraud フォルダーは、プロデューサーのテスト基本クラス名がそのフォルダーを参照するため重要です。

次の例は、Groovy と YAML の両方での契約を示しています。

YML 契約は非常に簡単です。ただし、静的に型付けされた Groovy DSL で作成された契約を見ると、value(client(…​), server(…​)) の部分が何なのか疑問に思うかもしれません。この表記法を使用することにより、Spring Cloud Contract では、JSON ブロック、URL、その他の動的構造の一部を定義できます。識別子またはタイムスタンプの場合、値をハードコードする必要はありません。いくつかの異なる範囲の値を許可したいとします。値の範囲を有効にするには、コンシューマー側でそれらの値に一致する正規表現を設定します。本文は、マップ表記または補間を含む文字列のいずれかを使用して提供できます。地図表記を使用することを強くお勧めします。

契約を設定するには、マップの表記を理解する必要があります。JSON に関する Groovy ドキュメント (英語) を参照してください。

前に示した契約は、次のような双方間の合意です。

  • HTTP リクエストが次のすべてとともに送信された場合:

    • /fraudcheck エンドポイント上の PUT メソッド

    • 正規表現 [0-9]{10} および loanAmount が 99999 に一致する client.id を含む JSON 本文

    • application/vnd.fraud.v1+json の値を持つ Content-Type ヘッダー

  • 次に、HTTP レスポンスがコンシューマーに送信されます。

    • ステータスは 200 です

    • FRAUD の値を含む fraudCheckStatus フィールドと Amount too high の値を持つ rejectionReason フィールドを含む JSON 本文が含まれます

    • application/vnd.fraud.v1+json の値を持つ Content-Type ヘッダーがあります

統合テストで API を実際に確認する準備ができたら、スタブをローカルにインストールする必要があります。

Spring Cloud Contract 検証プラグインを追加する

Maven または Gradle プラグインを追加できます。この例では、Maven を追加する方法を示します。まず、次の例に示すように、Spring Cloud Contract BOM を追加します。

次に、次の例に示すように、Spring Cloud Contract Verifier Maven プラグインを追加します。

プラグインが追加されたため、提供された契約から Spring Cloud Contract Verifier 機能を取得できます。

  • テストを生成して実行する

  • スタブを作成してインストールする

コンシューマーとしてはスタブを操作するだけなので、テストを生成する必要はありません。テストの生成と呼び出しをスキップする必要があります。これを行うには、次のコマンドを実行します。

$ cd local-http-server-repo
$ ./mvnw clean install -DskipTests

これらのコマンドを実行すると、ログに次のような内容が表示されるはずです。

[INFO] --- spring-cloud-contract-maven-plugin:1.0.0.BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server ---
[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar
[INFO]
[INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ http-server ---
[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:1.5.5.BUILD-SNAPSHOT:repackage (default) @ http-server ---
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ http-server ---
[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.jar
[INFO] Installing /some/path/http-server/pom.xml to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.pom
[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar

次の行は非常に重要です。

[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar

http-server のスタブがローカルリポジトリにインストールされていることを確認します。

統合テストの実行

Spring Cloud Contract スタブランナーの自動スタブダウンロード機能から利益を得るには、コンシューマー側プロジェクト (Loan Application service) で次のことを実行する必要があります。

  1. 次のように、Spring Cloud Contract BOM を追加します。

  2. 次のように依存関係を Spring Cloud Contract Stub Runner に追加します。

  3. テストクラスに @AutoConfigureStubRunner のアノテーションを付けます。アノテーションで、スタブランナーがコラボレーターのスタブをダウンロードするための group-id および artifact-id を指定します。

  4. (オプション) コラボレーターとオフラインでプレイしているため、オフライン作業スイッチ (StubRunnerProperties.StubsMode.LOCAL) を提供することもできます。

ここで、テストを実行すると、ログに次のような出力が表示されます。

2016-07-19 14:22:25.403  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Desired version is + - will try to resolve the latest version
2016-07-19 14:22:25.438  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Resolved version is 0.0.1-SNAPSHOT
2016-07-19 14:22:25.439  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Resolving artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT using remote repositories []
2016-07-19 14:22:25.451  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Resolved artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar
2016-07-19 14:22:25.465  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Unpacking stub from JAR [URI: file:/path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar]
2016-07-19 14:22:25.475  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Unpacked file to [/var/folders/0p/xwq47sq106x1_g3dtv6qfm940000gq/T/contracts100276532569594265]
2016-07-19 14:22:27.737  INFO 41050 --- [           main] o.s.c.c.stubrunner.StubRunnerExecutor    : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:0.0.1-SNAPSHOT:stubs=8080}]

この出力は、スタブランナーがスタブを見つけ、グループ ID com.example、アーティファクト ID http-server、スタブのバージョン 0.0.1-SNAPSHOT、ポート 8080 の stubs 分類子を使用してアプリケーションのサーバーを起動したことを意味します。

プルリクエストのサブミット

これまで行ってきたことは反復プロセスです。契約を試してみたり、ローカルにインストールしたり、契約が希望どおりに機能するまでコンシューマー側で作業したりすることができます。

結果に満足し、テストに合格したら、プルリクエストをサーバー側に発行できます。現在、コンシューマー側の作業は完了しています。

プロデューサー側 (不正検出サーバー)

不正検出サーバー (ローン発行サービスのサーバー) の開発者は、次のことを行うことができます。

  • プルリクエストを引き継ぐ

  • 不足している実装を書く

  • アプリケーションをデプロイする

次の UML 図は、不正検出フローを示しています。

getting-started-cdc-server

プルリクエストを引き継ぐ

念のため、次のリストに初期実装を示します。

その後、次のコマンドを実行できます。

$ git checkout -b contract-change-pr master
$ git pull https://your-git-server.com/server-side-fork.git contract-change-pr

次のように、自動生成されたテストに必要な依存関係を追加する必要があります。

Maven プラグインの構成では、次のように packageWithBaseClasses プロパティを渡す必要があります。

この例では、packageWithBaseClasses プロパティを設定することにより、「規則に基づく」名前付けを使用します。これを行うと、最後の 2 つのパッケージが結合されて基本テストクラスの名前が作成されることを意味します。私たちの場合、契約は src/test/resources/contracts/fraud に配置されました。contracts フォルダーから始まるパッケージが 2 つないため、fraud となるパッケージを 1 つだけ選択します。Base サフィックスを追加し、fraud を大文字にします。これにより、FraudBase テストクラス名が得られます。

生成されたすべてのテストはそのクラスを継承します。そこで、Spring コンテキストや必要なものをセットアップできます。この場合、安心の MVC [GitHub] (英語) を使用してサーバー側 FraudDetectionController を開始する必要があります。次のリストは、FraudBase クラスを示しています。

ここで、./mvnw clean install を実行すると、次のような出力が得られます。

Results :

Tests in error:
  ContractVerifierTest.validate_shouldMarkClientAsFraud:32 » IllegalState Parsed...

このエラーは、テストの生成元となった新しい契約があり、機能を実装していないために失敗したために発生します。自動生成されたテストは、次のテストメソッドのようになります。

@Test
public void validate_shouldMarkClientAsFraud() throws Exception {
    // given:
        MockMvcRequestSpecification request = given()
                .header("Content-Type", "application/vnd.fraud.v1+json")
                .body("{\"client.id\":\"1234567890\",\"loanAmount\":99999}");

    // when:
        ResponseOptions response = given().spec(request)
                .put("/fraudcheck");

    // then:
        assertThat(response.statusCode()).isEqualTo(200);
        assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*");
    // and:
        DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
        assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}");
        assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high");
}

Groovy DSL を使用した場合は、value(consumer(…​), producer(…​)) ブロックに存在する契約のすべての producer() 部分がテストに挿入されたことがわかります。YAML を使用する場合、response の matchers セクションにも同じことが当てはまります。

プロデューサー側でも TDD を実行していることに注意してください。期待はテストの形で表現されます。このテストでは、契約で定義された URL、ヘッダー、本文を使用してリクエストを独自のアプリケーションに送信します。また、レスポンスには正確に定義された値が必要です。つまり、redgreenrefactor の red 部分があります。red を green に変換する時期が来ました。

不足している実装を書き込む

予期される入力と予期される出力がわかっているため、欠落している実装を次のように記述できます。

./mvnw clean install を再度実行すると、テストは成功します。Spring Cloud Contract Verifier プラグインはテストを generated-test-sources に追加するため、IDE からこれらのテストを実際に実行できます。

アプリケーションのデプロイ

作業が完了したら、変更をデプロイできます。これを行うには、まず次のコマンドを実行して ブランチをマージする必要があります。

$ git checkout master
$ git merge --no-ff contract-change-pr
$ git push origin master

CI は、アプリケーションとスタブアーティファクトの両方を公開する ./mvnw clean deploy などのコマンドを実行する場合があります。

コンシューマー側(ローン発行)、最終ステップ

ローン発行サービスの開発者 (Fraud Detection サーバーの利用者) は、次のことを行う必要があります。

  • 機能 ブランチを master にマージします

  • オンライン作業モードに切り替える

次の UML 図は、プロセスの最終状態を示しています。

getting-started-cdc-client-final

ブランチをマスターにマージする

次のコマンドは、Git を使用して ブランチをマスターにマージする 1 つの方法を示しています。

$ git checkout master
$ git merge --no-ff contract-change-pr

オンラインで作業する

これで、Spring Cloud Contract スタブランナーのオフライン作業を無効にし、スタブを含むリポジトリの場所を指定できるようになりました。このとき、サーバー側のスタブは Nexus/Artifactory から自動的にダウンロードされます。stubsMode から REMOTE の値を設定できます。次のコードは、プロパティを変更して同じことを実現する例を示しています。

以上です。これでチュートリアルは終了です。