跳到主要内容

如何使用 Git 作为合约和桩代码的存储库?

DeepSeek V3 中英对照 How Can I Use Git as the Storage for Contracts and Stubs?

在多语言世界中,存在一些不使用二进制存储的语言,就像 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:用于查找存根的最新版本(假设快照始终是给定修订号的最新构件)。这意味着:

      • 如果您有 1.0.0.RELEASE、2.0.0.BUILD-SNAPSHOT 和 2.0.0.RELEASE,我们假设最新的是 2.0.0.BUILD-SNAPSHOT。

      • 如果您有 1.0.0.RELEASE 和 2.0.0.RELEASE,我们假设最新的是 2.0.0.RELEASE。

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

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

      • 如果您有 1.0.0.RELEASE、2.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 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://git@github.com:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git

生产者​

对于生产者而言,要使用SCM(源代码管理)方法,我们可以复用与外部合约相同的机制。我们将Spring Cloud Contract配置为使用以git://协议开头的URL所对应的SCM实现。

important

您必须在 Maven 中手动添加 pushStubsToScm 目标,或在 Gradle 中使用(绑定)pushStubsToScm 任务。我们不会将存根推送到您 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 任务。在以下示例中,该任务被定制为从本地 git 仓库中选取合约:

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。

本地存储合约的生产者​

另一种将SCM作为存根和合约目的地的选择是,将合约存储在本地(与生产者一起),并且只将合约和存根推送到SCM。以下项目展示了使用Maven和Gradle实现此设置所需的配置。

通过这样的设置:

  • 默认 src/test/resources/contracts 目录下的合约会被选取。

  • 测试是根据合约生成的。

  • 存根是根据合约创建的。

  • 一旦测试通过:

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

    • 存根和合约会被提交到克隆的仓库中。

  • 最后,会推送到该仓库的 origin。

将生产者契约和存根保存在外部仓库中​

你也可以将合约保留在生产者仓库中,但将存根保存在外部 git 仓库中。当你希望使用基础的消费者-生产者协作流程,但无法使用制品仓库来存储存根时,这种方法最为有用。

为此,请使用常规的生产者设置,然后添加 pushStubsToScm 目标,并将 contractsRepositoryUrl 设置为你希望存放存根的仓库地址。

消费者​

在消费者端,当传递 repositoryRoot 参数时,无论是通过 @AutoConfigureStubRunner 注解、JUnit 4 规则、JUnit 5 扩展还是属性,你都可以传递 SCM 仓库的 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存根下载器会进入META-INF/groupId/artifactId/version/文件夹来查找存根定义和契约。例如,对于com.example:foo:1.0.0,路径将是META-INF/com.example/foo/1.0.0/。

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

  • 消息定义被读取并用于消息传递测试。