プロデューサー側の契約を伴うコンシューマー主導契約 (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 サーバーのコンシューマー) は、次の手順を実行できます。
機能のテストを作成して TDD を開始します。
不足している実装を記述します。
Fraud Detection サービスリポジトリのクローンをローカルに作成します。
不正検出サービスのリポジトリでローカルに契約を定義します。
Spring Cloud Contract (SCC) プラグインを追加します。
統合テストを実行します。
プルリクエストを提出します。
初期実装を作成します。
プルリクエストを引き継ぎます。
不足している実装を記述します。
アプリケーションをデプロイします。
オンラインで作業します。
次の UML 図に示されているローン発行フローから始めます。
機能のテストを作成して 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
) で次のことを実行する必要があります。
次のように、
Spring Cloud Contract
BOM を追加します。次のように依存関係を
Spring Cloud Contract Stub Runner
に追加します。テストクラスに
@AutoConfigureStubRunner
のアノテーションを付けます。アノテーションで、スタブランナーがコラボレーターのスタブをダウンロードするためのgroup-id
およびartifact-id
を指定します。(オプション) コラボレーターとオフラインでプレイしているため、オフライン作業スイッチ (
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 図は、不正検出フローを示しています。
プルリクエストを引き継ぐ
念のため、次のリストに初期実装を示します。
その後、次のコマンドを実行できます。
$ 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、ヘッダー、本文を使用してリクエストを独自のアプリケーションに送信します。また、レスポンスには正確に定義された値が必要です。つまり、red
、green
、refactor
の red
部分があります。red
を green
に変換する時期が来ました。
コンシューマー側(ローン発行)、最終ステップ
ローン発行サービスの開発者 (Fraud Detection サーバーの利用者) は、次のことを行う必要があります。
機能 ブランチを
master
にマージしますオンライン作業モードに切り替える
次の UML 図は、プロセスの最終状態を示しています。
ブランチをマスターにマージする
次のコマンドは、Git を使用して ブランチをマスターにマージする 1 つの方法を示しています。
$ git checkout master
$ git merge --no-ff contract-change-pr