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

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

In the polyglot world, there are languages that do not use binary storage, as Artifactory and Nexus do. Starting from Spring Cloud Contract version 2.0.0, we provide mechanisms to store contracts and stubs in a SCM (Source Control Management) repository. Currently, the only supported SCM is Git.

存储库必须具有以下设置(你可以从 here 中检出):

The repository would have to have the following setup (which you can checkout from 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 文件夹下:

Under the META-INF folder:

  • We group applications by groupId (such as com.example).

  • Each application is represented by its artifactId (for example, beer-api-producer-git).

  • Next, each application is organized by its version (such as 0.0.1-SNAPSHOT). Starting from Spring Cloud Contract version 2.1.0, you can specify the versions as follows (assuming that your versions follow semantic versioning):

    • + or latest: To find the latest version of your stubs (assuming that the snapshots are always the latest artifact for a given revision number). That means:

      • If you have 1.0.0.RELEASE, 2.0.0.BUILD-SNAPSHOT, and 2.0.0.RELEASE, we assume that the latest is 2.0.0.BUILD-SNAPSHOT.

      • If you have 1.0.0.RELEASE and 2.0.0.RELEASE, we assume that the latest is 2.0.0.RELEASE.

      • If you have a version called latest or +, we will pick that folder.

    • release: To find the latest release version of your stubs. That means:

      • If you have 1.0.0.RELEASE, 2.0.0.BUILD-SNAPSHOT, and 2.0.0.RELEASE we assume that the latest is 2.0.0.RELEASE.

      • If you have a version called release, we pick that folder.

最后,有两个文件夹:

Finally, there are two folders:

  • contracts: The good practice is to store the contracts required by each consumer in the folder with the consumer name (such as beer-api-consumer). That way, you can use the stubs-per-consumer feature. Further directory structure is arbitrary.

  • mappings: The Maven or Gradle Spring Cloud Contract plugins push the stub server mappings in this folder. On the consumer side, Stub Runner scans this folder to start stub servers with stub definitions. The folder structure is a copy of the one created in the contracts subfolder.

Protocol Convention

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

To control the type and location of the source of contracts (whether binary storage or an SCM repository), you can use the protocol in the URL of the repository. Spring Cloud Contract iterates over registered protocol resolvers and tries to fetch the contracts (by using a plugin) or stubs (from Stub Runner).

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

For the SCM functionality, currently, we support the Git repository. To use it, in the property where the repository URL needs to be placed, you have to prefix the connection URL with git://. The following listing shows some examples:

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 实现。

For the producer, to use the SCM (Source Control Management) approach, we can reuse the same mechanism we use for external contracts. We route Spring Cloud Contract to use the SCM implementation from the URL that starts with the git:// protocol.

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

You have to manually add the pushStubsToScm goal in Maven or use (bind) the pushStubsToScm task in Gradle. We do not push stubs to the origin of your git repository.

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

The following listing includes the relevant parts both Maven and Gradle build files:

  • 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 资源库中提取合同:

You can also further customize the publishStubsToScm gradle task. In the following example, the task is customized to pick contracts from a local git repository:

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.

使用此设置:

With such a setup:

  • A git project is cloned to a temporary directory

  • The SCM stub downloader goes to the META-INF/groupId/artifactId/version/contracts folder to find contracts. For example, for com.example:foo:1.0.0, the path would be META-INF/com.example/foo/1.0.0/contracts.

  • Tests are generated from the contracts.

  • Stubs are created from the contracts.

  • Once the tests pass, the stubs are committed in the cloned repository.

  • Finally, a push is sent to that repo’s origin.

Producer with Contracts Stored Locally

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

Another option to use the SCM as the destination for stubs and contracts is to store the contracts locally, with the producer, and only push the contracts and the stubs to SCM. The following {samples_url}/producer_with_empty_git/[project] shows the setup required to achieve this with Maven and Gradle.

使用此设置:

With such a setup:

  • Contracts from the default src/test/resources/contracts directory are picked.

  • Tests are generated from the contracts.

  • Stubs are created from the contracts.

  • Once the tests pass:

    • The git project is cloned to a temporary directory.

    • The stubs and contracts are committed in the cloned repository.

  • Finally, a push is done to that repository’s origin.

Keeping Contracts with the Producer and Stubs in an External Repository

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

You can also keep the contracts in the producer repository but keep the stubs in an external git repository. This is most useful when you want to use the base consumer-producer collaboration flow but cannot use an artifact repository to store the stubs.

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

To do so, use the usual producer setup and then add the pushStubsToScm goal and set contractsRepositoryUrl to the repository where you want to keep the stubs.

Consumer

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

On the consumer side, when passing the repositoryRoot parameter, either from the @AutoConfigureStubRunner annotation, the JUnit 4 rule, JUnit 5 extension, or properties, you can pass the URL of the SCM repository, prefixed with the git:// protocol. The following example shows how to do so:

@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"
)

使用此设置:

With such a setup:

  • The git project is cloned to a temporary directory.

  • The SCM stub downloader goes to the META-INF/groupId/artifactId/version/ folder to find stub definitions and contracts. For example, for com.example:foo:1.0.0, the path would be META-INF/com.example/foo/1.0.0/.

  • Stub servers are started and fed with mappings.

  • Messaging definitions are read and used in the messaging tests.