万字详解|从软件复杂度的角度去理解DDD

  • 2023-04-11 23:10:15
  • 来源:阿里开发者

从我们作为业务开发主要的职责深入到DDD的本质是什么?复杂度应处理?规范设计怎么做?本文将全方位为大家解答。

作者 | 王鹏(台城)


【资料图】

来源 | 阿里开发者公众号

一、作为业务开发,我们的主要的职责是什么的

业务开发的职责

在文章的开始我想和大家一起思考一个问题: 作为一个工程开发,我们最主要的职责是什么?

我极度认可 <<浅谈什么是技术一号位>>【1】文章的观点 - 切实解决业务问题才是每一个工程开发最主要的职责 - 所以每个业务开发都必须要结合业务的视角去思考自己系统的建设和发展,而不是只是做一个“编程的”码农。

这里摘录一下文章中要点

技术一号位是负责使用技术能力解决业务问题,提供稳定可靠的技术支撑; 负责向业务各方提供各种必要的技术支撑,通过合理的数据分析为业务决策提供依据; 通过对技术领域的积累和发展,通过业务领域的理解和落地影响业务决策; 负责构建梯队完整、能力全面、制度完善的技术团队来支撑业务发展。

文中也提到了虽然不是每个人都负责一块完成的业务,也不是每个人都带领团队,但是至少每个人都是自己所负责的那块系统的技术一号位。

业务在实际开展中遇到的问题

那实际业务开展中,业务到底会遇到有哪些问题呢? 我们按业务的生命周期进行切分,然后具体查看每个业务生命周期的诉求:

业务启动期: 业务能力快速搭建 - 系统提供快速试错的能力 业务发展期: 业务能力扩展 - 系统需要支持越来越多的业务功能 业务平台期: 业务能力复制 - 系统需要支持越来越多的业务场景 业务衰退期: 业务能力创新 - 系统提高生产力延长业务的生命周期

我们技术要做的事情是:在业务验证没有问题的情况下,如果尽可能的延长业务的发展和平台期,让业务获取的利益最大化。所以为了支持业务的发展,业务的本身的功能支持诉求以及业务对技术的要求也会越来多,在这种情况下考验软件开发人员的一个非常关键的能力就是: 软件复杂度的控制的能力 。

软件复杂度

软件复杂度其实是一种多维度的概念,其可能来源于多个方面, 前阿里资深技术专家李运华在他的《从0开始学架构的》课程中从6个方面阐述了软件复杂度【2】,列举如下:

高性能 单机性能 集群性能 高可用 计算高可用 存储高可用 可扩展性 低成本 安全 规模 业务规模 系统物理规模

二、DDD的本质是什么

DDD本质上我认为就是一种减低软件复杂度的手段, 其推荐的方法论可以适用于上面包括了 业务规模 , 可扩展性 两个维度的复杂度应对。 其实业务规模的复杂度的处理包括了对可扩展性的支持。

DDD实施给系统之后,我们依然需要关注系统其它的复杂度,这里列举一些示例措施:

容量规划架构设计数据库设计缓存设计框架选型发布方案数据迁移、同步方案分库分表方案回滚方案高并发解决方案一致性选型性能压测方案监控报警方案

那么我们进一步对业务规模的复杂度进行拆解,又分为下面两类:

1 领域复杂度

领域模型描述问题域的准确性

2 技术实现的复杂性

代码没有按照业务绑定的”分析模型”去编码,软件变成一个大泥潭

软件的可扩展性较差 软件变成面向过程 分层不合理 没有规范

那DDD是如何处理上面提到的软件复杂度的?

提供了一个领域划分的方法:让软件系统产生边界。 提供一个一系列的战略模式:限界上下文的映射,分层架构等。 提供一个一系列的战术模式:如何规划领域层 内部

DDD不是什么?

不光光只是一种编程方法 不光光只是一种架构风格 不具体指导如何具体建模

三、复杂度处理-领域模型描述问题域的准确性

DDD的原名是 模型驱动的设计方法:通过领域模型(Domain Model)捕捉领域知识,使用领域模型构造更易维护的软件。

合理性证明

DDD的核心思想,大家都清楚,就是分析模型要和代码模型保持一致。

那么如果不保持一致到底会产生什么样的负面影响

如果技术实现和业务实现不在同一水平线上,那技术模型的行进路线只会考虑劈开技术障碍并且可能会撞在未来的业务障碍的墙上。 这样就很容易出现,业务持续演进等技术想实现的时候,却发现当前的实现依赖于“ 业务不会这样发展 ”的假设上。 这也是为什么会出现现在众多业务需求,技术无法实现或者是需要花大量时间去实现的原因。

但是如果技术和业务通过统一语言打破知识的壁垒保持一致,那么如果后面技术遇到问题即是业务碰到的问题,业务人员需求的变更和迭代会自然而然的帮助技术同学越过一些门槛。 也就是说 业务方与技术方参与到对方的工作中,就在双方之间带来了更好的协同,形成1+1>2的功效

什么是问题域

