如何使用通用倉庫來儲存契約,而不是將其與生產者一起存放?

另一種儲存契約的方式是將其儲存在通用位置,而不是與生產者一起存放。這種情況可能與安全問題有關(例如消費者無法克隆生產者的程式碼)。此外,如果您將契約儲存在一個位置,那麼作為生產者,您會知道有多少消費者,以及您本地的更改可能會破壞哪些消費者。

倉庫結構

假設我們有一個座標為 com.example:server 的生產者,以及三個消費者:client1client2client3。那麼,在儲存通用契約的倉庫中,您可以有以下設定(您可以在 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) 下,您可以看到三個消費者的期望(client1client2client3)。期望是標準的 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

how-to-common-repo

消費者

當消費者希望離線處理契約時,不必克隆生產者的程式碼,而是克隆通用倉庫,進入所需的生產者資料夾(例如 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 下載 groupidcom.example.standaloneartifactidcontracts 的 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 專案

  1. 為通用倉庫依賴項新增自定義配置,如下所示

    ext {
        contractsGroupId = "com.example"
        contractsArtifactId = "common-repo"
        contractsVersion = "1.2.3"
    }
    
    configurations {
        contracts {
            transitive = false
        }
    }
  2. 將通用倉庫依賴項新增到您的 classpath 中,如下所示

    dependencies {
        contracts "${contractsGroupId}:${contractsArtifactId}:${contractsVersion}"
        testCompile "${contractsGroupId}:${contractsArtifactId}:${contractsVersion}"
    }
  3. 將依賴項下載到合適的資料夾中,如下所示

    task getContracts(type: Copy) {
        from configurations.contracts
        into new File(project.buildDir, "downloadedContracts")
    }
  4. 解壓 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
    }
  5. 清理未使用的契約,如下所示

    task deleteUnwantedContracts(type: Delete) {
        delete fileTree(dir: "${buildDir}/unpackedContracts",
            include: "**/*",
            excludes: [
                "**/${project.name}/**"",
                "**/${first-topic}/**",
                "**/${second-topic}/**"])
    }
  6. 建立任務依賴,如下所示

    unzipContracts.dependsOn("getContracts")
    deleteUnwantedContracts.dependsOn("unzipContracts")
    build.dependsOn("deleteUnwantedContracts")
  7. 透過設定 contractsDslDir 屬性,指定包含契約的目錄來配置外掛,如下所示

    contracts {
        contractsDslDir = new File("${buildDir}/unpackedContracts")
    }