AI 年度盘点与2025发展趋势展望,50+案例解析亮相AICon 了解详情
写点什么

基于契约的开发:通过明确需求优化软件开发流程

作者:Naresh Jain

  • 2023-03-14
    北京
  • 本文字数:4266 字

    阅读完需:约 14 分钟

基于契约的开发:通过明确需求优化软件开发流程

独立开发和部署单个微服务的能力是成功采用微服务策略最关键的指标。然而,大多数团队在部署微服务之前必须经历大量的集成测试。这是因为集成测试已经成为识别微服务之间兼容性问题的必要条件,因为单元和组件或 API 测试没有覆盖微服务之间的交互。

 


首先,集成测试是一种发现兼容性问题的后期反馈机制。修复这些问题的成本随着发现时间的推移而成倍增加(如上图底部的热图所示)。

 

此外,这可能会导致客户端和服务端团队做大量的返工工作,严重影响特性交付的可预测性,因为团队不得不兼顾常规的特性开发和集成错误修复。

 

集成环境可能非常脆弱。由于两个组件或服务之间的兼容性问题,即使是单个中断的交互也会导致整个环境受到损害,这意味着即使是其他不相关的功能和微服务也无法测试。

 

这给生产交付造成了阻碍,即使是对关键问题的修复,而且会让整个交付过程陷入停顿,我们称之为“集成地狱”。

 

集成测试——了解这头野兽

 

在终结集成测试之前,我们先来了解它到底是什么。这个词经常被用在不恰当的地方。

 

测试应用程序不仅仅是测试每个函数、类或组件的逻辑。应用程序的功能是这些单独的逻辑片段与其对应部分交互产生的结果。如果两个组件之间的服务边界或 API 没有理清楚,就会导致通常所说的集成问题。例如,如果函数 A 只使用一个参数调用函数 B,而函数 B 需要两个必填的参数,那么这两个函数之间就存在集成或兼容性问题。这种快速的反馈有助于我们尽早纠正并立即解决问题。

 

然而,当我们在微服务级别(服务边界位于 HTTP、消息传递或事件级别)识别兼容性问题时,单元和组件或 API 测试都无法立即识别出任何偏离或违反服务边界的行为。微服务必须与所有实际对应的服务一起测试,才能验证是否存在中断的交互。这些被广泛地(在某种程度上错误地)归类为集成测试。

 

集成测试这个词被用来描述很多类型的检查:

 

  1. 两个或多个组件之间的兼容性;

  2. 工作流测试——涉及交互编排的整个功能;

  3. 与其他依赖项(如存储、消息传递基础设施等)的交互;

  4. 还有更多,生产基础设施的端到端测试除外。

 

需要明确说明的是,当我们说终结“集成测试”时,我们说的是消除对“集成测试”的依赖,不要将其作为识别微服务之间兼容性问题的唯一方法。但其他东西,例如工作流测试,可能仍然是必要的。

确定拐点——知道从哪里下手

 

当所有代码都属于一个单体,方法签名可能就可以作为服务边界的 API 规范。我们可以通过编译时检查等机制强制执行方法签名检查,从而为开发人员提供早期反馈。

 

然而,当一个服务的组件被拆分为多个微服务,服务边界变为接口(如 HTTP REST API)时,这种早期的反馈就不会有了。在之前作为方法签名进行文档化的 API 规范现在需要被显式地文档化,描述清楚正确的调用方法。如果 API 文档不是机器可解析的,还可能会导致团队之间的沟通混乱。

 

如果没有良好文档化的服务边界:

 

  1. 只能使用近似模拟的服务端来构建客户端,而手动模拟和存根技术通常会导致存根过期的问题,即存根无法真正表示服务端。

  2. 对于服务端来说,无法模拟客户端。

 

这意味着我们必须采用缓慢的串行化开发风格,即在开始开发另一个组件之前必须等待其中一个组件构建完成。如果需要快速发布特性,这就不是一种高效的方法。

 

转向微服务后,我们失去了两个关键的能力:

 

  1. 清楚地表示两个组件之间服务边界的 API 规范;

  2. 强制执行描述服务边界的 API 规范。

 

