如何使用 Git 作为契约和服务存根的存储?

在多语言(Polyglot)世界中,有些语言不使用像 Artifactory 和 Nexus 那样的二进制存储。从 Spring Cloud Contract 2.0.0 版本开始,我们提供了将契约和服务存根存储在 SCM(源代码控制管理)仓库中的机制。目前,唯一支持的 SCM 是 Git。

该仓库必须具有以下设置(您可以从此处签出)

.
└── META-INF
    └── com.example
        └── beer-api-producer-git
            └── 0.0.1-SNAPSHOT
                ├── contracts
                │   └── beer-api-consumer
                │       ├── messaging
                │       │   ├── shouldSendAcceptedVerification.groovy
                │       │   └── shouldSendRejectedVerification.groovy
                │       └── rest
                │           ├── shouldGrantABeerIfOldEnough.groovy
                │           └── shouldRejectABeerIfTooYoung.groovy
                └── mappings
                    └── beer-api-consumer
                        └── rest
                            ├── shouldGrantABeerIfOldEnough.json
                            └── shouldRejectABeerIfTooYoung.json

META-INF 文件夹下

  • 我们按 groupId 对应用进行分组(例如 com.example)。

  • 每个应用都由其 artifactId 表示(例如 beer-api-producer-git)。

  • 接下来,每个应用都按其版本进行组织(例如 0.0.1-SNAPSHOT)。从 Spring Cloud Contract 2.1.0 版本开始,您可以按以下方式指定版本(假设您的版本遵循语义化版本控制):

    • +latest:查找最新版本的存根(假设快照始终是给定修订号的最新 artifact)。这意味着

      • 如果您有 1.0.0.RELEASE2.0.0.BUILD-SNAPSHOT2.0.0.RELEASE,我们假设最新版本是 2.0.0.BUILD-SNAPSHOT

      • 如果您有 1.0.0.RELEASE2.0.0.RELEASE,我们假设最新版本是 2.0.0.RELEASE

      • 如果您有一个名为 latest+ 的版本,我们将选择该文件夹。

    • release:查找最新版本的发行版存根。这意味着

      • 如果您有 1.0.0.RELEASE2.0.0.BUILD-SNAPSHOT2.0.0.RELEASE,我们假设最新版本是 2.0.0.RELEASE

      • 如果您有一个名为 release 的版本,我们将选择该文件夹。

最后,还有两个文件夹

  • contracts:推荐的做法是将每个消费者所需的契约存储在以消费者名称命名的文件夹中(例如 beer-api-consumer)。这样,您就可以使用 stubs-per-consumer 特性。进一步的目录结构是任意的。

  • mappings:Maven 或 Gradle Spring Cloud Contract 插件将存根服务器映射推送到此文件夹。在消费者端,Stub Runner 会扫描此文件夹以启动带有存根定义的存根服务器。文件夹结构是 contracts 子文件夹中创建的结构的复制。

协议约定

为了控制契约源(无论是二进制存储还是 SCM 仓库)的类型和位置,您可以使用仓库 URL 中的协议。Spring Cloud Contract 会遍历注册的协议解析器,并尝试获取契约(通过插件)或存根(从 Stub Runner)。

对于 SCM 功能,目前我们支持 Git 仓库。要使用它,在需要放置仓库 URL 的属性中,您必须在连接 URL 前加上 git:// 前缀。以下列表显示了一些示例:

git://file:///foo/bar
git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git
git://[email protected]:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git

生产者

对于生产者,要使用 SCM(源代码控制管理)方式,我们可以重用用于外部契约的相同机制。我们将 Spring Cloud Contract 路由到使用以 git:// 协议开头的 URL 中的 SCM 实现。

您必须在 Maven 中手动添加 pushStubsToScm goal,或在 Gradle 中使用(绑定)pushStubsToScm task。我们不会将存根推送到 Git 仓库的 origin

以下列表包含 Maven 和 Gradle 构建文件中的相关部分:

Maven
<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>${spring-cloud-contract.version}</version>
    <extensions>true</extensions>
    <configuration>
        <!-- Base class mappings etc. -->

        <!-- We want to pick contracts from a Git repository -->
        <contractsRepositoryUrl>git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git</contractsRepositoryUrl>

        <!-- We reuse the contract dependency section to set up the path
        to the folder that contains the contract definitions. In our case the
        path will be /groupId/artifactId/version/contracts -->
        <contractDependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>${project.artifactId}</artifactId>
            <version>${project.version}</version>
        </contractDependency>

        <!-- The contracts mode can't be classpath -->
        <contractsMode>REMOTE</contractsMode>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <!-- By default we will not push the stubs back to SCM,
                you have to explicitly add it as a goal -->
                <goal>pushStubsToScm</goal>
            </goals>
        </execution>
    </executions>
