“AI 技术+人才”如何成为企业增长新引擎?戳此了解>>> 了解详情
写点什么

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

作者: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:005995

评论

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

🔥🔥🔥 计算机视觉 GPT-4V 它来了!

石头爱分享

VQA openai AIGC GPT MidJourney

🔥🔥🔥序幕:AIGC 进入“平民化”时代

石头爱分享

AI openai AIGC GPT-4 MidJourney

强化学习与深度学习相结合的新趋势

百度开发者中心

#人工智能 LLM 大语言模型

人工智能 | 自动驾驶汽车的未来:道路安全与出行革命

测吧(北京)科技有限公司

测试

Spring AOP 中被代理的对象一定是单例吗?

江南一点雨

Java spring

如何精细化管理嵌入式软件项目?ACT汽车电子与软件技术周演讲回顾

龙智—DevSecOps解决方案

实现语言理解与生成的新前沿

百度开发者中心

大模型训练 #人工智能 LLM

【后台体验】运营后台订单详情设计分享 | 京东云技术团队

京东科技开发者

后台开发 后台管理系统 订单系统 企业号10月PK榜 运营后台

材质、纹理、贴图的区别和关联

3D建模设计

材质 纹理 贴图

如何有效的给出反馈(二)

ShineScrum捷行

反馈 敏捷教练 敏捷教练引导 高管

人工智能 | AI驾驶系统:改变汽车产业的革命

测吧(北京)科技有限公司

测试

TimeWise-Jira工时管理插件6.0.0发布!对比测评某知名工时插件,谁的数据处理性能更胜一筹?

龙智—DevSecOps解决方案

TimeWise Jira工时管理插件

身为产品经理该如何向客户推广API商品数据接口

Noah

API接口文档 API 安全 API 接口

中企全球化案例-能源业:“1+2+3+N”,建设全球领先的智慧司库平台

用友BIP

全球司库 中企出海

深度学习与预训练语言的突破

百度开发者中心

自然语言处理 大模型训练 人工智能「

预训练模型在NLP中的应用与优化

百度开发者中心

自然语言处理 大模型训练 #人工智能

人工智能 | 自动驾驶的伦理挑战

测吧(北京)科技有限公司

测试

EazyDraw for mac(矢量图绘制软件) 11.2.0中文直装版

mac

苹果mac Windows软件 矢量绘图软件 EazyDraw

一文教你如何发挥好 TDengine Grafana 插件作用

TDengine

时序数据库 ​TDengine

人工智能 | 无人驾驶汽车:道路安全和效率的未来

测吧(北京)科技有限公司

测试

聊聊JDK19特性之虚拟线程 | 京东云技术团队

京东科技开发者

Java JVM 虚拟线程 jdk19 企业号10月PK榜

优化模型之“平均检出率”

矩视智能

深度学习 机器视觉

用友招聘云助力中企出海,充盈全球化人才蓄水池

用友BIP

招聘 中企出海

大模型训练:文本分类的未来之路

百度开发者中心

大模型训练 #人工智能

人工智能 | 机器学习与自动驾驶

测吧(北京)科技有限公司

测试

人工智能—走向智能化生活

测吧(北京)科技有限公司

测试

人工智能 | 自动驾驶技术:实现未来的智能交通

测吧(北京)科技有限公司

测试

🔥🔥🔥还没搞懂嵌入(Embedding)、微调(Fine-tuning)和提示工程(Prompt Engineering)?

石头爱分享

Embedding openai AIGC GPT-4 prompt 工程

七张图解锁Mybatis整体脉络,让你轻松拿捏面试官

小小怪下士

Java 程序员 mybatis

微软首款AI芯片代号“雅典娜”;马斯克四年内将让“星舰”上火星丨 RTE 开发者日报 Vol.61

声网

[大厂实践] 重新发明后端子集

俞凡

算法 Google 大厂实践

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