根据百度百科的解释【3】 在软件工程中,问题域是指待开发系统的应用领域,即在客观世界中由该系统处理的业务范围

那么问题域内的组成是什么呢? 就是我们的域模型。

这里直接摘抄一段前阿里P10\"阿白\"在阿里内部发表的域模型的观点:

域模型(domain model)英文又称为问题域模型(problem space model)。维基百科(Wikipedia)对它的定义是” A conceptual model of all the topics related to a specific problem” 可以翻译成: “域模型是针对某个特定问题的所有相关方面的抽象模型”。 这个定义有几个要点:第一是“特定问题”, 也即是说域模型是针对性某个问题域而言的, 脱离的这个特定问题,域模型的构建其实不存在一个最优或者是最合理的构建。 第二是抽象, 域模型是一个抽象模型, 不是对某个问题的各个相关方面的一个映射, 也不是解决方案的构建。

如何实现问题域的分析

在 DDD 中,Eric Evans 提倡出一种叫做知识消化(Knowledge Crunching)的方法帮助我们去提炼领域模型。简单来说就是五个步骤:

关联模型与软件实现; 基于模型提取统一语言; 开发富含知识的模型; 精炼模型; 头脑风暴与试验。

开发人员和业务专家在一起通过一个个业务用例仔细讨论应用程序的应用场景,从而使得业务人员深刻理解业务知识,开发人员和业务人员就重要的业务概念建立起统一的语言,开发人员将这些概念根据业务用例的上下文抽象出模型,并且这些模型将会最终成为最终软件实现中的领域模型。 随后随着更多的业务用例的输入,开发人员和业务人员会逐渐对已经构建的模型进行精化,并且也会用新的用例去检验之前构建模型的合法性和适用性。

DDD在这一步其实没有给出详实标准的如何建模的方法,毕竟建模还是来自于每个人的世界观,其过程还是倾向于经验的。但是还是有不少人总结一些标准的建模方法论例如:

1 四色原型法

2 用例分析法 https://baike.baidu.com/item/%E7%94%A8%E4%BE%8B%E5%88%86%E6%9E%90/2859078?fr=aladdin

问题域的拆分

大家应该发现上面的知识消化的流程是一个非常耗时和复杂耗脑力的过程, 涉及到产品,业务,技术等多方团队, 所以为了让有限的资源投入到最最核心的子域,我们需要对问题域进行这份,把重点的精力放到最核心的领域上。

核心领域一定是业务价值最高的,而非技术难度最高或者是基础设施框架部分。

要切分问题域,首先需要了解问题域的种类:

1 通用域: 非应用独有的,多个应用都会有的功能。 例如发送邮件,触达等

2 核心域:和竞争对手区别开来的区域,或者是在市场上被赋予了竞争优势的区域。

3 支撑子域:其余的区域

如何确定核心域,这里有几个提示:

系统哪部分最难用 手动处理过程阻止了他们进行了根据创造性, 有附加值的工作 哪些修改能提高收益 哪些修改能提高运营效率

取哪些提示,取决于业务系统的性质。

那如何决定支撑子域/通用子域,以及支撑子域/通用子域的切分呢?

目前在我查阅的资料中,还暂时没有人提及到具体的操作方法,感觉主要还是依靠经验主义在做划分。 我个人总结了一个方法,主要就是就是关注业务的核心实体和核心流程。 以核心实体和核心流程作为切分支撑子域的基础。

核心实体 :核心实体是存在于核心流程中,对核心流程的决策和扭转可能起到关键的作用。有的时候业务上为了能让核心实体在业务流程中起到更大或者更高效的作用,会添加一些让核心实体更好服务于业务流程一些业务功能,从而使业务实体从整体上看变得相对复杂,这个时候我们应该以核心实体为基础进行切割,把所有和核心实体CRUD相关的操作还有让其变得更高效的业务功能划分为单独的一个领域。

核心流程: 当某个业务流程足够复杂也可以当成一个子域。

在实现领域驱动设计【4】书中,提到了为在线拍卖网站系统划分问题域的一个例子,我们以此来验证上面等构想

划分子域

卖家 + 会员身份: 这两者都是核心实体,网站可能为了让促进会员能够多参与拍卖可能提供了分层,或者积分等工功能。 网站为了能让卖家能够更加提供更加有拍卖价值或者是转化率高的品类可能为卖家提供了数据分析等业务功能。 名册: 这也就是核心实体,网站会对名册提供一系列拍卖相关的功能,例如倒计时,一口价等,所以也需要形成一个领域。 拍卖 :网站最核心的业务流程,核心域无疑。 争议解决 :买卖家的售后冲突解决流程向来很复杂,所以会独立成为一个域无疑。

四、复杂度处理-进一步降低问题域的复杂度-限界上下文

限界上下文的诞生背景

一般情况下,一个复杂系统由一系列的模型来表示解答域, 理想状态是一个子域一个模型。 但是有些当业务需要且系统复杂的时候,一个模型可能被多个域共享,这个时候这个模型的概念可能变得不清楚。因此为了保护这些模型概念的完整性, 清晰的定义模型的责任边界很重要。

