领域驱动设计简介

Posted in design on 2021-10-26 by 王登武 ‐ 1 min read

领域驱动设计简介

领域驱动设计

代码开发的思考

以下开发方法区别是什么?

  • 面向过程开发
  • 面向对象开发
  • 面向CV(ctrl-c&ctrl-v)开发
  • 面向百度开发
  • 面向数据库开发

什么是技术债

技术债就像技术前进中的累赘一样,会像滚雪球那样越滚越大,不断拖延增加新功能的步伐,最终可能无法再为系统添加新功能。因此,技术负债的存在是导致软件质量下降的重要原因。软件质量下降以后,系统难以维护和修复,就会导致项目失败或者必须重写代码

你真的在面向对象开发吗?

  • JavaBean它真的好用吗?
  • DTO为什么无处不在?
  • MVC思想的滥用
  • 用着面向对象的语言,做着面向过程的开发

现在大部分对业务逻辑的处理,都是通过Controller到Service,再通过Dao组装操作到持久层数据库,而这其实是典型的面向过程的开发,中间只是数据的传递和组装修改。

而现在流行的微服务架构,如果不能很好的拆分出领域实体,确定好领域边界,只不过是增加了复杂度的单体应用而已。

领域驱动设计的思想,回归到面向对象的本质,封装,抽象上面来,虽然设计模式可以解决一部分场景问题,但是领域驱动设计更像重剑无锋,大巧不工

领域驱动为什么没有流行起来

我个人结合对领域驱动的理解和认识之后,总结为领域驱动早期引入的时候,翻译的名词太理论化,导致太抽象不接地气,下面我们就看看这些专有名词有多不接地气。

领域驱动名词概念

主要的概念如下:

  • 有界上下文
  • 领域事件
  • 聚合
  • 聚合根
  • 实体
  • 值对象

事件风暴

事件风暴是领域驱动很好的介入点,说人话就是需求讨论阶段,具体领域驱动这块的最佳实践就是需求讨论从事件入手,所谓事件就是业务内的动词,事件风暴从动词事件入手,虽然很繁琐,但是这些事件正是日后需要实现的功能激发的。

事件离需求功能更接近,对领域事件进行分门别类,可以发现有界上下文和聚合。

有界上下文就是指不同业务之间的边界,包括可能的数据传递和交互,而聚合是指业务的核心内容是什么。

DDD分析方法的核心:从细节动词入手发现有界上下文和聚合,以逻辑一致性为边界划分依据,对动作实现分门别类地划分。

领域事件

为什么是动作或事件?因为行为即类的方法,以方法聚合,即基于接口编程,才是高内聚松耦合的关键,又根据封装的特性,应该只暴露方法,而不应该暴露数据。

所以,解决复杂性的两种方法是:拆解成松耦合的组件+使用容易让人明白的套路表达出来。

DDD是怎么实现这两种方法的呢?首先,DDD通过引入“领域或子域”以及“有界上下文”来划分边界,边界一旦划分好,拆解的第一步就能完成;其次,DDD引入各种模式名词,比如聚合、实体、值对象、工厂、仓储、领域事件,让知晓这些模式的人能够一下子定位到功能对应的组件。

举个栗子

下面以某电力公司的电费结算领域为案例,说明如何通过组织的形式进行有界上下文的边界发现和划分。

首先需要了解一下领域知识、业务策略或业务规则。

电力公司是干什么的? 它是电力这个商品的批发商,从发电企业购买电力,通过电网输送到用电用户,再向用电用户收取电费。

其商业模式很简单,它的信息系统主要是管理金钱的进出,在上下游差价和巨额资金截流中赚取利润,这应该是其核心业务策略。

该业务策略落实到业务流程,就体现在部门组织设置上。

营销部负责面向供电用户销售电力和收取电费,交易中心负责向电厂集中购电,财务部门则是对购销双方进行统一资金结算。

不同部门负责不同的领域,现在可以根据这种原则划分三种有界上下文∶ 购电上下文;销电上下文和结算上下文,同时将参与开发的团队也相应地划分成三个团队。

子域

  • 核心子域(核心业务)
  • 支持子域(周边可以外包的业务)
  • 通用子域(基础支持如财务系统可以购买的系统)

聚合

聚合是子域内的类集合以及类关系集合。

聚合是一个行为在逻辑上高度一致的对象群。

注意,它是一个对象群体的总称。聚合的内部结构如同一棵树,每个聚合都有一个根,其他对象和聚合根之间都是枝叶与树根的有序关系。

有序的复杂,虽然也复杂,但是可以被理解,无序的复杂,不仅仅复杂,最重要的是不能被轻易理解。

这样有序化的好处是∶只有"根"能引用或指向其他对象,“根"自身不能被其他任何对象引用;“根"类似团队的小组长,队员都要向其汇报工作。

这就是聚合根的设计来源,聚合根拥有自己边界内的数据所有权,以及行为职责的管理权限。

数据和行为两者兼顾的所有权只有聚合才能具有,为什么需要数据和行为两者兼顾呢?

通常情况下,数据和行为是分离的,行为在服务中实现,而数据隔离在数据表中,行为通过服务转为SQL语句去操作数据表,这种方式的问题是隔离了行为和数据的紧密逻辑关系。

找出聚合和组合关联的类,组成树状类图,舍弃普通关联,这就是高内聚低关联的设计原则。

聚合是体现逻辑一致性的地方,也是保证业务规则实现的地方。

失血和充血

在没有设计的朴素情况下,领域模型一般是一个数据对象(DTO等),其中只有setter/getter方法,是一种纯粹的数据结构,然后将很多数据结构的算法操作设计在服务(Service)等专门的接口类中。

这样,数据对象作为服务接口方法的参数传入,在服务的方法中被加工。

所以说如果没有领域驱动设计的思想,即便是架构上是微服务的,服务内部也可能是只是面向过程的开发,只不过是service组装修改DTO,最终持久化而已。

区分开失血模型和贫血模型,有助于认识到数据库中的实体表其实是一种失血模型、一种纯数据结构;

通过ORM等工具映射到Javabean,也是一种只有setter/ getter的失血模型,这些实体模型并不是 DDD中的实体。

下面看看DDD中的实体是什么。

实体

什么是实体?具有唯一标识的聚合即实体

能够将复杂事情简单化,但是实体和聚合的表现形式太相似了。

其实这是从两个不同角度看问题的结果,聚合是从实体外部看实体的上下文环境,需要在这个场景上下文扮演的角色来定位,而实体本身的内部设计,包括标识和其他属性、职责以及关联属于事物内部的构造设计。

实体的设计不只要照顾到所处上下文,还要兼顾它被创建后的生命周期管理,实体的类名负责它在上下文中的定位,而实体的标识负责它被创建后的生命。

实体的构建往往需要builder模式

思考题:为什么需要使用builder模式?

值对象

值对象:没有唯一标识的对象,是一堆数据值的容器。如VO对象,具有不变性

首先,值对象中的数据值一旦被构建,就不能改变,这是不变性的特性,而DTO没有这种约束,这容易导致DTO传输过程中不断添加、修改各种字段。

DTO变成一个装载数据的可变长度的容器,虽然给编程带来了方便,但是将可变性带到代码的各个地方,

最后DTO进数据库存储时,才发现数据并不是原来想象的那样,至于在哪个环节修改了,就需要不断地跟踪,这种跟踪在复杂软件中也非常复杂。

Top