Netflix 团队六边形架构实践

应对变化,架构先行

Posted by 薛以致用 on March 17, 2020

申明

本站点所有文章,仅代表个人想法,不代表任何公司立场,所有数据都来自公开资料

转载请注明出处

唯一不变就是变化,研发团队每天处理各种新的需求或修修补补的零碎,而阻碍团队快速应对变化(快速上线)的一个障碍是“牵扯”,一个小小的变更有时跟数个模块有千丝万缕的联系,所以,2000年开始,亚马逊电商从一个单体应用拆分成了各种微服务,贝索斯的内部的一份邮件确定了归属不同团队的服务应该按照“契约”接口来通信,而且是唯一的通信方式,不允许绕开直接访问对方的数据库;因此一个个边界清晰的的小团队加微服务才能适应业务的快速发展和研发团队人数不断膨胀的挑战。

应用架构从单体到微服务的演进本身是业务快速发展的一个自然结果,拆分服务和团队边界越清晰,解耦做的越好,适应变化和弹性能力越大。

应用架构变迁

Netflix 内容制作工程师团队的挑战

Netflix 的自制剧逐年增长,团队需要构建提升整个创作过程效率的应用程序,支撑初期的编剧,到推广再到上线点播的整个过程,比如收购剧本,交易协商,供应商管理,日程安排,流媒体制作工作流等等。从一年前也就是 2019年,工程师团队开始着手从头开发一个跨多个业务的新应用,所以面临一个有趣的挑战,_新应用依赖于目前分布在许多不同系统的数据。_

比如,一些关于电影,制作日期,员工,拍摄地点等元数据,这些数据来自不同系统,需要通过不同的协议对接,包括 gRPC、JSON API、GraphQL接口等等,现有的数据对我们新的应用的业务逻辑和行为至关重要,一开始就需要高度集成。

流媒体内容创作过程

团队早期的应用都是单体架构,单体架构可以满足业务初期快速发展需求,最多得时候,该单体应用有 30多个开发人员,超过 300个数据库表。

随着时间的推移,应用程序从提供广泛的服务朝高度专业化演进。 这导致团队决定将单体分解为很多个特定业务域的微服务,这一决定并非出于性能考量,而是围绕所有这些不同业务领域划分,并使专门团队能够独立开发特定于业务领域的服务。

我们新应用程序需要的的大量数据仍然是由单体应用提供的,但我们知道该单体应用在某个时候会被分解,我们不确定拆分的具体时机,但我们知道这是不可避免的,我们需要做好准备。

因此,我们一开始就可以利用来自单体应用的一些数据,因为它仍然是可靠数据的来源,但准备好数据源切换架构设计,当拆分出来新的微服务上线后立刻着手切换。

引入六边形架构

六边形架构,相对于传统的多层架构,所有的依赖都指向内部,而内部是我们最核心的业务逻辑,对于外层的数据源实现或传输层细节一无所知。

六边形架构

从技术上,需要支持在不影响核心业务逻辑的情况下切换数据源的能力,因此需要分离关注点,团队决定基于六角架构原则构建我们新的应用程序。

六边形架构的想法是将输入和输出放在我们设计的边缘,业务逻辑不应该受限于我们是 REST 还是 GraphQL 接口,它不应该和我们从哪里获取数据进行耦合 ———— 来自一个数据库,通过 gRPC,REST API,或者只是一个简单的 CSV 文件。

该模式允许团队将应用程序的核心逻辑与外部问题隔离开来,隔离核心逻辑意味着我们可以轻松更改数据源详细信息,而不会对核心代码库产生重大影响或重写。

这样一个有明确边界的架构设计带给我们另外一个优势是测试策略 ———— 我们的大多数测试都可以不依赖于多变的协议接口来验证测试我们的业务逻辑。

定义核心原则

利用六边形架构,梳理出我们业务逻辑核心需要三个主要概念:实体(Domains)、存储库(Repositories)和交互器(Interactors):

  • 实体(Domains):业务领域对象(例如,一个电影或一个拍摄地点)———— 实体不知道它们的存储位置(不像 Ruby on Rails 中的 Active Record 或 Java Persistence API )
  • 存储库(Repositories):负责实体的读取、创建和更改实体;它们保留用于与数据源通信的方法列表,并返回单个实体或实体列表(例如 UserRepsitory)
  • 交互器(Interactors):是协调和执行领域事件(操作)的类 ———— 想象成 应用服务(Service Object) 或 用例服务(User Case Objects);他们实现复杂的业务规则和特定领域操作的验证逻辑(比如,启动一个内容制作)

通过这三种主要对象类型,团队能够定义业务逻辑,而无需关心数据如何保存,以及如何触发业务逻辑,除了业务逻辑之外,还有数据源和传输层:

  • 数据源是不同数据存储实现的适配器

数据源可能是 SQL 数据库的适配器(Rails里面的 ActiveRecord 或 Java JPA )、弹性搜索适配器、REST API,甚至是更简单的实现(例如 一个CSV 文件), 数据源实现在存储库上定义的方法,定义获取和保存数据的实现。

  • 传输层可以触发交互器来执行业务逻辑

我们将其视为我们系统的输入,微服务最常见的传输层是 HTTP API 和一组用于处理请求的控制器,通过将业务逻辑提取到交互器中,我们不会耦合到特定的传输层或控制器实现,交互器不仅可以由控制器触发,还可以通过事件、定时任务作业或命令行触发。