实现领域驱动设计【4】书中举了下面这个例子:

为了维护模型的概念的完整性,最直观的方法就是为这个模型化一个边界,e.g. 这个商品所表现的意思就是履约的时候用到的\"商品\",而不是下单的时候的\"商品\"。 只要有一个这样的边界定义,系统就会但是出现多个边界,毕竟\"商品\"在不同业务上下文中有不同的含义, 例如库存域的货品,物流域的运输品, 价格域的商品等等。这样的一个边界就是DDD的“限界上下文”。

限界上下文给人直观的感受其实和子域很像,我很早以前曾读过一些关于微服务的书籍,也提到过要把DDD中的限界上下文作为微服务划分的重要依据。 这里其实就给我很大的疑惑:

1 限界上下文到底是怎么划分的? 我们划分限界上下文难道真的是用一个基础概念,然后找这个基础概念不同的“上下文”吗?

2 限界上下文和子域到底区别是啥?

限界上下文的本质

DDD理论中提到了DDD的四个边界

所以在DDD中是把限界上下文作为某个子域的内部模块的划分,其实无论是子域的划分,限界上下文的识别,和聚合的划分他们的本质是一样的,他们都是对复杂问题的分解之后,然后归类分组。只不过“聚合”面向的是领域层内部,“领域”划分面向的是业务问题域,而“限界上下文”面向的是解答域,但是我跟倾向于把限界上下文理解为更加深一层次的业务问题域的划分,而不是面向的解答域。

如果这样看的话,那么其实就可以回答上面的疑问, 领域和限界上下文没有本质的区别,就像树的父节点和字节点一样都是树节点。 而限界上下文的划分完全可以使用子域划分的理论。(可以回顾下上面问题域拆分的段落)

上下文映射

上下文的映射是什么, 简单来说就是描述不同上下文之间的关系的描述。举个例子

DDD对于限界上下文直接提炼了几种方式,这里这边阿里内部文章《领域驱动设计:软件复杂性应对之道》解释的比较好,描述如下:

shared kernel

共享内核 shared kernel :通常是共享核心领域或者是一组通用子领域。

customer/supplier

客户/供应商关系 customer/supplier:上下游关系。不同客户需要协商来平衡,上游团队需要有自动测试套件。

conformist

跟随者模式 conformist:单方面跟随模式。上游的设计质量较好,容易兼容,可以采用严格遵循上游团队的模型。

anticorruption layer

防腐层 anticorruption layer:防腐层、隔离层,使用 facade or adapter 等模式。可以减少其它系统变动对本系统的影响。

separate way

各行其道 separate way:声明一个与其它上下文毫无关联的 bounded context,使开发人员能够在这个小范围内找到简单、专用的解决方案。

open host service

开放主机服务 open host service:开放子系统供其他系统访问。其核心思想是开放出一个标准的各个领域都认可的协议,减轻各个领域实施ACL的负担和成本。

published language

共享语言 published language:把一个良好文档化、能够表达领域信息的共享语言作为公共的通信媒介,必要时在其它信息与该语言之间进行转换。

在当前电商领域的范畴,目前我个人觉得只有ACL,Seperate Way, publish language 有比较好可行性,其他的关系都不是很靠谱:

shared kernel: 如果使用共享二方库,谁来维护这个二方库,如何防止在不同上下文使用不同kernal版本所带来的问题。

如果一定能保证shared kernel的维护在一个团队内,且所有使用shared kernel版本一定能保持一致, 那是可以使用的。

customer/supplier: 我曾经因为汇率包升级而去重构一个应用,因为汇率包变更太大,且应用没有防腐层,所以不论从开发还是测试都是非常痛苦的过程。 conformist: 和customer/supplier类似, 但是在互联网领域没有靠谱的设计, 只有有人维护和没有人维护的设计。 conformist从长期来看其实就是customer/supplier。 Open Host Service 没有任何一个领域保证自己的接口一定不会变,就算不会,其他领域的同学会相信吗,他们会忍住不用ACL吗? 如果他们用ACL,OHS的意义何在? publish language 目前阿里内部MTOP,TOP等协议正是使用这样的协议。

另外限界上下文之间真的能够随便无规则无条件的互相依赖,互相调用吗? 在下面的章节将会解释论述。

五、复杂度处理 - 分层不合理

架构分层主要的作用就是关注点隔离,如果和今天的话题联系起来就是领域模型和技术的关注点隔离(领域和存储,领域和展示)。

传统的三层架构

这种传统架构的缺点

1 业务逻辑层和数据访问层有明显的耦合。

2 没有领域的概念,所有的逻辑沉淀到service中。

所以传统架构只能针对小型的,没有过多的业务逻辑场景。由于这种架构能够保有领域能力的沉淀,所以在现在电商业务场景基本不会被使用。

剩余60%,完整内容请点击下方链接查看:

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

关键词:

Copyright@  2015-2023 非洲包装网版权所有  备案号: 沪ICP备2022005074号-8   联系邮箱:58 55 97 3@qq.com