微服务联调靠“玄学”?——Spring Cloud Contract 把接口争吵变成可执行契约!
🏆本文收录于《滚雪球学SpringBoot 3》:
https://blog.csdn.net/weixin_43970743/category_12795608.html,专门攻坚指数提升,本年度国内最系统+最专业+最详细(永久更新)。
本专栏致力打造最硬核 SpringBoot3 从零基础到进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中…欢迎大家订阅持续学习。 如果想快速定位学习,可以看这篇【SpringBoot3教程导航帖】https://blog.csdn.net/weixin_43970743/article/details/151115907,你想学习的都被收集在内,快速投入学习!!两不误。
若还想学习更多,可直接前往《滚雪球学SpringBoot(全版本合集)》:https://blog.csdn.net/weixin_43970743/category_11599389.html,涵盖SpringBoot所有版本教学文章。
演示环境说明:
- 开发工具:IDEA 2021.3
- JDK版本: JDK 17(推荐使用 JDK 17 或更高版本,因为 Spring Boot 3.x 系列要求 Java 17,Spring Boot 3.5.4 基于 Spring Framework 6.x 和 Jakarta EE 9,它们都要求至少 JDK 17。)
- Spring Boot版本:3.5.4(于25年7月24日发布)
- Maven版本:3.8.2 (或更高)
- Gradle:(如果使用 Gradle 构建工具的话):推荐使用 Gradle 7.5 或更高版本,确保与 JDK 17 兼容。
- 操作系统:Windows 11
1)微服务测试痛点:为什么需要 CDC(Consumer-Driven Contracts)?
微服务一多,最容易把人逼疯的不是业务复杂,而是依赖方太多:
- A 服务(Consumer)依赖 B 服务(Producer)的
/users/{id} - B 改了字段名、状态码、响应结构……A 这边测试不一定立刻炸(尤其是 mock 写得“很乐观”的时候)
- 真正炸往往发生在:联调、预发、甚至线上(那种“我明明没动你啊”最扎心🥲)
CDC 的核心思想很朴素:
由消费方提出期望(契约),生产方必须证明自己满足契约;同时,消费方使用由契约生成的 Stub 来隔离测试。
Spring Cloud Contract 就是把这件事工具化:你写契约(Groovy/YAML 等),它可以在生产者侧生成测试来验证实现,还能在消费者侧生成/运行 WireMock Stub用于隔离协作方。
2)契约文件怎么写:Groovy / YAML 两条路
官方文档明确:契约可以用 Groovy DSL 或 YAML 表达,并放在 contractsDslDir 指定目录下,默认是 src/test/resources/contracts。
(写到这我忍不住吐槽一句:默认目录真的挺合理,别一上来就改路径改到自己都找不到🤣)
2.1 Groovy 契约示例(HTTP)
场景:
GET /api/users/1返回 200 和用户信息
src/test/resources/contracts/user/get_user_1.groovy
import org.springframework.cloud.contract.spec.Contract
Contract.make {
description "should return user by id"
request {
method GET()
url "/api/users/1"
}
response {
status OK()
headers {
contentType(applicationJson())
}
body(
id: 1,
name: "Britney",
level: "VIP"
)
}
}
Groovy DSL 的好处是表达力强、能写 matcher、还能做 DSL 扩展(官方也提供了 DSL customization 的机制)。
2.2 YAML 契约示例(更“配置化”)
src/test/resources/contracts/user/get_user_1.yml
description: should return user by id
request:
method: GET
url: /api/users/1
response:
status: 200
headers:
Content-Type: application/json
body:
id: 1
name: Britney
level: VIP
如果你团队里不想引入 Groovy(或者有人看到 Groovy 就皱眉😅),YAML 很友好;但复杂匹配场景 Groovy 往往更顺手。
3)Producer 端:自动生成测试用例,验证接口实现(别“写了契约就当完成”)
Spring Cloud Contract 的基本生产者流程是:
- 在
contracts目录写契约 - 引入 Verifier 依赖 + Maven/Gradle 插件
- 生成测试并运行:只要实现不符合契约,构建直接失败
3.1 Maven 插件(典型配置思路)
官方文档对 Maven 项目接入有专门章节,并说明生成测试源目录有时需要加入 IDE classpath。
一个常见配置(示意):
<build>
<plugins>
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
<configuration>
<!-- 生成的测试会继承这个基类:你在这里准备 MockMvc/RestAssured 等 -->
<baseClassForTests>com.example.contract.ContractBase</baseClassForTests>
</configuration>
</plugin>
<!-- 可选:把 generated-test-sources 加入测试源码路径,IDE 才能识别 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-test-source</id>
<phase>generate-test-sources</phase>
<goals><goal>add-test-source</goal></goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-test-sources/contracts/</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
生成测试源目录与 build-helper 的做法在官方 Maven 项目文档里有提到。
3.2 baseClassForTests:让“生成测试”知道怎么发请求
你需要写个基类,让生成的测试能跑(比如用 MockMvc):
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
public abstract class ContractBase {
@Autowired MockMvc mockMvc;
@BeforeEach
void setup() {
RestAssuredMockMvc.mockMvc(mockMvc);
}
}
然后你在 Producer 里实现真实 Controller。只要响应和契约不一致,生成测试就会失败。这个机制本质上就是:契约在生产者侧被“自动变成验收测试”。
4)Consumer 端:自动生成 Stub(WireMock),隔离依赖测试
消费者最怕两件事:
- 依赖服务没起、网络不稳、环境不同步
- 你写的 mock 跟真实接口偏得离谱(还自信满满😂)
Spring Cloud Contract 的 Stub Runner 就是为了解决“怎么把生产者生成的 stub 交付给消费者,并自动跑起来”的问题:它可以下载 stub jar 或从 classpath 取 stub,然后启动 WireMock 服务供消费者测试使用。
4.1 Stub 交付:stubs-jar(常见做法)
生产者构建时除了跑生成测试,还会生成一个包含 WireMock 映射的 stubs-jar(Maven 插件提供了相关目标/能力)。
然后把它发布到 Nexus/Artifactory(或内部仓库),消费者测试时直接拉下来用。
4.2 Consumer 测试中跑 Stub:Stub Runner + WireMock
消费者侧(JUnit)典型用法是加个注解,让 stub 自动拉起:
@SpringBootTest
@AutoConfigureStubRunner(
ids = "com.example:producer-service:+:stubs:8085",
stubsMode = StubRunnerProperties.StubsMode.REMOTE
)
class ConsumerIntegrationTest {
@Test
void should_call_producer_stub() {
// 你的 consumer 代码把 producer baseUrl 指向 localhost:8085
// 然后跑集成测试:完全不依赖真实 producer
}
}
ids指向生产者的 stub 坐标(groupId:artifactId:version:classifier:port)- Stub Runner 会为你启动 WireMock(HTTP stubs)
这些能力在 Stub Runner Core 的官方说明中明确:下载/挑选 stub、启动 WireMock、提供 stub 定义。
小贴士:如果你不想连远程仓库,也可以用
LOCAL或CLASSPATH模式(看你团队的 artifact 管理习惯)。
5)把流程串成一条“能落地”的 CDC 工作流(我个人最推荐的那种🙂)
给你一个实战型流程,按这个跑,团队争吵会少很多:
- Consumer 提需求:把对 Producer 的调用期望写成契约(字段、状态码、headers、示例 body)
- Producer 拉契约:在 Producer 构建里生成测试并跑,保证实现满足契约
- Producer 发布 stubs-jar:把 stub 当作“可复用资产”发布到仓库
- Consumer 用 Stub Runner 测试:消费者 CI 不依赖真实 Producer,稳定、可重复
- 契约变更走版本化:契约是接口演进的“事实标准”,别靠口头同步
这套流程真正的价值不是“我用了一个新工具”,而是:
把接口协作从“人肉联调”升级成“可执行契约 + 可交付 stub”。
这才是 CDC 的魂。
6)常见坑(我替你提前踩两脚😅)
坑 1:契约写得太“宽松”,等于没写
比如响应 body 只写了一个字段,其他字段不约束;或者状态码不严格。最后 Producer 随便改,Consumer 还是会炸。
建议:关键字段要约束,非关键字段可以宽松,但别“全宽松”。
坑 2:Producer 生成测试没跑 / CI 没卡口
契约测试的核心是“强制验证”。如果生成测试不进 CI,那契约就变成装饰品。
建议:把 generateTests + test 绑定到构建生命周期,让不满足契约的提交过不了。
坑 3:Consumer 用 stub,但 baseUrl 没切对
Stub Runner 把 WireMock 起在某个端口,你的 consumer client 还是指向真实环境,那当然“不生效”。
建议:测试 profile 下通过配置注入 producerUrl(比如 http://localhost:8085)。
收尾:契约测试到底值不值?我用一句话反问你🙂
如果你的团队每次联调都在吵:
- “你怎么又改字段名?”
- “我这边没问题啊,你清缓存了吗?”
- “昨天还能跑,今天怎么挂了?”
那我想问你一句:
**你们到底是在开发功能,还是在反复对齐接口细节?**😄
🧧福利赠与你🧧
无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学SpringBoot」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门SpringBoot,就像滚雪球一样,越滚越大, 无边无际,指数级提升。
最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。
同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G PDF编程电子书、简历模板、技术文章Markdown文档等海量资料。
ps:本文涉及所有源代码,均已上传至Gitee:
https://gitee.com/bugjun01/SpringBoot-demo开源,供同学们一对一参考 Gitee传送门https://gitee.com/bugjun01/SpringBoot-demo,同时,原创开源不易,欢迎给个star🌟,想体验下被🌟的感jio,非常感谢❗
🫵 Who am I?
我是 bug菌:
- 热活跃于 CSDN:
https://blog.csdn.net/weixin_43970743| 掘金:https://juejin.cn/user/695333581765240| InfoQ:https://www.infoq.cn/profile/4F581734D60B28/publish| 51CTO:https://blog.51cto.com/u_15700751| 华为云:https://bbs.huaweicloud.com/community/usersnew/id_1582617489455371| 阿里云:https://developer.aliyun.com/profile/uolxikq5k3gke| 腾讯云:https://cloud.tencent.com/developer/user/10216480/articles等技术社区; - CSDN 博客之星 Top30、华为云多年度十佳博主&卓越贡献奖、掘金多年度人气作者 Top40;
- 掘金、InfoQ、51CTO 等平台签约及优质作者;
- 全网粉丝累计 30w+。
更多高质量技术内容及成长资料,可查看这个合集入口 👉 点击查看:https://bbs.csdn.net/topics/612438251 👈️
硬核技术公众号 「猿圈奇妙屋」https://bbs.csdn.net/topics/612438251 期待你的加入,一起进阶、一起打怪升级。
- End -
- 点赞
- 收藏
- 关注作者
评论(0)