团队拓扑:模块化与划分团队相结合


Martin Fowler的同事Matthew Foster描述了团队拓扑和领域驱动设计如何帮助组织扩展技术架构和团队结构,从而显着提高开发速度。

模块化架构能改善软件交付吗?
是的!但要注意一些问题。

这篇文章记录了一个企业的历程,他们为了减轻成长的痛苦,开始将自己的架构转变为更加模块化的架构。

他们发现,模块化是一个多方面的解决方案,它超越了架构,延伸到了业务沟通线、团队拓扑结构和有效的开发人员经验。
通过对这些因素的密切关注,该企业能够在其移动应用程序的交付性能方面取得重大提升。

在这篇文章中,我认为模块化架构的特点是有三个预期的好处:

  • 功能的封装
  • 通过适当的接口来抽象复杂度
  • 通过可互换性/重复使用实现可选择性

这篇文章描述了我们的一个客户在试图解决这些问题时的历程。我们讲述了他们的组织在过去是如何倾向于正确的解决方案的,但由于对这些解决方案的内在联系有误解,所以无法看到预期的好处。

我们通过讲述同一组织如何通过改变团队拓扑结构以匹配模块化架构,同时投资于开发人员的经验,从而实现平均周期时间减少60%,开发成本提高18倍,团队启动成本减少80%的观察。

识别征兆
尽管有最好的意图,但软件往往会随着时间的推移,在质量和性能方面都会恶化。

业务功能需要更长的时间才能进入市场,服务中断变得更加严重,需要更长的时间来解决,其结果往往是那些在产品上工作的人变得沮丧和不自信。

这其中有一些可以归咎于代码和它的维护。

然而,将责任完全归咎于代码质量,对于一个多层面的问题来说,感觉太天真了

在产品决策、康威法则、技术债务和固定架构的复杂相互作用下,劣化往往会随着时间的推移而增长。

在这一点上,介绍本文所围绕的组织似乎是合乎逻辑的。作为一家大型企业,这家企业一直在经历着将新功能引入其零售移动应用程序的时间逐渐延长的情况。

作为起步阶段,该组织正确地将他们所经历的摩擦归因于随着他们的应用程序的增长而增加的复杂性--他们现有的开发团队努力增加与现有功能保持一致的特性。

他们最初的反应是 "增加更多的开发人员";这在一定程度上确实对他们有效。

然而,最终变得很明显的是,增加人员是以更紧张的沟通为代价的,因为他们的技术领导开始感觉到协调费用的增加。

因此,在亚马逊提倡 "两个比萨 "的微服务规则:任何团队都应该小到可以用两个比萨饼来养活。
微服务理论认为,通过限制一个团队的规模,你可以避免出现沟通管理比实际价值创造花费更多时间的情况。

这是一个合理的理论,而且对亚马逊很有帮助。然而,当考虑到现有的团队已经变得太大,有一种倾向是对亚马逊 "货物崇拜 ":

货物崇拜
起源于第二次世界大战期间的太平洋群岛。原住民目睹了先进的军事力量通过飞机带来宝贵的物资。他们相信这些货物是仪式和摆道场做法的结果,于是创造了模仿品,希望能吸引同样的丰收。然而,他们的努力是徒劳的,因为他们对运送物资背后的真正机制缺乏了解。

限制认知负担
事实上,这个组织也不例外:他们曾经的小单体已经变得越来越成功,但随着功能、职责和团队成员的增加,也无法复制所需的成功率。随着功能交付期限的逼近和多个品牌市场的前景,他们的对策是将现有的团队拆分成多个更小的、相互联系的子团队--每个团队都是孤立的,管理一个单独的市场(尽管客户的旅程相似)。

事实上,这使事情变得更糟,因为它把沟通的责任从技术领导层转移到了实际的团队本身,同时没有减轻他们不断扩大的环境负荷。

意识到沟通和协调正在消耗那些负责实际价值创造的人越来越多的时间,我们最初的建议涉及Skelton和Pais(2019)所概述的 "认知负荷限制 "的想法。

这涉及到团队在单一复杂或复杂领域的分离。

这些软件内部的缝隙可以用来制定上述的'两个比萨饼大小的团队'。
其结果是每个团队的开销大大减少:动机上升,任务陈述更清晰,而沟通和背景转换则缩减到一个单一的共享焦点。

从理论上讲,这是对我们客户问题的一个很好的解决方案,但如果孤立地考虑,实际上会产生误导。

只有当一个应用程序的领域边界真正被很好地定义并在代码中得到一致尊重时,才能真正实现认知负荷限制的好处。

