Consumer Driven Contracts with Contracts in an External Repository

在此流程中,我们执行消费者驱动的契约测试。契约定义存储在一个单独的存储库中。

In this flow, we perform Consumer Driven Contract testing. The contract definitions are stored in a separate repository.

Prerequisites

若要对外部存储库中保留的契约使用消费者驱动的契约,你需要设置 Git 存储库,此存储库:

To use consumer-driven contracts with the contracts held in an external repository, you need to set up a git repository that:

  • 包含每个生产者的所有合同定义。

  • Contains all the contract definitions for each producer.

  • 可以将合同定义打包到 JAR 中。

  • Can package the contract definitions in a JAR.

  • 对于每个合同生产者,包含一种方法(例如,pom.xml)通过 Spring Cloud Contract Plugin (SCC Plugin) 本地安装存根。

  • For each contract producer, contains a way (for example, pom.xml) to install stubs locally through the Spring Cloud Contract Plugin (SCC Plugin).

更多信息,请参阅 How To section,我们描述了如何设置这样的存储库。对于此类项目的示例,请参阅 {samples_code}/beer_contracts[此示例]。

For more information, see the How To section, where we describe how to set up such a repository. For an example of such a project, see {samples_code}/beer_contracts[this sample].

您还需要具有已设置 Spring Cloud Contract Stub Runner 的使用者代码。对于此类项目的示例,请参阅 {samples_code}/consumer[此示例]。您还需要具有已设置 Spring Cloud Contract 的生产者代码,以及插件。对于此类项目的示例,请参阅 {samples_code}/producer_with_external_contracts[此示例]。存根存储是 Nexus 或 Artifactory。

You also need consumer code that has Spring Cloud Contract Stub Runner set up. For an example of such a project, see {samples_code}/consumer[this sample]. You also need producer code that has Spring Cloud Contract set up, together with a plugin. For an example of such a project, see {samples_code}/producer_with_external_contracts[this sample]. The stub storage is Nexus or Artifactory.

流程的高度概览如下:

At a high level, the flow is as follows:

  1. 使用者使用来自独立存储库的合同定义。

  2. The consumer works with the contract definitions from the separate repository.

  3. 一旦使用者的工作完成,就会在使用者端创建包含工作代码的分支,并向包含合同定义的独立存储库发出拉取请求。

  4. Once the consumer’s work is done, a branch with working code is created on the consumer side, and a pull request is made to the separate repository that holds the contract definitions.

  5. 该制作者负责对包含 contractdefinitions 的分离存储库发出拉取请求并本地安装包含所有合同的 JAR。

  6. The producer takes over the pull request to the separate repository with contract definitions and installs the JAR with all contracts locally.

  7. 该制作者根据本地存储的 JAR 生成测试,并编写缺少的实现来使测试通过。

  8. The producer generates tests from the locally stored JAR and writes the missing implementation to make the tests pass.

  9. 一旦该制作者的工作完成,负责存储合同定义的存储库的拉取请求即会合并。

  10. Once the producer’s work is done, the pull request to the repository that holds the contract definitions is merged.

  11. CI 工具构建了包含合同定义的存储库,并且 JAR 与合同定义上传到 Nexus 或 Artifactory 后,该制作者可以合并其分支。

  12. After the CI tool builds the repository with the contract definitions and the JAR with contract definitions gets uploaded to Nexus or Artifactory, the producer can merge its branch.

  13. 最后,该消费者可以切换到在线工作以从远程位置获取制作者的存根,并且该分支可以合并到 master 中。

  14. Finally, the consumer can switch to working online to fetch stubs of the producer from a remote location, and the branch can be merged to master.

Consumer Flow

消费者:

The consumer:

  1. 编写一个测试以向制作者发送请求。[.iokays-translated-d73cf48455a15e1af4877e31158a7fee] 由于不存在服务器,因此测试失败。

The test fails due to no server being present.

  1. Writes a test that would send a request to the producer.[.iokays-translated-d73cf48455a15e1af4877e31158a7fee] 由于不存在服务器,因此测试失败。

The test fails due to no server being present.

  1. 克隆包含合同定义的存储库。

  2. Clones the repository that holds the contract definitions.

  3. 在文件夹下将需求设定为合同,其中消费者名称为制作者的子文件夹。[.iokays-translated-87b5ad595377bfffeedec62e245c8b8b] 例如,对于名为 producer 的生产者和名为 consumer 的消费者,契约存储在 src/main/resources/contracts/producer/consumer/) 下

