Fork me on GitHub

如何在不破坏团队的情况下拆分单体系统

动态组织团队拓扑(1)

Posted by Kaelzhang on August 4, 2020

本文源自《ORGANIZATION DYNAMICS WITH TEAM TOPOLOGIES》

如何在不破坏团队的情况下拆分单体系统

许多组织认为可以将现有的软件系统分成较小的组件来尝试提高业务敏捷性,认为这将能够支撑更安全,更快速的变更。 但是,当从单体系统转换至松散耦合服务时,必须考虑新的架构将如何影响构建软件的关联团队。

在不考虑团队视角场景下,可能会承担将单体拆分到错误位置的风险,甚至可能创建复杂的,耦合的,无法维护的混乱代码,即所谓的“分布式单体”。

当我帮助组织将大型系统分解为较小的服务时,我采取的方法是从团队维度开始拆分,而非从技术维度拆分。 下面是一些常见的模式和技术,希望对你有用。 但是首先让我们退后一步。

什么是单体?

单体这个字面意思是“巨石”,在希腊语中是一大块东西,很重,很难使用。 在软件世界中,有很多不同种类的整体,每种都需要使用不同的方法来将其分解。 以下是六种较常见的单体类型:

  1. 单体应用:具有多个相关性和职责的单个大型应用,可能会暴露许多服务或不同的用户旅程。
  2. 共享数据库:多个应用程序或服务都耦合到同一数据库模式,因此难以变更。
  3. 单体构建:一个巨大的持续集成(CI)仅为完成任一组件的新版本的获取。
  4. 单体发布:将较小的组件打包到一起进行一次“发布”。
  5. 单体模型:尝试在许多不同上下文中实现语言和模型(表示)的一致性。 “每个人都可以处理任何事情”,从而导致领域模型不一致或泄漏。
  6. 单体思维:一种千篇一律的团队思维,导致团队之间的技术和实施方法受到不必要的限制。

这并非一份详尽的清单。 可能还有其他单体类型(或耦合过紧)。 因此,在开始拆分整体之前,请先确定要处理的是哪种,然后花时间进行良好的解耦。

一些组织已经花费时间和精力将单体应用拆分为微服务,而在部署流水线的更后阶段仍进行单体发布,这浪费了更快,更安全地迁移机会。 为避免创建下游单体,请始终注意上面列出的各种不同的单体类型。

使组织架构与软件架构匹配

多项研究证实康威定律的核心观点:“设计系统的架构受制于产生这些设计的组织的沟通结构。”在实践中对此有很多微妙之处,但可以归结为:如果团队之间的相互沟通协作不能反映出软件组件之间的实际或预期的交互通信,则该软件将难以构建和运行。

可以使用“ Reverse Conway”(更改团队结构以匹配所需的系统架构)以及诸如域驱动设计(DDD)和代码取证等技术来重塑团队职责,以便与需生产的软件架构保持一致,以清晰边界并改善系统的开发和运行。但如何知道在哪里安全地分割单体呢?

通过代码取证识别隐含耦合

通过将单体代码拆分为较小的服务,可能能使责任边界更加清晰,尽管这不会自动发生。 可以使用诸如Codescene和Code Maat之类的工具来分析代码库,并且不仅可以检测如循环依赖和静态耦合之类的标准代码度量,而且还可以检测时态耦合-代码库中明显无关的文件经常同时变化,如存储库历史记录中所示。

图1. Codescene的时态耦合分析

Codescene和Code Maat的作者Adam Tornhill在他的《代码即犯罪现场》中解释了如何使用警察取证技术来分析和理解代码库的演变。 例如:“信息不足的抽象名称会导致额外的(非必要的)责任。”命名的方式确实很重要,因为选择不当的名称往往会增加额外的代码,从而使软件难以使用。 Tornhill的新书《软件设计X射线:使用行为代码分析修复技术债务》对这些想法有更加深入描述。

团队的认知负荷决定子系统的大小

对于安全的单体拆分,至关重要的是考虑与软件一起工作的每个团队的认知负担。根据心理学家约翰·斯威勒(John Sweller)的定义,认知负荷是“在工作记忆中所使用的脑力劳动量”。因此,认知负荷在需要智力的敏捷活动(例如软件开发)中很重要。