领域驱动的纪律
领域驱动设计(DDD)对于将复杂的逻辑组织成可管理的组并为每个组定义共同的语言或模型很有用。然而,将一个应用分解成领域只是一个持续过程的一部分。

严格控制有边界的上下文界限与定义领域本身一样重要。

在检查我们客户的应用程序的代码时,我们遇到了一个常见的陷阱:最初的投资很明确,正确地定义和组织了领域的责任,但随着应用程序的发展,这种纪律性开始被削弱。

来自利益相关者的传闻表明,在紧急产品需求的驱动下,长期忙碌的团队走捷径已经成为团队的常态。这反过来又导致了由于技术债务的积累而导致的价值交付的逐步放缓。

由于发布代码越来越难,调试问题也越来越难,应用程序的四个关键指标出现了可衡量的下降趋势,这进一步突出了这一点。

通过普通的代码分析工具,我们发现了管理不善的上下文界限的进一步警告信号:

  • 我们发现一个代码库已经发展成为紧密耦合的,缺乏凝聚力的。高度耦合的代码很难在不影响系统的其他部分的情况下进行改变。
  • 内聚力低的代码有许多责任和关注点不在其职权范围内,这使得人们很难理解其目的。

随着时间的推移,这两个问题都变得更加严重,因为我们客户的应用程序中每个领域的复杂性都在增加。

其他迹象表明,认知负荷也是如此。

应用程序中各领域之间不明确的界限或依赖关系意味着,当一个领域发生变化时,很可能会不由自主地影响到其他领域。
我们注意到,由于这个原因,开发团队需要了解多个领域的知识来解决任何可能出现的问题,从而增加了认知负荷

对于组织来说,对每个领域范围的严格控制是一个进步,以确保知识和责任都在同一个地方。这导致了任何变化的 "爆炸半径 "的限制,包括工作量和所需的知识。

此外,对技术债务的累积和处理进行更严格的控制,确保任何短期的 "领域漏洞 "在其发展之前就能被拒绝或纠正。

该组织的移动应用中缺少的另一个指标是重用的可选择性。
如前所述,有多个现有的、成熟的品牌市场应用。这些应用程序之间的功能一致性很低,而且由于对单个市场自主权的渴望,统一成一个单一的移动应用程序的意愿很困难。

整个系统的紧密耦合降低了在其他地方重复使用领域的能力:
为了在另一个市场上重用一个领域,不得不移植大部分现有的移动应用程序,这带来了高额的整合和持续管理成本。我们利用适当的以领域为界限的上下文控制,通过阻止对其他域的直接依赖,向模块化迈出了良好的第一步。

但我们发现,这并不是我们需要采取的唯一行动。

场景1:整洁的单体
当被视为一个孤立的单一应用时,简单地将应用分割成领域,分配给一个团队,并管理他们的耦合(以便不违反他们的边界环境),效果非常好。

以一个单独的应用程序的功能请求为例:

这个功能请求被传递给拥有相关领域的应用团队。我们严格的有边界的上下文意味着我们的变化的爆炸半径包含在其内部,这意味着我们的功能可以被建立,测试,甚至部署,而不需要改变我们应用程序的另一部分。我们加快了我们的上市时间,并允许多个功能同时孤立地开发。

的确,这在单一的市场背景下运作良好。

然而,一旦我们试图解决我们的第二个扩展问题--由于缺乏复用性而产生的市场功能差异--我们就开始遇到问题了。

情景2:下一个市场机会
该组织在寻求领域模块化方面的下一步是通过将 "整齐的单体 "的一部分移植到现有的市场应用中来实现快速的开发节约。

这涉及到创建一个共同的框架(我们将在后面谈到的方面),允许功能/领域在移动应用中被重复使用,而不是在从零开始。

为了更好地说明我们的方法,下面的例子显示了两个市场应用,一个在英国,另一个是基于美国的新应用。
我们在美国的应用团队决定,除了他们在美国的特定域名外,他们还想利用忠诚度积分和结账域名作为他们应用的一部分,并已将它们导入。

复用比起传统的重写领域功能的行为要节省一个数量级的开发费用。

然而,这并不是故事的结束--在我们急于走向模块化的过程中,我们没有考虑到该组织现有的沟通结构,这些结构最终决定了工作的优先次序。

