消費者驅動合同 (CDC) 循序漸進指南:生產方持有合同
考慮一個欺詐檢測和貸款發放流程的例子。業務場景是我們希望向人們發放貸款,但又不希望他們欺詐我們。我們系統當前的實現會向所有人發放貸款。
假設 Loan Issuance
是 Fraud Detection
伺服器的客戶端。在當前衝刺中,我們必須開發一個新功能:如果客戶想借太多錢,我們將其標記為欺詐。
技術說明
-
Fraud Detection 的
artifact-id
為http-server
。 -
Loan Issuance 的
artifact-id
為http-client
。 -
兩者的
group-id
均為com.example
。 -
為了本例的需要,
Stub Storage
為 Nexus/Artifactory。
社交說明
-
客戶端和服務端開發團隊都需要在整個流程中直接溝通並討論變更。
-
CDC 的核心在於溝通。
服務端程式碼位於 Spring Cloud Contract Samples 倉庫的 samples/standalone/dsl/http-server
路徑下,客戶端程式碼位於 Spring Cloud Contract 倉庫的 samples/standalone/dsl/http-client
路徑下。
在本例中,生產方擁有合同。實際上,所有合同都位於生產方的倉庫中。 |
技術說明
重要提示:所有程式碼均可在 Spring Cloud Contract Samples 倉庫中找到。
為簡單起見,我們使用以下縮寫
-
Loan Issuance (LI): HTTP 客戶端
-
Fraud Detection (FD): HTTP 伺服器
-
SCC: Spring Cloud Contract
消費方 (Loan Issuance)
作為 Loan Issuance 服務(Fraud Detection 伺服器的消費方)的開發者,您可能會執行以下步驟
-
透過為您的功能編寫測試來開始 TDD。
-
編寫缺失的實現。
-
在本地克隆 Fraud Detection 服務倉庫。
-
在本地的 fraud detection 服務倉庫中定義合同。
-
新增 Spring Cloud Contract (SCC) 外掛。
-
執行整合測試。
-
提交 Pull Request。
-
建立初始實現。
-
接管 Pull Request。
-
編寫缺失的實現。
-
部署您的應用。
-
線上工作。
我們從貸款發放流程開始,如下面的 UML 圖所示