我们需要另一种方法来弥补这两方面的缺失。

API 规范

 

如果想要恢复清晰且按照机器可解析的方式来表示 API 签名的能力,采用 API 规范标准(如 OpenAPI 或 AsyncAPI)就变得至关重要。虽然这增加了开发人员创建和维护这些规范的工作量,但利大于弊。

 

尽管如此,API 规范,顾名思义,也只是有助于描述 API 签名。在开发过程中,为了获得早期的反馈,又该如何强制执行它们呢?这一部分仍然是缺失的。

代码/文档生成——无效且不可持续

 

我们可以认为,我们可以通过代码生成技术来生成和维护 API 规范。从表面上看,如果代码是基于规范生成的,就不会偏离规范。

 

然而,这里存在一些难点:

 

  1. 正在进行中的开发——大多数代码生成工具/技术为服务器端和客户端代码生成脚手架,并要求我们在这个脚手架/模板中填写业务逻辑。问题是,当规范发生变化时,我们通常需要重新生成脚手架,从旧版本的代码中提取业务逻辑,并再次粘贴到新的脚手架中,这增加了犯人为错误的可能性。

  2. 数据类型不匹配——代码生成工具/技术必须支持每一种编程语言。在多语言环境中,生成的脚手架在不同编程语言之间的数据类型(或其他东西)可能不一致。如果我们为一种编程语言生成文档(基于服务端代码生成 API 规范),然后利用生成的规范进一步为客户端代码生成脚手架,这将进一步加剧这种情况的恶化。

 

数据类型不匹配——代码生成工具/技术必须支持每一种编程语言。在多语言环境中,生成的脚手架在不同编程语言之间的数据类型(或其他东西)可能不一致。如果我们为一种编程语言生成文档(基于服务端代码生成 API 规范),然后利用生成的规范进一步为客户端代码生成脚手架,这将进一步加剧这种情况的恶化。

 

总的来说,代码生成和文档生成只能满足有限的场景。虽然它们最初可能通过生成代码为团队提供快捷的构建应用程序的方法,但这种技术的持续成本会让团队不堪重负。

 

因此,我们需要另一种方法来执行 API 规范。

契约驱动开发——API 规范作为可执行契约

 

方法签名可以由编译器强制执行,在开发人员偏离方法签名时向他们提供早期反馈。那么 API 也能实现类似的效果吗?

 

契约测试就是实现这种效果的一种尝试。Pact.io的文档中写道:

 

契约测试是一种测试集成点的技术,它会单独检查每个应用程序,确保它们发送或接收的消息符合记录在“契约”中的内容。

 

不过需要注意的是,契约测试本身也包含了几种方式,例如客户端驱动的契约测试(Pact.io)、服务端驱动的契约测试(生产者契约测试方法中的Spring云契约)、双向契约测试(Pactflow.io)等等。在大多数这些测试方法中,API 契约是独立于 API 规范的文档。例如,在 Pact.io 中,JSON 就是 API 契约。Spring 云契约也有用于定义契约的 DSL。与其维护两个不同的工件(可能会导致不同步),不如利用 API 规范本身作为 API 契约,在开发人员偏离 API 规范导致客户端出现问题时为他们提供早期反馈,这样会不会更好?

 

Specmatic就是这样做的。Specmatic 是一个开源的基于契约驱动开发的工具。它将客户端和服务端之间的交互划分为独立可验证的单元。考虑下面两个微服务之间的交互,目前只在更大级别的测试环境中进行验证。

 

ServiceA <-> ServiceB
复制代码

 

CDD 可以将这种交互分解成连续的组成部分:

 

ServiceA <-> Contract as Stub {API spec of ServiceB}             Contract as Test {API spec of ServiceB} <-> ServiceB
复制代码

 