发展我们之前的例子作为解释的手段:在他们自己的市场上使用了这些领域之后,美国团队在他们的一个进口领域里有了一个新功能的想法。但是他们并没有拥有这个领域的上下文的修改权限,所以他们联系英国的应用团队并提交了一个功能请求。英国团队接受了这个请求,并坚持认为这听起来是 "一个伟大的想法",只是他们目前正在 "处理来自英国的利益相关者的请求",所以不清楚他们何时能够进行这项工作......

我们发现,这种优先考虑领域功能的利益冲突限制了共享功能的消费者可以期待的重用量。
导致市场团队对进口领域缺乏进展感到沮丧时很明显。

我们在理论上提出了一些解决问题的方法:
共享功能模块的消费使用团队也许可以分叉他们自己的领域版本,并围绕它协调一个团队。

然而,正如我们已经知道的那样,学习/拥有整个领域来增加少量的功能是低效的,而且分叉也给未来的升级共享或市场之间的功能平等带来问题。

我们研究的另一个方案是通过拉动请求来贡献:
然而,这给贡献团队带来了自己的认知负担--迫使他们在第二个代码库中工作,同时仍然依赖于主域团队的跨团队贡献支持。例如,不清楚领域团队在他们自己的市场功能开发之间是否有足够的时间来提供架构指导或PR审查。


情景3:市场不可知领域
显然,问题在于我们的团队是如何组织起来的。

康威定律是这样观察的:一个组织将设计其业务系统以反映其自身的通信结构。

我们之前的例子描述了这样一种情况:从技术角度来看,功能是模块化的,但是从所有权的角度来看,仍然是单一的:
"忠诚度积分最初是为英国的应用程序创建的,所以它属于那个团队"。

对这种情况的一个潜在反应是在 "反康威手法 "中描述的。
这涉及到改变开发团队的结构,使其能够出现所选择的技术架构。

在下面的例子中,我们从之前的方案出发,对我们的团队进行结构性改变,以反映我们之前的模块化架构。

领域从具体的移动应用中抽象出来,而成为自主的开发团队本身。

当我们这样做时,我们注意到应用程序团队之间的关系发生了变化,因为他们不再对市场之间的功能有依赖性。
取而代之的是,我们发现新的关系正在形成,在消费者和提供者方面得到更好的描述。
我们的领域团队为他们的市场客户提供功能,而他们反过来消费这些功能,并反馈新的功能要求,以更好地开发领域产品。

与我们之前的迭代相比,这次重组的主要优势在于明确了重点。
早些时候,我们描述了当一个市场提出改变来自另一个市场的领域的请求时发生的利益冲突。
将一个领域从它的市场中抽象出来,改变了重点,从仅仅为了市场的利益而建立任何功能,到建立满足其消费者需求的功能的更全面的任务。
成功的衡量标准是消费者的接受程度和终端用户的接受程度。任何新的功能都只根据它给这个领域和消费者带来的价值量来审查。

注重开发人员的经验,支持模块化
回顾一下,该组织现在有一个拓扑结构,支持跨市场的组件的模块化。

  • 自主的研发团队被分配到拥有和开发的领域。
  • 市场部署应用被简化为配置容器。

在概念上,这一切都很有意义--我们可以很容易地描绘出反馈如何从消费者流向提供者。我们也可以做出高水平的乌托邦式理想化的假设,比如:"所有的领域都是独立开发/部署的 "或者 "消费者'只是'拉入他们想要的任何可重用的领域来形成一个应用程序"。

然而,在实践中,我们发现这些都是难以解决的技术问题。

  • 例如,你如何在自主领域团队之间保持一定程度的用户体验/品牌一致性?
  • 当你只负责整体应用的一部分时,你如何实现移动应用的开发?
  • 你如何允许领域的可发现性?可测试性?跨市场的兼容性?

解决这些问题是完全可能的,但却带来了自己的认知负担,在我们目前的结构中,这个责任没有任何明确的主人。

所以我们做了一个!


一个解决中心问题的领域
我们的新领域被归类为 "平台"。

该平台基本上是一个包罗万象的术语,我们用来描述工具和指导,使我们的团队能够在所选择的架构中独立交付。

我们的新领域团队保持着我们已经看到的提供者/消费者的关系,并负责改善在平台内构建其应用程序和领域的团队的开发者体验。

我们假设,更强大的开发者体验将有助于推动我们新架构的采用。

但是 "开发者体验"(DX)是一个相当不具体的术语,所以我们认为有必要定义我们的新团队需要提供一个好的体验。我们将DX领域细化为一系列必要的能力--首先是高效引导。

