阅读 350
破局微服务架构演化——初探消费者驱动契约

破局微服务架构演化——初探消费者驱动契约

image.png

前言

前段时间跟我司的陈鑫在地铁上聊到了契约,他描述了他之前做过的一个项目,那个项目提供了一个机制,可以促进前后端在开发过程中采取契约先行方式进行协同,这种开发方式有以下几个优势:

  1. 这份契约定义出来之后 前端同学就可以在不依赖后端接口的情况下 进行开发和联调,大幅降低等待后端接口的时间。
  2. 前端只要开发完成,测试就可以先进场对功能进行快速验证,并站在测试角度提出一些优化意见。
  3. 可以快速提供一个 Demo 跟客户一起验证商业价值。

顿时,我对契约先行这种模式产生极大的兴趣。在阅读相关资料后,一个有意思的概念浮现到我眼前,即消费者驱动契约(Consumer-Driven Contracts),下文我会用 CDC 来代替 Consumer-Driven Contracts。

Consumer-Driven Contracts 起源

image.png

2006 年 4 月 25 日, Ian Robinson 在的 Microsoft Architect Insight Conference 上跟老马(Martin Fowler)和 Ron Jacobs 就“架构演进(Evolution of Architecture)“话题讨论中提出 CDC(Consumer-Driven Contracts) 概念。 同年6月,Ian Robinson 的文章 Consumer-Driven Contracts: A Service Evolution Pattern 发布在老马的个人网站上。

Ian Robinson 把 CDC 描述为是一种服务演化模式,并在 Consumer-Driven Contracts: A Service Evolution Pattern 一文中总结了 Provider ContractsConsumer Contracts 以及 Consumer-Driven Contracts 之间的异同点。

Ian Robinson 在文中提出了一个基本假设,认为 Provider 本身对于业务来说没有价值,它的价值在于被消费,因此 Ian Robinson 认为应该把表达业务价值交由更接近用户的服务(Consumer)来承担,旨在以精益、及时的方式开发业务价值。在这个假设下,CDC 诞生。

CDC 是在 SOA(Service-oriented architecture)和分布式架构流行的背景下诞生的,它主要是为了解决架构演化的需求。CDC 在 Consumer 和 Provider 之间建立起一个快速反馈机制,促使多方协作,确保服务提供方(Provider)的演化不影响消费方(Consumer)。可想而知,CDC 同样适用于微服务。

微服务的流行

image.png

如今 DDD(Domain-Driven Design) 和微服务架构风格如火如荼,微服务是一种开发软件的架构和组织方法,其中软件由通过明确定义的API 进行通信的小型独立服务组成。 这些服务由各个小型独立团队负责。 微服务架构使应用程序更易于扩展和更快地开发,从而加速创新并缩短新功能的上市时间。

当然上面都是漂亮话,微服务受制于康威定律、跨部门 KPI 以及 团队成员的基本功。

然而在微服务那错中复杂的关系中,对服务的演化和服务之间的测试提出的非常高的要求。

微服务的测试问题

假设我们的系统包含了多个微服务,依赖关系如下图: image.png

现在我们要测试其中的某个服务,验证该服务是否可以与其它服务进行正常通信,我们有两种方案

  1. 部署所有微服务进行端对端测试
  2. 在单元测试和集成测试中 mock 外部服务

这两者都有它们的优势和劣势

部署所有微服务进行端对端测试

优势:

  • 模拟生产环境
  • 测试服务之间的真实通信

劣势:

  • 测试成本高,测试其中一个服务需要同时部署依赖链涉及到的所有微服务
  • 其中一个服务出问题,将会阻塞端对端测试
  • 运行慢、测试慢,连锁反应导致反馈也非常慢

在单元测试和集成测试中 mock 外部服务

优势:

  • 反馈快
  • 对于基础设施的要求很低

劣势:

  • Mock 掉的外部接口不代表服务提供者的真实实现,双方存在信息不对称
  • 通过测试不代表上线就没问题

微服务架构演化的难点

image.png

图片来源 abelsu7.top/2019/09/18/…

为了使读者有代入感,我在这里提出来两个假设:

  1. 上图是我们的微服务网络拓扑结构,展示了服务之间的依赖关系。

  2. 我们已经做了自动化单元测试、自动化应用内集成测试、并搭建了完善的 CI/CD pipeline。