編寫缺失的實現
在某個時候,您需要向 Fraud Detection 服務傳送請求。假設您需要傳送包含客戶 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
在本地的 Fraud Detection 服務倉庫中定義合同
作為消費方,您需要定義您究竟想要實現什麼。您需要明確您的期望。為此,編寫以下合同
將合同放在 src/test/resources/contracts/fraud 資料夾中。fraud 資料夾很重要,因為生產方的測試基類名稱會引用該資料夾。 |
以下示例顯示了我們的合同,包括 Groovy 和 YAML 格式
YML 合同非常直觀。然而,當您檢視使用靜態型別 Groovy DSL 編寫的合同時,您可能會想知道 value(client(…), server(…))
部分是什麼。透過使用這種表示法,Spring Cloud Contract 允許您定義 JSON 塊、URL 或其他結構的動態部分。在識別符號或時間戳的情況下,您無需硬編碼一個值。您希望允許不同的值範圍。要啟用值範圍,您可以為消費方設定匹配這些值的正則表示式。您可以透過 map 表示法或帶插值的 String 來提供 body。我們強烈建議使用 map 表示法。
要設定合同,您必須理解 map 表示法。請參閱 Groovy 關於 JSON 的文件。 |
之前顯示的合同是雙方之間的協議,內容如下
-
如果傳送一個 HTTP 請求,包含以下所有內容
-
在
/fraudcheck
端點上使用PUT
方法 -
一個 JSON body,其中
client.id
匹配正則表示式[0-9]{10}
,且loanAmount
等於99999
-
一個
Content-Type
頭部,其值為application/vnd.fraud.v1+json
-
-
則會向消費方傳送一個 HTTP 響應,該響應
-
狀態為
200
-
包含一個 JSON body,其中
fraudCheckStatus
欄位的值為FRAUD
,rejectionReason
欄位的值為Amount too high
-
包含一個
Content-Type
頭部,其值為application/vnd.fraud.v1+json
-
一旦您準備好在整合測試中實際檢查 API,您需要在本地安裝 stubs。
新增 Spring Cloud Contract Verifier 外掛
我們可以新增 Maven 或 Gradle 外掛。在本例中,我們展示如何新增 Maven。首先,我們新增 Spring Cloud Contract
BOM,如下例所示
接下來,新增 Spring Cloud Contract Verifier
Maven 外掛,如下例所示
由於添加了外掛,您將獲得 Spring Cloud Contract Verifier
功能,這些功能會根據提供的合同
-
生成並執行測試
-
生成並安裝 stubs
您不需要生成測試,因為作為消費方,您只需要使用 stubs。您需要跳過測試的生成和呼叫。為此,請執行以下命令
$ 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
的 stubs 已經安裝到本地倉庫。
執行整合測試
為了利用 Spring Cloud Contract Stub Runner 的自動下載 stub 功能,您必須在您的消費方專案(Loan Application service
)中執行以下操作
-
新增
Spring Cloud Contract
BOM,如下所示 -
新增對
Spring Cloud Contract Stub Runner
的依賴,如下所示 -
使用
@AutoConfigureStubRunner
註解您的測試類。在註解中,提供group-id
和artifact-id
,以便 Stub Runner 下載您的協作服務的 stubs。 -
(可選)由於您是在離線狀態下使用協作服務,您也可以提供離線工作開關(
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}]
此輸出意味著 Stub Runner 找到了您的 stubs,併為您的應用啟動了一個伺服器,其 group ID 為 com.example
,artifact ID 為 http-server
,使用了 0.0.1-SNAPSHOT
版本的 stubs,classifier 為 stubs
,埠為 8080
。
生產方 (Fraud Detection 伺服器)
作為 Fraud Detection 伺服器(Loan Issuance 服務的伺服器)的開發者,您可能需要
-
接管 Pull Request
-
編寫缺失的實現
-
部署應用
以下 UML 圖顯示了欺詐檢測流程

接管 Pull Request
提醒一下,以下清單顯示了初始實現
然後您可以執行以下命令
$ git checkout -b contract-change-pr master
$ git pull https://your-git-server.com/server-side-fork.git contract-change-pr
您必須新增自動生成測試所需的依賴項,如下所示
在 Maven 外掛的配置中,您必須傳遞 packageWithBaseClasses
屬性,如下所示
本例透過設定 packageWithBaseClasses 屬性使用“基於約定”的命名。這樣做意味著最後兩個包組合起來構成測試基類的名稱。在本例中,合同位於 src/test/resources/contracts/fraud 下。由於從 contracts 資料夾開始沒有兩個包,因此只選取一個,即 fraud 。新增 Base 字尾並將 fraud 首字母大寫。這樣就得到了 FraudBase 測試類名稱。 |
所有生成的測試都擴充套件該類。在那裡,您可以設定您的 Spring Context 或任何必要的內容。在本例中,您應該使用 Rest Assured MVC 來啟動服務端 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、頭部和 body 的請求。它還期望響應中包含精確定義的值。換句話說,您處於紅、綠、重構(red
, green
, refactor
)中的 red
階段。是時候將 red
轉換為 green
了。
消費方 (Loan Issuance),最後一步
作為 loan issuance 服務(Fraud Detection 伺服器的消費方)的開發者,您需要
-
將我們的功能分支合併到
master
-
切換到線上工作模式
以下 UML 圖顯示了流程的最終狀態

合併分支到 Master
以下命令展示了使用 Git 將分支合併到 master 的一種方法
$ git checkout master
$ git merge --no-ff contract-change-pr