DDD专家张逸:复杂与架构演进的关系

张逸

读完需要

25

分钟

速读仅需 5 分钟

张逸,架构编码实践者,IT 文艺工作者,大数据平台架构师,兼爱面向对象与函数式程序设计,热衷于编程语言学习与技艺提升,致力于将主流领域驱动设计(DDD)与函数式编程、响应式编程以及微服务架构完美结合。个人微信公众号:逸言。个人博客:  (http://iamzhangyi.github.io)

复杂的事物让人着迷,繁复、多样、无序以及其中蕴含的无穷变化或许也是我觉得软件设计有趣的地方。由于设计的复杂性,我在每次面临不同的项目、不同的产品时,油然而生一种耳目一新的感觉,似乎重启了新的旅程,风景不同,心境自然也就不同了。

然而,复杂并不总是令人感到有趣,除非我们具有掌控复杂的能力。

那么,什么是复杂/p>

1

   

什么是复杂

Jurgen Appelo 在分析复杂系统理论时,将 Complicated 与 Complex 分别放在理解力与预测能力两个迥然不同的维度上。Complicated 与 Simple(简单)相对,意指非常难以理解, 而 Complex 则介于 Ordered(有序的)与 Chaotic(混沌的)之间,意指在某种程度上可以预测,但会有很多出乎意料的事情发生,如图 4.1 所示。

大多数软件系统是难以理解的,虽然我们可以遵循一些设计原则来应对未来的变化,但由于未来是不可预测的,因而软件的演进其实存在不可预测的风险。如此看来,软件系统所谓的“复杂”其实覆盖了 Complicated 与 Complex 两个方面,等同于图 4.1 中城市所处的位置。凑巧的是,Sam Newman 也认为城市的变迁与软件的演化存在很大程度的相似性:

DDD专家张逸:复杂与架构演进的关系图 4.2

这个模块的代码行数达到了四十多万行,其中重复代码竟然达到了惊人的 33.9%,超 过一半的代码文件混入了重复代码。显然,这里估算的代码行数并没有真实地体现软件规模,相反,因为重复代码的缘故,可能还额外增加了软件的复杂度。

Neal Ford 在文章 Emergent design through metrics 中谈到了如何通过指标来指导设计。文中提及的 iPlasma 是一个用于面向对象设计的质量评估平台,或许我们可以通过该工具的指标(见表 4.1)来找到评价软件规模的要素。

表 4.1 iPlasma 的指标及说明

DDD专家张逸:复杂与架构演进的关系图 4.3

要理清这种通信网结构的脉络,就得弄清楚子系统之间的消息传递方式,明确消息格式的定义;同时,这种分布式的部署结构,在实现这些功能的同时,还必须额外考虑跨进程通信可能出现的异常场景,例如如何确保消息的可靠传递,如何保证数据结果的一致性。换言之,系统因为结构的繁复而增加了复杂度。

微服务的最终一致性

基于 CAP 理论,微服务这种分布式架构在满足 A(Availability)与 P(Partition Toralence) 的前提下,至少要保证数据的最终一致性,即系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。

分布式架构的通信特点让我们必须要认为网络通信是不可靠的,这就导致在实现一致性上,微服务比传统的单体架构要复杂得多。假如采用补偿模式来实现数据的最终一致性, 就需要引入一个额外的协调服务,它负责协调各个需要保证一致性的微服务,其职责为协调服务并按顺序调用各个微服务,如果某个微服务调用异常(包括业务异常和技术异常), 就取消之前所有已经调用成功的微服务。同时,还需要考虑取消操作也可能失败的情况, 即补偿过程本身也需要满足最终一致性,这就要求在服务调用出现异常后,取消服务至少要被调用一次,而取消服务操作本身则必须是幂等的。

为了实现补偿模式,我们需要记录每次业务操作,同时还要确定失败的步骤与状态, 以便于定位补偿的范围。为了提高正常业务操作的成功率,还需要在设计时考虑引入重试 机制。服务执行失败的原因各有不同,重试机制也需要提供与之对应的策略。例如对于系 统繁忙的异常,我们应采用等待重试机制;对于一些出现概率非常小的罕见异常,可以考虑立刻重试;如果失败原因是由于某种业务原因导致的,那么即使重试也不可能保证操作成功,应采取终止重试策略。显然,这些机制都会因为微服务的分解而带来设计上的额外成本,它必然会导致整个系统的结构变得更加复杂。有得必有失,软件世界的自然规律其实是公平的。

在考虑微服务设计时,业界普遍认为服务分解与组织结构要保持一致,即遵循康威定律:

任何组织在设计一套系统(广义概念上的系统)时,所交付的设计方案在结构上都与该组织的沟通结构保持一致。

Sam Newman 认为是“适应沟通路径”使康威原则在软件结构与组织结构中生效 1 的。他分析了一种典型的分处异地的分布式团队,整个团队共享单个服务的代码所有权。由于分布式团队的地域和时区界限,使得沟通成本变高,团队之间只能进行粗粒度的沟通。当协调变化的成本增加后,人们就会想方设法降低协调/沟通成本。直截了当的做法就是分解代码,分配代码所有权,分处异地的团队各自负责一部分代码库,从而更容易地修改代码。团队之间会有更多关于如何集成两部分代码的粗粒度的沟通,最终,与组织结构内的沟通路径匹配所形成的粗粒度 API 形成了代码库中两部分之间的边界。

注意,匹配设计方案的团队是负责开发的团队,而非使用软件产品的客户团队。在软件开发中,常常会遇见分布式的客户团队,例如不同的部门会在不同的地理位置,他们的使用场景也不尽相同,甚至用户的角色也不相同,但在对软件系统进行架构设计时,却不能想当然地按照用户角色、地理位置或部门组织来分解模块(服务),并以为这遵循了康威定律。设计人员错误地把客户的组织结构视为系统模块(服务)的分解依据。

我曾经参与过一款通信产品的改进与维护工作。这款产品为通信运营商提供对宽带网的授权、认证与计费工作。该产品的终端用户主要有两种角色:营业厅的营业员与购买宽带网服务的消费者。

该产品的最初设计就自然而然地按照这两种不同的角色划分为后台管理系统与服务门户两个完全独立的子系统,而在这两个子系统中都存在资费套餐管理、客户信息维护等业务。

这种不合理的软件系统结构划分,属于典型的职责分配不合理,不仅会产生大量重复代码,还会因为结构失当而带来许多不必要的通信与集成,增加软件系统的复杂度。

国际报税系统的架构演进

在我参与的一个国际报税系统中,就根据用户的角色进行了系统分解。针对报税人, 设计了 Front End 模块提供报税等终端业务,而 Office End 模块则面向业务人员和系统管理者,如图 4.4 所示。

DDD专家张逸:复杂与架构演进的关系图 4.5

无论是优雅的设计,还是拙劣的设计,都可能因为某种设计权衡而导致系统结构变得复杂。唯一的区别在于前者是主动地控制结构的复杂度,而后者带来的复杂度是偶发的,是错误的滋生,是一种技术债,它可能会随着系统规模的增大而导致一种无序设计。

在 Pete Goodliffe 讲述的“两个系统的故事:现代软件神话” 中详细地罗列了无序设计系统的几种警告信号:

  • 代码中没有显而易见的进入系统中的路径。

  • 不存在一致性,不存在风格,也没有统一的概念能够将不同的部分组织在一起。

  • 系统中的控制流让人觉得不舒服,无法预测。

  • 系统中有太多的“坏味道”,整个代码库散发着腐烂的气味,是在大热天里散发着刺激气体的一个垃圾堆。

  • 数据很少被放在使用它的地方。

  • 经常引入额外的巴罗克式缓存层,目的是试图让数据停留在更方便的地方。看一个设计无序的软件系统,就好像隔着一层半透明的玻璃观察事物一般,系统中的软件元素都变得模糊不清,充斥着各种技术债。细节层面,代码污浊不堪,违背了“高内 聚、松耦合”的设计原则,导致许多代码要么放错了位置,要么出现重复的代码块;架构层面缺乏清晰的边界,各种通信与调用依赖纠缠在一起,同一问题域的解决方案各式各样, 让人眼花缭乱,仿佛进入了没有规则的无序社会。

架构与代码评审

我曾经为一个制造业客户开发的业务工具项目提供架构与代码评审的咨询服务。当时,该工具产品的代码库只有不到三万六千行代码,是一个简单的基于 ASP.NET 开发的 BS (Brower/Server)架构系统。虽然项目规模并不大,但是在经历了约半年的开发周期后,项 目质量与交付周期都不能得到足够的保证。在之前交付的版本中,位于欧洲的销售代表普遍对这个工具不满意,所以客户希望我们能够在技术层面上提供一些咨询建议。

该工具产品的开发存在诸多问题,例如在领域层充斥着大量的贫血对象,对框架的强依赖导致“供应商锁定”,在技术选型上也多有不当之处。但最大的问题还是系统缺乏清晰的边界,如图 4.6 所示。

DDD专家张逸:复杂与架构演进的关系图 4.7

2. DbHelper 访问方式,其代码如图 4.8 所示。

DDD专家张逸:复杂与架构演进的关系

图 4.9

显然,选择这三种迥然不同的访问方式并非出于技术原因,又或者受到某个质量属性的约束,而是在设计时没有做到统一的规划,开发人员率性而为,内心会自然而然地选择自己最熟悉、实现成本最低的技术方案,从而导致访问数据库的解决方案不一致。

1.3

   

变化

我们之所以不能预测未来,是因为未来总会出现不可预测的变化。这种不可预测性带来的复杂度使得我们产生畏惧,因为不知道何时会发生变化,变化的方向又会走向哪里, 所以导致心里滋生一种仿若失重一般的感觉。变化让事物失去控制,受到事物牵扯的我们便会感到惶恐不安。

在设计软件系统时,变化让我们患得患失,不知道如何把握系统设计的度。若拒绝对变化做出理智的预测,那么系统的设计会变得僵化,一旦变化发生,修改的成本就会非常大;若过于看重变化产生的影响,渴望涵盖一切变化的可能,则一旦预期的变化不曾发生, 我们之前为变化付出的成本就再也补偿不回来了。

从需求的角度讲,变化可能来自业务需求,也可能来自质量属性,而以对系统架构的 影响而言,尤以后者为甚,因为它可能牵涉整个基础架构的变更。George Fairbanks 在《恰如其分的软件架构》一书中介绍了邮件托管服务公司 RackSpace 的日志架构变迁,虽然业务功能没有任何变化,但是邮件数量却持续增长,为了满足性能需求,架构经历了三个完全不同的系统变迁:从最初的本地日志文件,到中央数据库,再到基于 HDFS 的分布式存储,整个系统几乎发生了颠覆性的变化。这并非 RackSpace 的架构设计师欠缺设计能力, 而是在公司草创之初,他们没有能够高瞻远瞩地预见到客户数量的增长,导致日志数据增多,以至于超出了已有系统支持的能力范围。

俗话说“事后诸葛亮”,当对一个软件系统的架构设计进行复盘时,总会发现许多设计决策是如此愚昧。殊不知这并非愚昧,而是在设计之初,我们手中掌握的筹码不足以让自己赢下这场面对未来的战争罢了。这就是变化之殇!(未完待续)

本文节选自中生代技术社区出版架构图书:架构宝典,根据篇幅略有删节

往期推荐

欧创新:深度解析DDD中台和微服务设计

领域驱动专家张逸文字脱口秀:简单工厂不简单

DDD专家张逸:《解构领域驱动设计》前言

Hacker News热文:请停止学习框架,学习领域驱动设计(DDD)(获500个点赞)

京东平台研发朱志国:领域驱动设计(DDD)理论启示

DDD专家张逸:构建领域驱动设计知识体系

领域驱动设计(DDD)在美团点评业务系统的实践

当DDD遇上微服务

DDD战略篇:架构设计的响应力

可视化与领域驱动设计

领域驱动设计(DDD)前夜:面向对象思想

领域驱动设计(DDD):领域和子域

DDD专家张逸:复杂与架构演进的关系

点个在看,让更多人看见

文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览92262 人正在系统学习中

来源:普通网友

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年2月26日
下一篇 2021年2月26日

相关推荐