How Can I Use Git as the Storage for Contracts and Stubs?

在多语言世界中,有一些语言不使用二进制存储,就像 Artifactory 和 Nexus 所做的那样。从 Spring Cloud Contract 版本 2.0.0 开始,我们提供了一种机制,用于在 SCM(源代码管理)存储库中存储合同和存根。目前,唯一受支持的 SCM 是 Git。 存储库必须具有以下设置(你可以从 here 中检出):

.
└── 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:查找存根的最新版本(假设快照始终是给定修订号的最新构件)。这意味着:

      • 如果你有 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: 查找 Stub 的最新版本。这意味着:

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

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

最后,有两个文件夹:

  • contracts: 一个好做法是将每个使用者所需的合同存储在带有使用者名称的文件夹中(例如 beer-api-consumer)。这样,您可以使用 stubs-per-consumer 功能。其他目录结构是任意的。

  • mappings: Maven 或 Gradle Spring Cloud Contract 插件将 Stub Server 映射推送到该文件夹中。在使用者侧,Stub Runner 将扫描此文件夹,使用 Stub 定义启动 Stub Server。此文件夹结构是 contracts 子文件夹中所创建的结构的副本。

Protocol Convention

为了控制合同来源的类型和位置(无论是个二进制存储还是一个 SCM 资源库),你可以在资源库的 URL 中使用协议。Spring Cloud Contract 会重复使用已注册协议解析器并尝试提取合同(使用插件)或存根(从存根运行器)。

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

git://file:///foo/bar
git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git
git://git@github.com:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git

Producer

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

您必须在 Maven 中手动添加`pushStubsToScm`目标或使用 Gradle(绑定)pushStubsToScm`任务。我们不会将存根推送到您的 Git 存储库的`origin

下列清单包含相关的 Maven 和 Gradle 构建文件:

  • Maven

  • Gradle

<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>
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 任务。在下面的示例中,此任务已自定义为从本地 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"
	}
IMPORTANT

Starting with the 2.3.0.RELEASE, the customize{} closure previously used for the publishStubsToScm customization is no longer available. The settings should be applied directly within the publishStubsToScm closure, as in the preceding example.

使用此设置:

  • 一个 git 项目会被克隆到一个临时目录

  • SCM Stub 下载程序会转到 META-INF/groupId/artifactId/version/contracts 文件夹以查找合同。例如,对于 com.example:foo:1.0.0,路径将为 META-INF/com.example/foo/1.0.0/contracts

  • 测试从合同生成。

  • Stubs 从合同生成。

  • 一旦测试通过,Stub 会提交到所克隆的代码库。

  • 最后,会向该代码库的 origin 发送一个 push。

Producer with Contracts Stored Locally

Другой возможностью использования SCM в качестве назначения для заглушек и контрактов является локальное хранение контрактов у изготовителя и отправка только контрактов и заглушек в SCM. В следующем [проекте] {samples_url}/producer_with_empty_git/ показана настройка, необходимая для этого с помощью Maven и Gradle.

使用此设置:

  • 会选择默认 src/test/resources/contracts 目录的合同。

  • 测试从合同生成。

  • Stubs 从合同生成。

  • Once the tests pass:

    • Git 项目会被克隆到一个临时目录。

    • Stubs 和合同会被提交到所克隆的代码库中。

  • 最后,会向该代码库的 origin 执行一个 push。

Keeping Contracts with the Producer and Stubs in an External Repository

你还可以将合同保存在生产者资源库中,但将存根保存在外部 git 资源库中。当你想要使用基础消费者-生产者协作流程但不能使用工件资源库存储存根时,此操作最有用。

要做到这一点,请使用常规生产者设置,然后添加 pushStubsToScm 目标并设置 contractsRepositoryUrl 到你想为存根保留的资源库中。

Consumer

在消费者方面,当你从 @AutoConfigureStubRunner 注释、JUnit 4 规则、JUnit 5 扩展或属性传递 repositoryRoot 参数时,你可以传递 SCM 资源库的 URL,该 URL 以 git:// 协议为前缀。以下示例展示如何做到这一点:

@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 Stub 下载程序会转到 META-INF/groupId/artifactId/version/ 文件夹以查找 Stub 定义和合同。例如,对于 com.example:foo:1.0.0,路径将为 META-INF/com.example/foo/1.0.0/

  • Stub Server 会启动并使用映射进行填充。

  • 消息定义会读取,并用于消息测试中。