消费者驱动契约(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 Samples 仓库的 samples/standalone/dsl/http-server 路径下获取,客户端代码则可在 Spring Cloud Contract 的仓库 samples/standalone/dsl/http-client 路径下找到。
| 在这种情况下,生产者拥有合同。在物理上,所有的合同都位于生产者的存储库中。 |
技术说明
重要:所有代码均可在 spring Cloud Contract 示例仓库 中获取。
为简化起见,我们使用以下缩写:
-
贷款发放(LI):HTTP 客户端
-
欺诈检测(FD):HTTP 服务器
-
SCC: Spring Cloud Contract
消费者端(贷款发放)
作为贷款发放服务(欺诈检测服务器的消费者)的开发者,您可能需要执行以下步骤:
-
通过为您的功能编写测试来开始进行TDD(测试驱动开发)。
-
编写缺失的实现。
-
本地克隆欺诈检测服务仓库。
-
define the contract locally in the repository of the fraud detection service.
-
添加Spring Cloud Contract(SCC)插件。
-
运行集成测试。
-
提交Pull Request。
-
创建一个初始实现。
-
完成拉取请求。
-
编写缺失的实现。
-
部署您的应用程序。
-
在线工作。
我们先来看贷款发放流程,下图显示了该流程的 UML 图:
编写缺失的实现
到某个时间点,您需要向欺诈检测服务发送请求。假设需要发送包含客户端 ID 和客户想要借款金额的请求。您要使用PUT方法将它发送到/fraudcheck URL。为此,您可以使用类似的代码:
为了简化,欺诈检测服务的端口设置为 8080,并且应用运行在 8090 上。
如果您在此处开始测试,将会失败,因为端口8080上没有任何服务当前正在运行。 |
在本地克隆反欺诈服务仓库
你可以先从使用服务端契约开始。为此,必须首先 克隆它,通过运行以下命令:
$ git clone https://your-git-server.com/server-side.git local-http-server-repo
<p>在欺诈检测服务的存储库中本地定义合同</p>
作为消费者,您需要明确您想要实现什么目标。您需要提出您的期望。为此,您需要写下以下合同:
将合同放在src/test/resources/contracts/fraud文件夹中。fraud文件夹很重要,因为生产者的测试基础类名引用了该文件夹。 |
以下示例展示了我们的契约,包括Groovy和YAML两种形式:
YML 协议很简单。但是,当您查看使用强类型 Groovy DSL 编写的协议时,可能会想知道这些代码 0 部分是什么意思。通过使用这种表示法,Spring Cloud Contract 允许您为 JSON 块、URL 或其他结构定义部分,这些部分是动态的。在这种情况下,标识符或时间戳不需要硬编码值。您需要允许一些不同的值范围。要启用值范围,可以设置匹配这些值的正则表达式,以便在使用者端。您可以使用映射语法或带有插值的字符串提供正文。我们强烈建议使用映射语法。
To set up contracts, you must understand the map notation. See theGroovy docs regarding JSON. |
之前展示的合同是双方之间的协议,规定了:
-
如果HTTP请求中包含了以下所有内容:
-
A
PUTmethod on the/fraudcheckendpoint -
A JSON body with a
client.idthat matches the regular expression[0-9]{10}andloanAmountequal to99999 -
A
Content-Type标头的值为application/vnd.fraud.v1+json
-
-
然后向消费者发送HTTP响应
-
无状态信息
-
包含一个带有值
FRAUD的fraudCheckStatus字段和带值Amount too high的rejectionReason字段的 JSON 载荷。 -
Has a
Content-Typeheader with a value ofapplication/vnd.fraud.v1+json
-
一旦你准备好在集成测试中实践检查API,就需要在本地安装存档。
添加Spring CloudContractVerifier插件
我们可以在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 Stub Runner 功能中获得自动存档下载的好处,您必须在消费方侧项目(0)中执行以下操作:Loan
Application service:
-
Add the
Spring Cloud ContractBOM, as follows: -
Add the dependency to
Spring Cloud Contract Stub Runner, as follows: -
使用注解标注您的测试类
@AutoConfigureStubRunner。在注解中提供group-id和artifact-id,以便 Stub Runner 下载您协同对象的占位程序。 -
(可选) 因为您离线与合作者一起玩,所以也可以提供离线工作开关 (0)。
现在,当你运行你的测试时,你将在日志中看到如下输出:
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}]
This output means that Stub Runner has found your stubs and started a server for your application with a group ID of com.example and an artifact ID of http-server with version 0.0.1-SNAPSHOT of the stubs and with the stubs classifier on port 8080.
生产者端(欺诈检测服务器)
作为贷款发放服务的开发人员,您可能希望执行以下操作:
-
接手拉取请求
-
编写缺失的实现
-
部署应用程序
以下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来使用“基于约定”的命名。这样做意味着最后两个包组合起来构成了基础测试类的名称。在我们的例子中,合同被放在了src/test/resources/contracts/fraud文件夹下。由于您从contracts文件夹开始没有两个包,只需选择一个,应该是fraud。添加后缀Base并首字母大写fraud。这就为您提供了FraudBase测试类名。 |
All the generated tests extend that class. Over there, you can set up your Spring Context or whatever is necessary. In this case, you should use Rest Assured MVC to start the server side FraudDetectionController. The following listing shows the FraudBase class:
现在,如果您运行 0,则会得到如下输出:
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,您会发现Contract中所有出现在producer()块中的value(consumer(…), producer(…))部分都注入了测试。如果您使用YAML,则对于matchers部分的response也同样适用。
请注意,在生产者一侧,你也进行着TDD。期望以测试的形式表达:该测试会向我们自己的应用程序发送具有在合同中定义的URL、头信息和正文的请求,并期望响应中精确定义的值。换句话说,你已经有了red部分的red、green和refactor。现在要把red转换为green。
顾客端(贷款发放),最后一步
作为贷款发放服务的开发者(欺诈检测服务器的消费者),你需要:
-
合并我们的功能分支到
master -
切换到在线工作模式
下面的UML图展示了该过程的最终状态:
Merging a Branch to Master
以下命令展示了一种使用 Git 将分支合并到 master 的方式:
$ git checkout master
$ git merge --no-ff contract-change-pr