For example, for a producer named producer and a consumer named consumer, the contracts would be stored under src/main/resources/contracts/producer/consumer/)

  1. Sets up the requirements as contracts under the folder, with the consumer name as a subfolder of the producer.[.iokays-translated-87b5ad595377bfffeedec62e245c8b8b] 例如,对于名为 producer 的生产者和名为 consumer 的消费者,契约存储在 src/main/resources/contracts/producer/consumer/) 下

For example, for a producer named producer and a consumer named consumer, the contracts would be stored under src/main/resources/contracts/producer/consumer/)

  1. 定义合同后,将制作者存根安装到本地存储,如下例所示:[source, bash]

$ cd src/main/resource/contracts/producer
$ ./mvnw clean install
  1. Once the contracts are defined, installs the producer stubs to local storage, as the following example shows:[source, bash]

$ cd src/main/resource/contracts/producer
$ ./mvnw clean install
  1. 在消费者测试中设置 Spring Cloud Contract (SCC) Stub Runner,以:

    • 从本地存储获取制作者存根。

    • Fetch the producer stubs from local storage.

    • 在存根到消费者模式下工作(启用消费者驱动合同模式)。[.iokays-translated-071d063289660d5f9cc5fc7d41d430fa] SCC Stub Runner:

The SCC Stub Runner:

  • Work in the stubs-per-consumer mode (this enables consumer driven contracts mode).[.iokays-translated-071d063289660d5f9cc5fc7d41d430fa] SCC Stub Runner:

The SCC Stub Runner:

  • Fetches the producer stubs.

  • 使用制作者存根运行内存中 HTTP 服务器存根。现在,您的测试会与 HTTP 服务器存根进行通信,并通过测试。

  • Runs an in-memory HTTP server stub with the producer stubs. Now your test communicates with the HTTP server stub, and your tests pass.

  • 对包含新合同的制作者的存储库创建拉取请求。

  • Creates a pull request to the repository with contract definitions, with the new contracts for the producer.

  • 为您的消费者代码添加分支,直至制作者团队合并其代码。

  • Branches your consumer code, until the producer team has merged their code.

  1. Sets up Spring Cloud Contract (SCC) Stub Runner in the consumer tests, to:

    • 从本地存储获取制作者存根。

    • Fetch the producer stubs from local storage.

    • 在存根到消费者模式下工作(启用消费者驱动合同模式)。[.iokays-translated-071d063289660d5f9cc5fc7d41d430fa] SCC Stub Runner:

The SCC Stub Runner:

  • Work in the stubs-per-consumer mode (this enables consumer driven contracts mode).[.iokays-translated-071d063289660d5f9cc5fc7d41d430fa] SCC Stub Runner:

The SCC Stub Runner:

  • Fetches the producer stubs.

  • 使用制作者存根运行内存中 HTTP 服务器存根。现在,您的测试会与 HTTP 服务器存根进行通信,并通过测试。

  • Runs an in-memory HTTP server stub with the producer stubs. Now your test communicates with the HTTP server stub, and your tests pass.

  • 对包含新合同的制作者的存储库创建拉取请求。

  • Creates a pull request to the repository with contract definitions, with the new contracts for the producer.

  • 为您的消费者代码添加分支,直至制作者团队合并其代码。

  • Branches your consumer code, until the producer team has merged their code.

以下 UML 图显示了消费者流程:

The following UML diagram shows the consumer flow:

"Consumer"->"Repo\nwith\ncontracts": clone
"Repo\nwith\ncontracts"->"Repo\nwith\ncontracts\nclone": cloned
"Consumer"->"Repo\nwith\ncontracts\nclone": create contract\ndefinitions of\nthe [Producer]
"Repo\nwith\ncontracts\nclone"->"Local storage": install [Producer]\nstubs locally
"Consumer"->"Consumer\nBuild": run tests
"Consumer\nBuild"->"SCC\nStub Runner": Run [Producer] stubs
"SCC\nStub Runner"->"Local storage": fetch [Producer] stubs
"SCC\nStub Runner"->"Producer stub": stub is running
"Consumer\nBuild"->"Producer stub": send a request\nin the tests
"Producer stub"->"Consumer\nBuild": send a response
"Consumer\nBuild"->"Consumer": the tests are passing
"Consumer"->"Repo\nwith\ncontracts\nclone": send a pull request
"Repo\nwith\ncontracts\nclone"->"Repo\nwith\ncontracts": pull request sent
"Consumer"->"Consumer": branch the code

Producer Flow

生产者:

The producer:

  1. 负责对包含 contractdefinitions 的存储库发出拉取请求。您可以从命令行执行此操作,如下所示:[source, bash]