然而,在这种令人窒息的依赖关系下,我们就算在开发层面做了较好的质量保障(只能确保单个服务内的可靠性),对于服务的演化和交互还是会遇到两个棘手问题

  1. 如何使服务能够互相独立演化(各个服务的演化速度不同,我们可不想为了升级一个服务,被迫升级所有下游服务)?
  2. 如何确保服务演化上线后不影响下游消费者?

CDC 如何保障架构演化

consumer_driven_contracts.png

相关术语:

  • Provider —— 服务提供方
  • Consumer —— 服务消费方
  • Contract —— 契约
  • Expectation —— 期望

通过上图可以得出以下结论

  1. Consumer 和 Provider 围绕关键业务价值进行沟通和协同。
  2. Consumer 将 Expectation 导入 Contract。
  3. Provider 收到 Contract 后,根据 Expectation 导出 Consumer 所需的业务元素。
  4. Consumer 和 Provider 都通过 自动化契约测试 来达成对 Contact 的承诺。

前三步围绕 Contract 的定义和实现展开,第四步将原本口头约定的契约,通过自动化契约测试牢牢绑定到 Provider 和 Consumer 的生命周期中。

这里出现一个有意思的情况,Provider 的演化跟 Consumer 的业务需求稳定性强依赖

稳定的 Contract

这种情况 Provider 可以选择不演化,也可以选择演化(内部重构),只要保证 Consumer 和 Provider 使用同一份 Contract,那么 Provider 只要能够通过契约测试,我们就有理由相信 Provider 能够满足 Consumer 的业务需求,我们就有信心单独为 Provider 进行演化和上线。

如果 Provider 在演化的过程中导致契约测试失败(契约测试提供的快速反馈机制),此时 Provider 只要修复失败的契约测试也可以进行独立演化和上线。

不稳定的 Contract

因为业务需求变更导致 Contract 变更(新增或修改),这种情况 Provider 就必须跟着 Consumer 一起演化,也就是说,服务演化 只会出现在 Consumer 表达明确需求的地方

如果出现多份 Contract 作用于同一个 Provider 接口,我们可以采用一个原则来解决,即“一个实现的发送行为必须是保守的,而接收行为必须是自由的。”我们可以在服务的演化过程中遵循这一原则,使得 Provider 接口返回的业务元素覆盖多个 Contract,而 Consumer (接收端)只选择对 Contract 中的元素做“刚刚好”的断言,这个原则对于 Provider 服务的意义是重大的,因为只有这样 Provider 的接口才能复用,才不会陷入版本地狱。

CDC 解决微服务测试问题的原理

消费者驱动契约测试的实现原理大同小异,大致实现流程可以分为三步

  1. 增加一个 Mock Server(图中的 Mock Provider),它主要的职责是根据 Contract 的描述向 Consumer 提供访问能力。
  2. Consumer 向 Mock Provider 发出的请求,并将请求结果写入合同,或者把请求结果跟 Contract 的 Expectation 进行匹配(这取决于 Contract 的生成方式)。
  3. 向 Provider 重放 Consumer 的请求,然后匹配 Consumer 和 Provider 的请求结果是否一致。

通过上面三步,就可以知道 Consumer 和 Provider 是否达成对契约的承诺。

下面三张图是该流程的可视化效果:

cdc_slide_1_replay.gif

cdc_slide_2.gif

cdc_slide_3.gif

这个方案既解决了“启动所有微服务”的弊端,又能模拟更真实的服务通信,可谓一举两得。

Consumer-Driven Contracts 测试框架

Consumer-Driven Contracts 的挑战

然而,这个看起来还不错的模式存在着一些挑战

  • 如果单一 Provider 难以满足 Consumer 的 Expectation 时,可能会促使 Provider 另辟蹊径,从而导致破坏 Provider 的概念完整性,这种情况就可能需要 BFF(Backend For Frontend)了。
  • 基础设施不可避免地引入一层复杂性和协议依赖性。
  • 需要培养具备相应能力的开发人员。
  • 测试策略如何兼容多种测试实践的结合。

总结

Consumer-Driven Contracts 是一种架构演化模式,演化只会出现在 Consumer 表达明确需求的地方;在开发层面,它促进契约先行原则,并通过契约测试模拟服务之间的通信来保障服务的可靠性;在交付层面,它促进 Consumer 和 Provider 围绕关键业务价值,从最低限度的精益需求开始协作,消除浪费,不过度实现,聚焦功能的交付。

在敏捷和微服务的流行下,Consumer-Driven Contracts 将会是一把有用的利剑。

后续

下一篇我们一起来看看 Spring Cloud Contract 如何实现 Consumer-Driven Contracts。

文章分类
后端
文章标签