Netflix 的六边形架构

使用传统的分层架构,所有的依赖关系指向一个方向,上面的每个层取决于下面的层,传输层将取决于交互者,交互者将取决于持久性层。

切换数据源

切换数据源的需求比预想的要早 ———— 有一天突然触发了单体应用的读取限额,需要将一个实体的特定读取切换到暴露在 GraphQL 聚合层上新的微服务,该微服务和单体保持同步,并具有相同的数据集,从一个服务切换到另一个服务读取产生相同的结果。

Netflix 团队可以做到在 2小时内将数据接口从 JSON API 转换到 GraphQL 暴露的数据源。

之所以能够这么快切换的主要原因是六边形架构,我们没有让任何持久性细节暴露到我们的业务逻辑中,我们创建了一个实现存储库接口的 GraphQL 数据源。,一个简单的单行更改就是我们开始从不同的数据源读取所需的一切。

关于单行更改的重要意义是它减少了新版本发布的风险,在初始部署时,下游微服务失败的情况下,很容易回滚,这也使团队能够分离功能部署和激活,因为团队可以决定通过配置使用哪个数据源。

隐藏数据源实现细节

这种架构的最大优势之一是我们能够封装和隔离数据源实现细节。

团队遇到了一个情况,有新业务需求,需要一个尚不存在的 API 调用 ———— 一个下游服务有一个获取单个资源的接口,但没有实现批量获取接口。在与提供 API 接口团队沟通之后,我们意识到这个新的接口需要一些时间来交付。 因此,需要决定在该接口还在构建过程中的时候采用另一种方案来解决问题。

团队定义了一个存储库方法,该方法将通过给定的多个记录标识符一次获取多个资源 ———— 并且该方法在数据源上的初始实现将多个并发调用发送到下游服务;团队都知道这是一个临时解决方案,数据源实现的第二个考虑是在实现后使用批量 API 接口。

对业务屏蔽的实现细节

这样的设计使团队能够继续满足业务需求,而不会产生太多技术债务或随后改变业务逻辑的需求。

测试策略

当开始试验六边形架构时,除了开发,还需要需要一个测试策略。

敏捷开发的先决条件是拥有一个可靠和超快的自动化测试流水线,这是一个必备的而不是可选的。

团队决定在三个不同层次上测试我们的应用程序:

  • 测试应用的交互者(Interactors),这是业务逻辑的核心所在,但独立于任何类型的持久化实现或传输协议,我们利用依赖注入并模拟任何类型的存储库交互;由于涉及业务逻辑,需要详细测试,也是团队努力做的大部分测试。

  • 测试应用的数据源(Data Sources),以确定它们是否与其他服务正确集成,它们是否符合存储库接口,并检查它们在故障或错误时的行为,但尽量最小化这些测试用例的数量。

  • 整个堆栈的集成规范,从传输 / API 层到交互器、存储库、数据源以及下游服务, 这些规格测试我们是否正确地 “连接“ 一切。如果数据源是外部 API,我们调用该服务接口并记录响应(并将它们存储在 Git 代码库中),从而允许测试工具在每次后续调用时都能快速运行,团队不会在这个层面上进行广泛的测试覆盖 ———— 通常针对某个领域操作只有一个成功场景和一个失败场景。

测试策略

团队不测试存储库,因为它们是定义数据源实现的简单接口;也很少测试实体,因为它们是定义属性的普通对象,但会测试实体是否有其他方法(不涉及持久化层)

依然有改进的余地,例如不要 Ping 应用依赖的任何服务,而是 100% 进行契约测试。 通过以上方式编写的测试套件,团队可以做到在一个进程中每 100 秒运行大约 3000 个规格测试。

使用一个可以轻松在任何机器上运行的测试套件非常令人欣慰,开发团队可以在不受干扰的情况下开发他们的每天的新功能。

推迟作出决定

当涉及到切换不同数据源实现场景,这块利用六边形架构已经可以很好的进行处理。 一个关键好处是,团队可以延迟决定是否以及如何将数据存储到新应用内部;根据具体的用例,团队甚至可以灵活地确定数据存储的类型————无论是关系数据库还是 NoSQL/文档型数据库。

在项目开始时,关于正在构建的系统的信息我们了解最少,因为缺少真正用户的反馈, 团队不应该把自己限制在某种特定架构中,因为缺乏足够信息的决定可能导致一个似是而非的结论。

Netflix 团队作出的决定符合现在的需求,并使团队能够迅速行动;六边形架构带来的最大好处是它保持应用灵活性,以满足未来的需求。

总结:应对变化,架构先行

应用是以交付业务结果为目标,当单体不再适合,当团队需要更好的分工协作,重构应用甚至是研发团队,在所难免,我们不倡导过渡设计,但如今同样的成本投入,在云上可以有更多的架构可能,快速试错;

很多时候,作为用户,基础设施比如云服务不是你能控制的,但架构设计和优化却是团队可以主动选择和演进的,好处在于云服务本身后面的小团队也如你们一样在努力不断迭代升级,应对变化,迎接挑战,从优良架构开始。


公众号二维码

诞生于 2019,遇见 2020。

感谢关注,欢迎动动手指标星和置顶;

这样就不会错过少但精彩的技术探讨、团队建设、案例分享!

每周至少一更,转发是对我的最大鼓励!

学习之路漫漫,走走停停,
偶有所感,随心所记,
言由心声,问心无愧!

从客户中来,到客户中去!