$ git checkout -b the_branch_with_pull_request master
git pull https://github.com/user_id/project_name.git the_branch_with_pull_request
  1. Takes over the pull request to the repository with contract definitions. You can do it from the command line, as follows[source, bash]

$ git checkout -b the_branch_with_pull_request master
git pull https://github.com/user_id/project_name.git the_branch_with_pull_request
  1. 安装合同定义,如下所示:[source, bash]

$ ./mvnw clean install
  1. Installs the contract definitions, as follows[source, bash]

$ ./mvnw clean install
  1. 设置插件从 JAR(而不是 src/test/resources/contracts)获取合约定义,如下所示:

    ===Maven

    ===

    Maven
    <plugin>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-contract-maven-plugin</artifactId>
    	<version>${spring-cloud-contract.version}</version>
    	<extensions>true</extensions>
    	<configuration>
    		<!-- We want to use the JAR with contracts with the following coordinates -->
    		<contractDependency>
    			<groupId>com.example</groupId>
    			<artifactId>beer-contracts</artifactId>
    		</contractDependency>
    		<!-- The JAR with contracts should be taken from Maven local -->
    		<contractsMode>LOCAL</contractsMode>
    		<!-- ... additional configuration -->
    	</configuration>
    </plugin>
    Gradle
    contracts {
    	// We want to use the JAR with contracts with the following coordinates
    	// group id `com.example`, artifact id `beer-contracts`, LATEST version and NO classifier
    	contractDependency {
    		stringNotation = 'com.example:beer-contracts:+:'
    	}
    	// The JAR with contracts should be taken from Maven local
    	contractsMode = "LOCAL"
    	// Additional configuration
    }

    ===

  2. Sets up the plugin to fetch the contract definitions from a JAR instead of from src/test/resources/contracts, as follows:

    ===Maven

    ===

    Maven
    <plugin>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-contract-maven-plugin</artifactId>
    	<version>${spring-cloud-contract.version}</version>
    	<extensions>true</extensions>
    	<configuration>
    		<!-- We want to use the JAR with contracts with the following coordinates -->
    		<contractDependency>
    			<groupId>com.example</groupId>
    			<artifactId>beer-contracts</artifactId>
    		</contractDependency>
    		<!-- The JAR with contracts should be taken from Maven local -->
    		<contractsMode>LOCAL</contractsMode>
    		<!-- ... additional configuration -->
    	</configuration>
    </plugin>
    Gradle
    contracts {
    	// We want to use the JAR with contracts with the following coordinates
    	// group id `com.example`, artifact id `beer-contracts`, LATEST version and NO classifier
    	contractDependency {
    		stringNotation = 'com.example:beer-contracts:+:'
    	}
    	// The JAR with contracts should be taken from Maven local
    	contractsMode = "LOCAL"
    	// Additional configuration
    }

    ===

  3. 运行构建以生成测试和存根,如下所示:

    ===Maven

    ===

    Maven
    ./mvnw clean install
    Gradle
    ./gradlew clean build

    ===

  4. Runs the build to generate tests and stubs, as follows:

    ===Maven

    ===

    Maven
    ./mvnw clean install
    Gradle
    ./gradlew clean build

    ===

  5. 编写缺少的实现,使测试通过。

  6. Writes the missing implementation, to make the tests pass.

  7. 将包含合约定义的拉取请求合并到代码库中:[source, bash]

$ git commit -am "Finished the implementation to make the contract tests pass"
$ git checkout master
$ git merge --no-ff the_branch_with_pull_request
$ git push origin master

CI 系统使用契约定义构建项目,并将具有契约定义的 JAR 上传到 Nexus 或 Artifactory。

The CI system builds the project with the contract definitions and uploads the JAR with the contract definitions to Nexus or Artifactory.

  1. Merges the pull request to the repository with contract definitions, as follows:[source, bash]

$ git commit -am "Finished the implementation to make the contract tests pass"
$ git checkout master
$ git merge --no-ff the_branch_with_pull_request
$ git push origin master

CI 系统使用契约定义构建项目,并将具有契约定义的 JAR 上传到 Nexus 或 Artifactory。

