在生产者端使用契约进行消费者驱动契约 (CDC) 的分步指南
考虑一个欺诈检测和贷款发放过程的示例。业务场景是:我们想向人们发放贷款,但又不希望他们从我们这里骗取钱财。我们系统的当前实现是向所有人发放贷款。
假设贷款发放是欺诈检测服务器的客户端。在当前冲刺中,我们必须开发一个新功能:如果客户想借的钱太多,我们就将该客户标记为欺诈。
技术说明
-
欺诈检测的
artifact-id是http-server。 -
贷款发放的
artifact-id是http-client。 -
两者都有一个
group-id,即com.example。 -
为了这个例子,
存根存储是 Nexus/Artifactory。
社会说明
-
客户端和服务器开发团队都需要直接沟通,并在过程中讨论变更。
-
CDC 核心在于沟通。
服务器端代码可在 Spring Cloud Contract Samples 仓库的 samples/standalone/dsl/http-server 路径下找到,客户端代码可在 Spring Cloud Contract 仓库的 samples/standalone/dsl/http-client 路径下找到。
| 在这种情况下,生产者拥有契约。实际上,所有契约都在生产者的仓库中。 |
技术说明
重要提示:所有代码都可在 Spring Cloud Contract Samples 仓库中找到。
为简洁起见,我们使用以下缩写
-
贷款发放 (LI):HTTP 客户端
-
欺诈检测 (FD):HTTP 服务器
-
SCC:Spring Cloud Contract
消费者端(贷款发放)
作为贷款发放服务(欺诈检测服务器的消费者)的开发者,您可以执行以下步骤
-
通过为您的功能编写测试来开始 TDD。
-
编写缺失的实现。
-
在本地克隆欺诈检测服务仓库。
-
在欺诈检测服务的仓库中本地定义契约。
-
添加 Spring Cloud Contract (SCC) 插件。
-
运行集成测试。
-
提交拉取请求。
-
创建初始实现。
-
接管拉取请求。
-
编写缺失的实现。
-
部署您的应用程序。
-
在线工作。
我们从贷款发放流程开始,以下 UML 图显示了该流程
编写缺失的实现
在某个时刻,您需要向欺诈检测服务发送请求。假设您需要发送包含客户 ID 和客户想要借款金额的请求。您想通过 PUT 方法将其发送到 /fraudcheck URL。为此,您可以使用类似于以下代码的代码
为简单起见,欺诈检测服务的端口设置为 8080,应用程序运行在 8090。
如果此时您启动测试,它会中断,因为目前没有服务在端口 8080 上运行。 |
在本地克隆欺诈检测服务仓库
您可以从尝试服务器端契约开始。为此,您必须首先通过运行以下命令来克隆它
$ 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 或其他动态结构的一部分。在标识符或时间戳的情况下,您无需硬编码值。您希望允许一些不同范围的值。要启用值范围,您可以设置与消费者侧这些值匹配的正则表达式。您可以通过映射表示法或带插值的字符串提供正文。我们强烈建议使用映射表示法。
| 要设置契约,您必须了解映射表示法。请参阅 Groovy 关于 JSON 的文档。 |
前面显示的契约是双方之间的协议,即
-
如果发送 HTTP 请求时包含所有以下内容
-
对
/fraudcheck端点的PUT方法 -
一个 JSON 主体,其中
client.id匹配正则表达式[0-9]{10}且loanAmount等于99999 -
Content-Type标头,值为application/vnd.fraud.v1+json
-
-
则会向消费者发送一个 HTTP 响应,该响应
-
状态为
200 -
包含一个 JSON 主体,其中
fraudCheckStatus字段的值为FRAUD,rejectionReason字段的值为Amount too high -
Content-Type标头,值为application/vnd.fraud.v1+json
-
一旦您准备好在集成测试中实际检查 API,您需要在本地安装存根。
添加 Spring Cloud Contract Verifier 插件
我们可以添加 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 自动下载存根的功能,您必须在您的消费者端项目(贷款申请服务)中执行以下操作
-
添加
Spring Cloud ContractBOM,如下所示 -
添加
Spring Cloud Contract Stub Runner的依赖项,如下所示 -
使用
@AutoConfigureStubRunner注解您的测试类。在注解中,为 Stub Runner 提供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}]
此输出意味着 Stub Runner 找到了您的存根,并为您的应用程序启动了一个服务器,其 group ID 为 com.example,artifact ID 为 http-server,存根版本为 0.0.1-SNAPSHOT,分类器为 stubs,端口为 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 测试类名。 |
所有生成的测试都扩展该类。在那里,您可以设置您的 Spring 上下文或任何必要的东西。在这种情况下,您应该使用 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、标头和正文。它还期望响应中包含精确定义的值。换句话说,您拥有 red、green 和 refactor 中的 red 部分。现在是时候将 red 转换为 green 了。