Consumer Driven Contracts with Contracts in an External Repository

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

Prerequisites

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

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

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

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

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

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

流程的高度概览如下:

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

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

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

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

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

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

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

Consumer Flow

消费者:

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

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

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

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

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

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

    • 在存根到消费者模式下工作(启用消费者驱动合同模式)。SCC Stub Runner:

    • Fetches the producer stubs.

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

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

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

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

"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

生产者:

  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. 安装合同定义,如下所示:[source, bash]

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

    ===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. 运行构建以生成测试和存根,如下所示:

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

    ===

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

  4. 将包含合约定义的拉取请求合并到代码库中:[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。 . Switches to working remotely. . 设置插件,使其不再从本地存储获取合约定义,而从远程位置获取:

===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
}

===

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

  2. The CI system:

    • Builds the project.

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

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

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

"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