一个软件团队的最大有效规模约为9人,任何给定团队的最大认知负荷是所有团队成员的能力总和。一个团队的最大认知负荷会与其他团队不同。例如,一个经验丰富的工程师团队将比一个经验不足的团队具有更高的认知负荷。但是,每个子系统仍然有一个最大有效大小,并且比许多软件单体要小。

这意味着应将每个子系统的大小限制为不超过组成该子系统的团队的认知负荷。没错:软件的大小和形态应由团队的最大认知负荷来确定。通过从每个团队的需求入手,可以推断出最适合团队成员的软件和系统架构。

单体拆分秘诀

现在,你已经考虑了Conway定律,使用Code Maat或类似工具来分析代码库的时态耦合,并限制了每个子系统的最大大小以匹配每个团队的认知负荷,接下来就可以着手拆分单体了。但是首先进行一些假设验证。

你确定现有的单体能够按预期工作吗?包/命名空间/模块之间的内部职责是否整洁且定义明确?当从进程内调用转移到跨计算机HTTP调用时,一些细微错误是否会变得十分严重?

要回答这些问题,需要使用现代的日志记录,跟踪和度量技术对代码进行检测,以生成有关软件在运行时的确切工作方式的丰富数据。具体来说,可以从日志记录中使用事件ID技术来检测代码中发生的意外操作和状态以及调用链追踪。可以使用诸如OpenTracing或Zipkin之类的工具和/或应用程序性能监视(APM)工具来检测在请求或执行路径中的确切代码路径。

这些技术可能会突显子系统意外通信的区域,或者发现未检测到的故障情况。在拆分代码之前,请解决这些问题。否则,当转换到分布式微服务模式下,这些额外的调用和错误的问题将会加剧。

图2. 使用日志记录和跟踪来检测单体中的意外的调用和故障

修复所有不必要的调用和故障后,就可以开始将单体的内容与DDD中称为“业务域限界上下文”的内容进行对齐,DDD指明功能一致且术语一致的片段负有单一职责,例如付款或准备文件。在可能的情况下,请按DDD限界上下文来拆分。

但是有时需要替代方法来拆分(我称之为“断裂面”),例如按技术或风险分割。例如,为了符合法规要求(例如PCI-DSS),可能需要沿数据边界拆分。同样,为了帮助实现性能隔离(例如,对于大量票务预订系统),可能需要从技术层面拆分预订流程。

然后,您可以逐段拆分新的团队协作子系统或服务,每次查看丰富的日志数据和指标数据时,都可以使用它们来验证关于拆分代码前后软件工作方式的假设。你应该:

  1. 使用日志记录,跟踪和度量指标对单体进行检测。
  2. 了解数据流和故障响应并解决所有问题。
  3. 根据合适的断裂面将团队对准可用的片段。
  4. 逐段拆分,并使用日志记录和度量指标来验证变更。

从单体中分离出一个片段之后,请确保新的片段在每个区域都具有独立性,包括一个单独的版本控制存储库,一个构建和部署流水线,以及可能是单独的服务器(如果您使用的是虚拟机)或pods(使用Kubernetes时)。新的片段独立于单体和其他片段,使负责每个片段的团队能够独立工作。

行动起来:如何开始

从单体演进到较小的,分离的服务,可实现更快,更安全地发布。但是要避免造成复杂的混乱分散,请首先考虑团队如何构建和运行新服务。顺应康威定律,团队之间的沟通将推动新的,解耦的架构,并且新的服务不应超过每个团队的认知负荷。

首先,明确要处理的单体类型。然后,在拆分代码之前,使用代码取证工具(例如Code Maat)来识别时态耦合。使用现代化日志记录,跟踪和度量工具识别意外的调用和故障。

只有这样,才能在代码中识别出用作分割边界的可行断裂面。最后,逐步分割,在每个阶段使用日志记录和度量指标来验证系统行为假设。

参考文献

  1. https://techbeacon.com/app-dev-testing/how-break-apart-monolith-without-destroying-your-team

  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!