如何使用通用倉庫來儲存契約,而不是將其與生產者一起存放?
另一種儲存契約的方式是將其儲存在通用位置,而不是與生產者一起存放。這種情況可能與安全問題有關(例如消費者無法克隆生產者的程式碼)。此外,如果您將契約儲存在一個位置,那麼作為生產者,您會知道有多少消費者,以及您本地的更改可能會破壞哪些消費者。
倉庫結構
假設我們有一個座標為 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 檔案,該檔案與倉庫的內容一一對應。
以下示例展示了 server
資料夾內的 pom.xml
檔案
除了 Spring Cloud Contract Maven Plugin,沒有其他依賴。這些 pom.xml
檔案是消費者端執行 mvn clean install -DskipTests
所必需的,以便在本地安裝生產者專案的存根。
根資料夾中的 pom.xml
檔案可能如下所示
它使用 assembly plugin 來構建包含所有契約的 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>
透過此設定,將從 link/to/your/nexus/or/artifactory/or/sth
下載 groupid
為 com.example.standalone
且 artifactid
為 contracts
的 JAR。然後將其解壓到本地臨時資料夾中,並選取 com/example/server
中存在的契約作為用於生成測試和存根的契約。由於這種約定,生產者團隊在進行一些不相容更改時,可以知道哪些消費者團隊受到了影響。
其餘流程保持不變。
如何按主題而不是按生產者定義訊息契約?
為了避免在通用倉庫中出現訊息契約重複(當多個生產者向同一個主題寫入訊息時),我們可以建立一個結構:REST 契約按生產者存放在資料夾中,而訊息契約按主題存放在資料夾中。
對於 Maven 專案
為了能夠在生產者端工作,我們應該指定一個包含模式,以便根據我們感興趣的訊息主題過濾通用倉庫的 jar 檔案。Spring Cloud Contract Maven 外掛的 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 } }
-
將通用倉庫依賴項新增到您的 classpath 中,如下所示
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") }