DDD建模 vs 传统开发

领域驱动设计(DDD)系列

Posted by Bruce Wong on September 21, 2024

最近和一个研发团队尝试用领域驱动开发(DDD)的方式进行开发工作。在这个过程中,和传统开发的思考方式有了若干次的碰撞,是一个有趣的过程。在这里记录一下自己的思考。

背景

简化一下要做的功能,方便大家理解。这是一个企业安全合规的软件,可以监控企业每一个用户对数据的访问情况,以及分享情况,一旦一些关键文件被放入到错误的位置,或者分享给了不正确的人,那么系统会第一时间发出通知,让数据的负责人进行处理,避免安全合规风险。

领域划分

从业务角度看,最核心的部分是能够准确的监控用户管理的数据,第一时间展示安全和合规风险,以及提供处理的方式。当然企业数据可能有不同的存放位置,我们可以分别考虑。另一个是及时通知用户,发现问题,通知部分。当然通知部分不仅可以通知发现的问题(Issue),还可以有其他消息通知。看下面的图例: domain_boundarycontext

紫色的是聚合,在绿色的核心域中可以看到SharePoint站点、FTP文档服务器、云存储(Azure Storage,AWS S3等),这些都是企业主要存储数据的地方。作为聚合,他们具体内容有一些不同,但是Issue列表、风险状态、合规状态都是相同的,所以我用SharePoint站点聚合为例子。

另一个领域是蓝色的支撑域。通知业务聚合就在这个领域中。它其中有一个代办事项列表,里面每一个待办事项是一个实体,用来描述代办事项的内容:代办Id,内容,时间,状态,代办事项类型,事项Id等。其中待办事项类型有多种不同系统类型,其中一个就是Issue,事项Id对应的就是Issue这个实体的Id。

聚合和实体

可以看到在不同的领域中,有多个聚合,他们可以理解为当前领域下某一个业务活动,包括对业务的描述属性——实体和值对象们,另外还有一系列业务动作和事件等。 当然聚合对象的处理过程一般会包括一组实体集合,而在DDD中单个实体代表了最小业务单元。例如Issue实体,证明实体等。

与传统开发思维方式的区别

上面的设计中有一个代办事项实体和Issue实体、证明实体都是一个一个具体的面向对象编程语言的类。按照上面的图可以看到,这里是三个类,他们之间只有事项Id作为关联。当时团队的开发小伙伴就提出,是不是要将代办事项做一个基类,抽象一部分共同的属性和操作,然后Issue和证明实体继承这个基类。这样可以减少代码重复,提高代码的可维护性。

DDD的思路是将具体业务都封装在聚合实体内。代办事项实体应对的业务是通知用户有代办事项,事项的紧急程度,事项的时间,是否处理完成的状态等,而他的业务操作有:处理代办事项、状态更新、超时未处理提醒等。而代办事项实体承载的内容中,有两类具体业务:Issue和证明。他们通过Id来关联。在图中有实线关联可以看到。真正处理Issue或者证明的业务,都是在这两个实体内部来做的,不应该放在代办事项实体中。

Issue和证明都属于核心业务,而代办事项属于支撑业务,所以他们被划分在不同的领域中。

一些思考

  1. 传统开发喜欢抽象,希望尽可能重用代码,但是这也会带来代码可读性差,因为抽象的代码会让业务逻辑变得模糊。
  2. DDD的思路是基于业务来划分领域,聚合,实体,这样可以让代码更加贴近业务,可读性更高。代码即文档,而且是活文档。
  3. 除了可读性高,按照业务划分可能会有部分代码重复,但是高内聚的业务逻辑让类之间的耦合度降低,系统复杂度降低,因为更改不会彼此影响。
  4. 按照业务来设计代码模型另一个好处是,天然就拆分了用户故事,每一个聚合或者实体都可以看成一个具体的业务场景,天然满足INVEST(Independent,Negotiable,Valuable,Estimable,Small,Testable)原则。方便开发团队在每次迭代中交付可工作的软件。

践行敏捷实践,让工作变得更美好。欢迎关注我的公众号,交流落地经验。