|
请使用 spring-cloud-contract 5.0.2 获取最新稳定版本! |
我该如何使用通用仓库与契约,而不是将它们存储在生产者中?
另一种存储契约的方式,而不是将其与生产者一同保存,是将它们存放在一个共同的位置。这种情况可能涉及安全问题(即消费者无法克隆生产者的代码)。此外,如果您将契约集中存放在一处,那么作为生产者,您就能了解自己有多少个消费者,以及在进行本地更改时可能会对哪个消费者造成影响。
仓库结构
假设我们有一个生产者,其坐标为 com.example:server,以及三个消费者:client1、client2 和 client3。那么,在包含通用契约的仓库中,您可以拥有如下设置(您可以在 Spring Cloud Contract 的仓库 samples/standalone/contracts 子文件夹中查看)。
以下列表展示了这种结构:
├── com
│ └── example
│ └── server
│ ├── client1
│ │ └── expectation.groovy
│ ├── client2
│ │ └── expectation.groovy
│ ├── client3
│ │ └── expectation.groovy
│ └── pom.xml
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
└── assembly
└── contracts.xml
在斜杠分隔的 groupid/artifact id 文件夹(com/example/server)下,您对三个消费者(client1、client2 和 client3)有预期。这些预期是标准的 Groovy DSL 合同文件,如本文档中所述。该存储库必须生成一个 JAR 文件,使其内容与存储库内容一一对应。
以下示例显示了 pom.xml 文件位于 server 文件夹内:
除了 Spring Cloud Contract Maven 插件外,没有其他依赖项。这些 pom.xml 个文件对于消费者端运行 mvn clean install -DskipTests 以本地安装生产者项目的存根(stubs)是必需的。
根目录中的 pom.xml 文件可以如下所示:
它使用装配插件构建包含所有契约的 JAR 文件。以下示例展示了此类设置:
工作流
该工作流程假设在消费者端和生产者端均已配置 Spring Cloud Contract。此外,通用仓库中也已正确设置了相应的插件以管理契约。CI 作业已在通用仓库中设置,用于构建所有契约的构件并将其上传至 Nexus 或 Artifactory。下图展示了此工作流程的 UML 图:
消费者
当消费者希望离线处理契约时,与其克隆生产者代码,不如由消费者团队克隆公共仓库,进入所需生产者文件夹(例如 com/example/server),然后运行 mvn clean install -DskipTests 以本地安装由契约转换而来的存根。
| 您需要在本地安装 Maven。 |
生产者
作为生产者,您可以修改 Spring Cloud Contract Verifier,以提供包含契约的 JAR 的 URL 和依赖关系,如下所示:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<configuration>
<contractsMode>REMOTE</contractsMode>
<contractsRepositoryUrl>
https://link/to/your/nexus/or/artifactory/or/sth
</contractsRepositoryUrl>
<contractDependency>
<groupId>com.example.standalone</groupId>
<artifactId>contracts</artifactId>
</contractDependency>
</configuration>
</plugin>
在此设置下,包含 groupid(即 com.example.standalone)和 artifactid(即 contracts)的 JAR 文件将从 link/to/your/nexus/or/artifactory/or/sth 下载。随后,该文件会被解压到本地临时文件夹中,并从中选取位于 com/example/server 的契约作为生成测试用例和存根所依据的契约。由于这一约定,生产方团队可以知晓在做出某些不兼容变更时,哪些消费方团队受到影响。
流程的其余部分看起来相同。
我如何按主题而非按生产者定义消息契约?
为避免在通用仓库中重复消息契约,当多个生产者向同一主题写入消息时,我们可以创建一种结构:将 REST 契约放置在每个生产者对应的文件夹中,而消息契约则放置在每个主题对应的文件夹中。
对于 Maven 项目
为了能够在生产者端进行工作,我们需要指定一个包含模式,以根据我们感兴趣的消息主题来过滤常见的存储库 JAR 文件。Maven Spring Cloud Contract 插件的 includedFiles 属性可实现此目的。此外,contractsPath 也必须被指定,因为默认路径将是常见的存储库 groupid/artifactid。以下示例展示了一个用于 Spring Cloud Contract 的 Maven 插件:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<configuration>
<contractsMode>REMOTE</contractsMode>
<contractsRepositoryUrl>https://link/to/your/nexus/or/artifactory/or/sth</contractsRepositoryUrl>
<contractDependency>
<groupId>com.example</groupId>
<artifactId>common-repo-with-contracts</artifactId>
<version>+</version>
</contractDependency>
<contractsPath>/</contractsPath>
<baseClassMappings>
<baseClassMapping>
<contractPackageRegex>.*messaging.*</contractPackageRegex>
<baseClassFQN>com.example.services.MessagingBase</baseClassFQN>
</baseClassMapping>
<baseClassMapping>
<contractPackageRegex>.*rest.*</contractPackageRegex>
<baseClassFQN>com.example.services.TestBase</baseClassFQN>
</baseClassMapping>
</baseClassMappings>
<includedFiles>
<includedFile>**/${project.artifactId}/**</includedFile>
<includedFile>**/${first-topic}/**</includedFile>
<includedFile>**/${second-topic}/**</includedFile>
</includedFiles>
</configuration>
</plugin>
| 前面列出的Maven插件中的许多值都可以进行更改。我们将其包含在内是为了说明目的,而非试图提供一个“典型”的示例。 |
对于 Gradle 项目
要使用 Gradle 项目:
-
添加一个自定义配置以用于常见的仓库依赖,如下所示:
ext { contractsGroupId = "com.example" contractsArtifactId = "common-repo" contractsVersion = "1.2.3" } configurations { contracts { transitive = false } } -
将通用仓库依赖项添加到您的类路径中,如下所示:
dependencies { contracts "${contractsGroupId}:${contractsArtifactId}:${contractsVersion}" testCompile "${contractsGroupId}:${contractsArtifactId}:${contractsVersion}" } -
将依赖项下载到适当的文件夹中,如下所示:
task getContracts(type: Copy) { from configurations.contracts into new File(project.buildDir, "downloadedContracts") } -
解压 JAR 文件,如下所示:
task unzipContracts(type: Copy) { def zipFile = new File(project.buildDir, "downloadedContracts/${contractsArtifactId}-${contractsVersion}.jar") def outputDir = file("${buildDir}/unpackedContracts") from zipTree(zipFile) into outputDir } -
清理未使用的合约,具体如下:
task deleteUnwantedContracts(type: Delete) { delete fileTree(dir: "${buildDir}/unpackedContracts", include: "**/*", excludes: [ "**/${project.name}/**"", "**/${first-topic}/**", "**/${second-topic}/**"]) } -
创建任务依赖关系,如下所示:
unzipContracts.dependsOn("getContracts") deleteUnwantedContracts.dependsOn("unzipContracts") build.dependsOn("deleteUnwantedContracts") -
通过指定包含契约的目录来配置插件,方法是设置
contractsDslDir属性,如下所示:contracts { contractsDslDir = new File("${buildDir}/unpackedContracts") }