迄今为止最完整的DDD实践

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了迄今为止最完整的DDD实践相关的知识,希望对你有一定的参考价值。

参考技术A

作者:章磊(章三) 阿里飞猪技术团队

对于一个架构师来说,在软件开发中如何降低系统复杂度是一个永恒的挑战。

领域(战略):业务范围,范围就是边界。
子领域:领域可大可小,我们将一个领域进行拆解形成子领域,子领域还可以进行拆解。当一个领域太大的时候需要进行细化拆解。
模型(战术):基于某个业务领域识别出这个业务领域的聚合,聚合根,界限上下文,实体,值对象。

决定产品和公司核心竞争力的子域是核心域,它是业务成功的主要因素和公司的核心竞争力。 直接对业务产生价值

没有太多个性化的诉求,同时被多个子域使用的通用功能子域是通用域。例如,权限,登陆等等。 间接对业务产生价值

支撑其他领域业务,具有企业特性,但不具有通用性。 间接对业务产生价值

一个业务一定有他最重要的部分,在日常做业务判断和需求优先级判断的时候可以基于这个划分来做决策。例如:一个交易相关的需求和一个配置相关的需求排优先级,很明显交易是核心域,规则是支持域。同样我们认为是支撑域或者通用域的在其他公司可能是核心域,例如权限对于我们来说是通用域,但是对于专业做权限系统的公司,这个是核心域。

业务的边界的划分,这个边界可以是一个领域或者多个领域的集合。复杂业务需要多个域编排完成一个复杂业务流程。限界上下文可以作为微服务划分的方法。其本质还是高内聚低耦合,只是限界上下文只是站在更高的层面来进行划分。如何进行划分,我的方法是一个界限上下文必须支持一个完整的业务流程,保证这个业务流程所涉及的领域都在一个限界上下文中。

定义: 实体有唯一的标识,有生命周期且具有延续性。例如一个交易订单,从创建订单我们会给他一个订单编号并且是唯一的这就是 实体唯一标识 。同时订单实体会从创建,支付,发货等过程最终走到终态这就是 实体的生命周期 。订单实体在这个过程中属性发生了变化,但订单还是那个订单,不会因为属性的变化而变化,这就是 实体的延续性
实体的业务形态: 实体能够反映业务的真实形态,实体是从用例提取出来的。领域模型中的实体是多个属性、操作或行为的载体。
实体的代码形态: 我们要保证实体代码形态与业务形态的一致性。那么实体的代码应该也有属性和行为,也就是我们说的充血模型,但实际情况下我们使用的是贫血模型。贫血模型缺点是业务逻辑分散,更像数据库模型,充血模型能够反映业务,但过重依赖数据库操作,而且复杂场景下需要编排领域服务,会导致事务过长,影响性能。所以我们使用充血模型,但行为里面只涉及业务逻辑的内存操作。
实体的运行形态: 实体有唯一ID,当我们在流程中对实体属性进行修改,但ID不会变,实体还是那个实体。
实体的数据库形态: 实体在映射数据库模型时,一般是一对一,也有一对多的情况。

值对象的运行形态: 值对象创建后就不允许修改了,只能用另外一个值对象来整体替换。当我们修改地址时,从页面传入一个新的地址对象替换调用person对象的地址即可。如果我们把address设计成实体,必然存在ID,那么我们需要从页面传入的地址对象的ID与person里面的地址对像的ID进行比较,如果相同就更新,如果不同先删除数据库在新增数据。
值对象的数据库形态: 有两种方式嵌入式和序列化大对象。
案例1:以属性嵌入的方式形成的人员实体对象,地址值对象直接以属性值嵌入人员实体中。

当我们只有一个地址的时候使用嵌入式比较好,如果多个地址必须有序列化大对象。同时可以支持搜索。

案例2:以序列化大对象的方式形成的人员实体对象,地址值对象被序列化成大对象 Json 串后,嵌入人员实体中。

支持多个地址存储,不支持搜索。

值对象的优势和局限:
1.简化数据库设计,提升数据库操作的性能(多表新增和修改,关联表查询)
2.虽然简化数据库设计,但是领域模型还是可以表达业务
3.序列化的方式会使搜索实现困难(通过搜索引擎可以解决)

多个实体和值对象组成的我们叫聚合,聚合的内部一定的高内聚。这个聚合里面一定有一个实体是聚合根。
聚合与领域的关系:聚合也是范围的划分,领域也是范围的划分。领域与聚合可以是一对一,也可以是一对多的关系
聚合根的作用是保证内部的实体的一致性,对外只需要对聚合根进行操作。

领域包含限界上下文,限界上下文包含子域,子域包含聚合,聚合包含实体和值对象

除了领域专家,事件风暴的其他参与者可以是DDD专家、架构师、产品经理、项目经理、开发人员和测试人员等项目团队成员

一面墙和一支笔。

实体执行命令产生事件。

通过业务场景和用例找出实体,命令,事件。

领域建模时,我们会根据场景分析过程中产生的领域对象,比如命令、事件等之间关系,找出产生命令的实体,分析实体之间的依赖关系组成聚合,为聚合划定限界上下文,建立领域模型以及模型之间的依赖。领域模型利用限界上下文向上可以指导微服务设计,通过聚合向下可以指导聚合根、实体和值对象的设计。

需求:我们需要把系统自动化失败转人工订单自动分配给小二,避免人工挑单和抢单,通过自动分配提升整体履约处理效率。

沟通的过程就是推导和验证模型的过程,最后进行域的划分:

穷举所有场景,重新验证模型是否可以覆盖所有场景。

每一层都定义了相应的接口主要目的是规范代码:

application:CRQS模式,ApplicationCmdService是command,ApplicationQueryService是query

service:是领域服务规范,其中定义了DomainService,应用系统需要继承它。

model:是聚合根,实体,值对象的规范。

repository

event:事件处理

exception:定义了不同层用的异常


域服务,聚合根,值对象,领域参数,仓库定义

所有技术代码在这一层。mybatis,redis,mq,job,opensearch代码都在这里实现,domain通过依赖倒置不依赖这些技术代码和JAR。

对外提供服务

内外都要用的共享对象

充血模型VS贫血模型:

规范大于技巧:DDD架构可以避免引入一些其他概念,系统只有域,域服务,聚合根,实体,值对象,事件来构建系统。

聚合根的reconProcess的方法的业务逻辑被reconHandler和reconRiskHandler处理,必然这些handler要访问聚合根里面的实体的属性,那么逻辑就会散落。修改后:

没有引入其他概念,都是在聚合根里面组织实体完成具体业务逻辑,去掉了handler这种技术语言。

修改了mapstruct生成转换代码的源码,修改后生成的代码:

当属性被改变后就转换到po中,这样就可以实现修改后的字段更新。修改后的mapstruct代码地址:git@gitlab.alibaba-inc.com:flight-agent/mapstruct.git

以上是关于迄今为止最完整的DDD实践的主要内容,如果未能解决你的问题,请参考以下文章

DDD专栏6:建模演练:如何使用DDD来设计支付风控系统?

到目前为止,Linux下最完整的Samba服务器配置攻略

领域驱动设计实战-DDD

如何一步一步用DDD设计一个电商网站—— 一个完整的购物车

DDD架构

保存/加载完整表格