现在我们来仔细研究一下。

 

  1. 左边:ServiceA => Contract as Stub我们为客户端(ServiceA)模拟服务端(ServiceB),这样客户端应用程序开发就可以独立于服务端进行。由于 Contract as Stub(智能 Mock)是基于双方约定的 API 规范,因此能够真正作为服务端(ServiceB)的 Mock,它会在客户端(ServiceA)调用 API 并偏离 API 规范时给出反馈/抛出错误。

  2. 右边:Contract as Test => ServiceB 为服务端(ServiceB)模拟客户端(ServiceA),并验证响应是否符合双方约定的 API 规范。Contract as Test 将在服务端(ServiceB)应用程序开发人员偏离规范时立即向他们提供反馈。

 

既然我们可以在组件级别让客户端(ServiceA)和服务端(ServiceB)应用程序遵守 API 规范,同时又可以独立构建,那么就没有必要将它们部署在一起来测试它们的交互。这样我们就不需要再依赖集成测试来识别兼容性问题。

 

Specmatic就是这样利用 API 规范作为可执行契约

 


契约即代码

 

这里的关键是 API 规范本身,它可以让 API 提供者和使用者分离并独立地驱动各自组件的开发和部署,同时保持所有组件的一致性。

 

为了成功地进行契约驱动开发,我们需要采用 API 优先的方法,即 API 提供者和使用者需要先协作设计和记录 API 规范。这意味着他们需要使用现代的可视化编辑器之一,如 Swagger、Postman、Stoplight 等来编写 API 规范,在开始独立构建各自的部分之前专注于 API 设计,并确保所有利益相关者保持同步。

 

习惯于基于代码生成 API 规范的团队可能会对这种先编写 API 规范的反向流程感到不适应。CDD 需要类似测试驱动开发的心态转变。在进行测试驱动开发时,我们需要通过先手写测试来指导/驱动代码设计。类似地,在 CDD 中,我们需要先手工编写 API 规范,然后使用 Specmatic 等工具将它们转换为可执行的契约测试。

 

我发现,对于基于代码生成 API 规范的方法来说,API 设计处于次要地位,变得更像是事后的想法,或者是偏向于客户端或服务端。此外,由于发布时间的压力,在采用 API 规范优先的方式时,我们能够并行独立开发客户端和服务端组件,而基于代码生成 API 规范这种方式是不可能做到这一点的(客户端必须等待服务端代码完成并生成了规范)。

 

在就公共 API 规范达成了共识之后,让这些 API 规范有一个单一的真实来源就变得非常重要。如果这些规范出现了多个副本,会导致客户端和服务端团队在实现方面出现分歧。

 

CDD 建立在三个基础支柱之上。“契约即存根(Contract as Stub)”和“契约即测试(Contract as Test)”让客户端和服务端团队保持一致,但将一切联系在一起的粘合剂是第三个支柱——“中央契约存储库”。

 

API 规范是机器可解析的代码,所以还有什么地方比版本控制系统更适合存储它们的呢?将它们存储在版本控制系统(如 Git)中,我们就可以通过添加 Pull/Merge 请求过程来为它们的构建过程增加一些严格性。理想情况下,Pull/Merge 请求应该包括以下步骤:

 

  1. 语法检查,确保一致性;

  2. 向后兼容性检查,确定是否有任何重大变更;

  3. 最后的评审和合并。

 

强烈建议将规范存储在同一个中心位置,这适用于大多数情况(甚至是大型企业)。除非绝对有必要,否则不建议跨多个存储库存储规范。

 


等到规范被存储到了中央存储库中,它们就可以被:

 

  1. 客户端和服务端团队使用,分别进行独立的开发;

  2. 发布到 API 网关。

集成测试的终结

 

我们已经消除了对通过集成测试来识别应用程序兼容性问题的需求,那么系统测试和工作流测试呢?

 

CDD 为更大级别的测试环境铺平了道路,因为所有兼容性问题都在开发的更早阶段(在本地和 CI 等环境中)被识别出来,在这些环境中修复问题的成本要低得多。我们可以通过系统测试和工作流测试在稳定的更大级别的环境中验证复杂的编排问题。另外,由于我们已经不需要通过集成测试来识别兼容性问题,在更大级别的环境中测试套件的总体运行时间也缩短了。

 


原文链接:

https://www.infoq.com/articles/contract-driven-development/

2023-03-14 08:006199