对于任何通用的框架,都有一个不可避免的学习曲线。一个好的开发者体验旨在尽可能地减少该曲线的严重性。合理的默认值和启动包是减少入职时感受到的摩擦的一种非官方方式。我们为我们的平台领域定义了一些例子:

我们承诺:

  • 你将能够在一个命令中快速生成一个新的域,包括所有相关的移动依赖、通用UI/UX、遥测和CI/CD基础设施
  • 你将能够独立地构建、测试和运行你的域 你的域在捆绑到一个应用程序中时的运行方式与它独立运行的方式相同"

请注意,这些承诺描述了开发者生产力平台中的自助服务体验的要素。因此,我们认为一个有效的开发者平台是允许那些专注于终端用户功能的团队专注于他们的任务,而不是在看似无止境的非生产性任务列表中拼命工作。

我们为平台领域确定的第二个必要能力是技术架构即服务。在组织中,架构功能也遵循康威法则,因此,架构决策的责任被集中在一个单独的筒仓中,与需要指导的团队脱节。我们的自主团队虽然能够做出自己的决定,但往往需要某种程度的 "技术指导",以便在原则、模式和组织管理上保持一致。当我们把这些要求推断为一种按需服务时,我们创造了一些东西,看起来像:

我们承诺:

  • 我们提供的最佳实践将伴随着你可以使用的例子或你可以采取的实际步骤
  • 我们将维护每个应用的域名使用情况的总体情况,并在需要时协调各垂直领域的合作。
  • 通往生产的道路将是可见的和正确的
  • 我们将与你一起工作"

请注意,这些承诺描述了对团队的仆人式领导关系,认识到每个人都要对架构负责。这与一些人可能描述为命令和控制的架构治理政策形成对比。

关于平台领域的最后一点,也是值得从前面的例子中重新审视的一点。
根据我们的经验,一个成功的平台团队是一个深深扎根于客户需求的团队。

在丰田精益生产中,"Genchi Genbutsu "大致翻译为 "自己去看"。其意思是,通过访问问题的源头并亲眼看到它,你才能知道如何解决它。

我们了解到,一个专注于改善开发者体验的团队必须能够与使用其产品的开发者产生共鸣,以真正了解他们的需求。当我们第一次创建平台团队时,我们没有给予这个原则应有的重视,只是看到我们的自主团队找到了自己的方式。这最终导致了工作的重复、不兼容和对架构的不信任,需要时间来纠正。


结果
我们已经讲述了我们如何将一个移动应用模块化的故事,但随着时间的推移,它的成功程度如何?
获得经验性的证据可能很困难。

根据我们的经验,在同一个组织内,使用相同的领域,有一个遗留的应用程序和一个新架构的应用程序,并为两者提供交付指标,这种情况并不常见。

然而,幸运的是,在这个例子中,该组织足够大,可以一次过渡到一个应用程序。
对于这些结果,我们比较了两个功能相似的零售应用:

  • 一个是具有高耦合性和低内聚力的遗留问题,尽管有一个高产和成熟的开发团队("遗留单体")。
  • 另一个是我们之前描述的模块化重构工作的结果--一个定义和管理良好的有边界的上下文,但有 "较新的 "个人领域团队支持("有边界的领域应用程序")。

周期时间在这里是一个很好的衡量标准,因为它代表了在代码中 "做出 "改变所需的时间:

  • 传统单体花费时间 :   17 days
  • 领域界限上下文Domain Bounded Context (Avg)花费时间:    10.3 days

即使在我们的第二个应用程序中,当周期时间被平均到所有领域的团队时,我们也看到了与传统应用程序相比,经验较少的团队有显著的提升。

我们的第二个比较是关于重复使用的可选性,或者说是缺乏这种可选性。
在这种情况下,我们检查了组织中同样的两个移动应用程序。同样,我们比较了一个需要现有领域功能的应用程序(没有选择,只能自己编写)和我们的模块化应用程序(能够插入和播放现有领域)。

  • 非模块化Non-modular    90 days
  • 模块化Modular    5 days

banq评:可复用的界限上下文(微服务)拓展成为一个平台服务,类似SaaS,好像提高了初期生产效率,但是学习使用这些平台服务本身也成为认知负荷,而且这种消费使用的认知负担比对平台本身的领域知识学习带来的认知负担更重,该文没有从认知负担是否降低还是变得更严重角度评测。

只有一句话能说清楚的功能可以复用为平台,而业务功能通常不是一两句话说清楚的,有很长窗口的上下文背景知识,这些上下文背景知识就成为限制使用该业务功能的前提条件,所以,作为使用消费者,必须了解这些前提条件。