</plugin>
Gradle
contracts {
	// We want to pick contracts from a Git repository
	contractDependency {
		stringNotation = "${project.group}:${project.name}:${project.version}"
	}
	/*
	We reuse the contract dependency section to set up the path
	to the folder that contains the contract definitions. In our case the
	path will be /groupId/artifactId/version/contracts
	 */
	contractRepository {
		repositoryUrl = "git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git"
	}
	// The mode can't be classpath
	contractsMode = "REMOTE"
	// Base class mappings etc.
}

/*
In this scenario we want to publish stubs to SCM whenever
the `publish` task is invoked
*/
publish.dependsOn("publishStubsToScm")

您还可以进一步自定义 publishStubsToScm gradle task。在以下示例中,自定义了此 task 以从本地 Git 仓库获取契约:

gradle
publishStubsToScm {
	// We want to modify the default set up of the plugin when publish stubs to scm is called
	// We want to pick contracts from a Git repository
	contractDependency {
		stringNotation = "${project.group}:${project.name}:${project.version}"
	}
	/*
	We reuse the contract dependency section to set up the path
	to the folder that contains the contract definitions. In our case the
	path will be /groupId/artifactId/version/contracts
	 */
	contractRepository {
		repositoryUrl = "git://file://${new File(project.rootDir, "../target")}/contract_empty_git/"
	}
	// We set the contracts mode to `LOCAL`
	contractsMode = "LOCAL"
	}
重要

2.3.0.RELEASE 版本开始,以前用于 publishStubsToScm 自定义的 customize{} 闭包不再可用。设置应直接应用于 publishStubsToScm 闭包内部,如前面的示例所示。

通过此设置

  • 将 Git 项目克隆到临时目录

  • SCM 存根下载器会访问 META-INF/groupId/artifactId/version/contracts 文件夹查找契约。例如,对于 com.example:foo:1.0.0,路径将是 META-INF/com.example/foo/1.0.0/contracts

  • 根据契约生成测试。

  • 根据契约创建存根。

  • 测试通过后,将存根提交到克隆的仓库中。

  • 最后,向该仓库的 origin 发送 push 操作。

在本地存储契约的生产者

另一种使用 SCM 作为存根和契约目标的选项是将契约与生产者一起存储在本地,然后只将契约和存根推送到 SCM。以下链接展示了使用 Maven 和 Gradle 实现此目标所需的设置。

通过此设置

  • 从默认的 src/test/resources/contracts 目录获取契约。

  • 根据契约生成测试。

  • 根据契约创建存根。

  • 测试通过后

    • 将 Git 项目克隆到临时目录。

    • 将存根和契约提交到克隆的仓库中。

  • 最后,向该仓库的 origin 执行 push 操作。

将契约与生产者一起存储,并将存根存储在外部仓库

您还可以将契约保存在生产者仓库中,但将存根保存在外部 Git 仓库中。当您想使用基本的消费者-生产者协作流程但无法使用 artifact 仓库来存储存根时,这非常有用。

为此,请使用常规的生产者设置,然后添加 pushStubsToScm goal,并将 contractsRepositoryUrl 设置为您要存储存根的仓库。

消费者

在消费者端,当传递 repositoryRoot 参数时,无论是通过 @AutoConfigureStubRunner 注解、JUnit 4 rule、JUnit 5 extension 还是属性,您都可以传递以 git:// 协议为前缀的 SCM 仓库 URL。以下示例展示了如何操作:

@AutoConfigureStubRunner(
    stubsMode="REMOTE",
    repositoryRoot="git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git",
    ids="com.example:bookstore:0.0.1.RELEASE"
)

通过此设置

  • 将 Git 项目克隆到临时目录。

  • SCM 存根下载器会访问 META-INF/groupId/artifactId/version/ 文件夹查找存根定义和契约。例如,对于 com.example:foo:1.0.0,路径将是 META-INF/com.example/foo/1.0.0/

  • 启动存根服务器并加载映射。

  • 读取消息传递定义并在消息传递测试中使用。