评论

发布
暂无评论
发现更多内容

湃方科技联合CnosDB助力旋转类机械预测性维护

CnosDB

数据库 开源 时序数据库 CnosDB

对于Vue3和Ts的心得和思考

京东科技开发者

Vue ts 京东云 京东技术 企业号 3 月 PK 榜

安全测试之探索windows游戏扫雷

京东科技开发者

编程 windows 企业号 3 月 PK 榜 动态调式 代码编写

DevOps 在未来将如何演进?丨行业观察

SEAL安全

DevOps 平台工程

预约直播|3月30日下周四,应用促活大揭秘,快速提升DAU

HarmonyOS SDK

HMS Core

复杂「场景」数据导入导出

Java 架构

关于数据库分库分表的一点想法

京东科技开发者

数据库 ShardingSphere 京东云 京东技术 企业号 3 月 PK 榜

京东金融Android瘦身探索与实践

京东科技开发者

App 京东云 京东技术 企业号 3 月 PK 榜 京东科技

详细分析Spring Boot启动流程

Java你猿哥

spring Spring Boot SpringBoot启动流程

面试突击:MVCC 和间隙锁有什么区别?

王磊

java面试

分享一个修改了xml文件再也不用重启的项目mybatis-xmlrealod

越长大越悲伤

Java 开源 Spring Boot mybatis 开源项目

华为都入场做ERP了,国产ERP困局如何破解?

陈飞

分布式数据库 国产化 ERP SaaS 平台 PaaS平台

2023年最新互联网Java面试八股文出炉(附大厂P5-P8技术栈)

架构师之道

Java 程序员 面试

京东金融Android瘦身探索与实践

京东科技开发者

架构 sdk 安装包瘦身 APK 企业号 3 月 PK 榜

在路上!OceanBase首次公布四大“开发者友好”实践

OceanBase 数据库

数据库 oceanbase

GPT-4创造者:第二次改变AI浪潮的方向

OneFlow

“找工难&金三银四”,关于面试,你需要知道的这些事

IT学习日记

面试 Java、 找工作 简历模板

非常小的一个东西,Spring依赖注入Bean类型的8种情况

小小怪下士

Java spring 程序员 后端

Sentinel Go- 揭秘 [热点参数] 的实现原理

柠檬汁Code(binbin0325)

golang 中间件 限流 原理分析 Sentienl

有一种焦虑叫ChatGPT(62/100)

hackstoic

AI AIGC ChatGPT

Spring源码核心剖析

京东科技开发者

spring 前端 京东云 京东技术 企业号 3 月 PK 榜

多功能文件搜索软件:HoudahSpot中文版

真大的脸盆

Mac Mac 软件 文件搜索 搜索工具 搜索文件

Github 爆火,阿里巴巴大牛都在强推的 Java 性能优化实践小册!

架构师之道

Java 编程 计算机

想要找macOS系统上跑分工具?Geekbench 6帮助您快速,准确地测量处理器和内存性能进行测试

Rose

mac系统 系统跑分 Geekbench Geekbench 6

PyTorch 深度学习实战 | 知识图谱嵌入结合图路径的推荐 RippleNet

TiAmo

深度学习 Ripplenet

【ASPLOS 2023】图神经网络统一图算子抽象uGrapher,大幅提高计算性能

阿里云大数据AI技术

人工智能 机器学习 性能优化 图神经网络 企业号 3 月 PK 榜

通过 Amazon Managed Microsoft Active Directory 运行混合 Active Directory 服务

亚马逊云科技 (Amazon Web Services)

Amazon

三天吃透MySQL面试八股文

程序员大彬

MySQL java面试

备战金三银四:2023年1200道Java面试真题合集,助你搞定面试官

采菊东篱下

Java 面试

奇点云数据云平台如何确保“多租户”安全?从某大型零售企业实践说起

Geek_2d6073

mac版photoshop 2023存储为窗口显示空白、黑屏如何解决

Rose

PhotoShop ps2023存储空白 空白、黑屏

基于契约的开发:通过明确需求优化软件开发流程_文化 & 方法_InfoQ精选文章