The CI system builds the project with the contract definitions and uploads the JAR with the contract definitions to Nexus or Artifactory.

  1. Switches to working remotely.

  2. 设置插件,使其不再从本地存储获取合约定义,而从远程位置获取:

    ===Maven

    ===

    Maven
    <plugin>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-contract-maven-plugin</artifactId>
    	<version>${spring-cloud-contract.version}</version>
    	<extensions>true</extensions>
    	<configuration>
    		<!-- We want to use the JAR with contracts with the following coordinates -->
    		<contractDependency>
    			<groupId>com.example</groupId>
    			<artifactId>beer-contracts</artifactId>
    		</contractDependency>
    		<!-- The JAR with contracts should be taken from a remote location -->
    		<contractsMode>REMOTE</contractsMode>
    		<!-- ... additional configuration -->
    	</configuration>
    </plugin>
    Gradle
    contracts {
    	// We want to use the JAR with contracts with the following coordinates
    	// group id `com.example`, artifact id `beer-contracts`, LATEST version and NO classifier
    	contractDependency {
    		stringNotation = 'com.example:beer-contracts:+:'
    	}
    	// The JAR with contracts should be taken from a remote location
    	contractsMode = "REMOTE"
    	// Additional configuration
    }

    ===

  3. Sets up the plugin so that the contract definitions are no longer taken from the local storage but from a remote location, as follows:

    ===Maven

    ===

    Maven
    <plugin>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-contract-maven-plugin</artifactId>
    	<version>${spring-cloud-contract.version}</version>
    	<extensions>true</extensions>
    	<configuration>
    		<!-- We want to use the JAR with contracts with the following coordinates -->
    		<contractDependency>
    			<groupId>com.example</groupId>
    			<artifactId>beer-contracts</artifactId>
    		</contractDependency>
    		<!-- The JAR with contracts should be taken from a remote location -->
    		<contractsMode>REMOTE</contractsMode>
    		<!-- ... additional configuration -->
    	</configuration>
    </plugin>
    Gradle
    contracts {
    	// We want to use the JAR with contracts with the following coordinates
    	// group id `com.example`, artifact id `beer-contracts`, LATEST version and NO classifier
    	contractDependency {
    		stringNotation = 'com.example:beer-contracts:+:'
    	}
    	// The JAR with contracts should be taken from a remote location
    	contractsMode = "REMOTE"
    	// Additional configuration
    }

    ===

  4. 将生产者代码与新的实现合并。

  5. Merges the producer code with the new implementation.

  6. The CI system:

    • Builds the project.

    • 生成测试、存根和存根 JAR。

    • Generates tests, stubs, and the stub JAR.

    • 将包含应用程序和存根的人工制品上传到Nexus或Artifactory。

    • Uploads the artifact with the application and the stubs to Nexus or Artifactory.

以下 UML 图显示了生产者流程:

The following UML diagram shows the producer process:

"Producer"->"Repo\nwith\ncontracts": take over the pull request
"Producer"->"Repo\nwith\ncontracts": install the contract\ndefinitions JAR
"Repo\nwith\ncontracts"->"Local storage": install the\ncontract definitions\nJAR locally
"Local storage"->"Repo\nwith\ncontracts": contract definitions\nJAR installed
"Producer"->"Producer\nBuild": run build
"Producer\nBuild"->"SCC\nPlugin": generate tests,\nstubs\nand stub jar
"SCC\nPlugin"->"Local storage": fetch the contract definitions
"Local storage"->"SCC\nPlugin": contract definitions found
"SCC\nPlugin"->"SCC\nPlugin": generate tests
"Producer\nBuild"->"Producer\nBuild": run the\ngenerated tests
"Producer\nBuild"->"Producer": the tests failed to pass
"Producer"->"Producer": write the missing implementation
"Producer"->"Producer\nBuild": run the build again
"Producer\nBuild"->"Producer\nBuild": fetch the contract definitions\nrun the generated tests
"Producer\nBuild"->"Producer": the tests passed
"Producer"->"Repo\nwith\ncontracts": merge the pull request
"Repo\nwith\ncontracts"->"CI": build and upload the\ncontract definitions artifact
"CI"->"Stub Storage": upload the\ncontract definitions
"Producer"->"Producer": setup the SCC Plugin\nto work remotely
"Producer"->"Producer": merge the code\nwith the implementation
"Producer"->"CI": build and upload\nthe artifacts
"CI"->"Producer\nBuild\non CI": generate tests,\nstubs\nand stub jar
"Producer\nBuild\non CI"->"SCC\nPlugin": generate tests,\nstubs\nand stub jar
"SCC\nPlugin"->"Stub Storage": fetch the contract definitions
"Stub Storage"->"SCC\nPlugin": contract definitions found
"SCC\nPlugin"->"SCC\nPlugin": generate tests
"Producer\nBuild\non CI"->"CI": the build passed
"Producer\nBuild\non CI"->"Stub Storage": upload the application JAR\nand the stubs jar