DDD(领域驱动设计)学习笔记
〇、引言
DDD是一种非常正确的的设计理念。可以说是对传统OO的升华。
相较于现在被大量使用的 web + service + dao 的分层结构,DDD更接近于面向对象设计的本质,或者说其更接近于业务模型,代码的通用性、复用性、可维护性、扩展性更为完善。
但是如果完全将其进入工业生产的话,不可避免带来一个让人困惑的问题:完全遵循DDD设计规范的话,大多数情况下项目整体效率是高不起来的。DDD究竟能为企业行为带来什么好处,其推崇的交流、设计方式到底是好是坏?
一、DDD的基础知识
1-1、什么是DDD
DDD = Domain Driven Design,即领域驱动设计。
不可能在不了解业务知识(最终产品)的前提下进行软件开发;在开发前,通常需要进行大量的业务知识梳理,而后到达软件设计的层面,最后才是开发。
在业务知识梳理的过程中,必然会形成某个领域知识,根据领域知识来一步步驱动软件设计,就是领域驱动设计的基本概念。
DDD与瀑布模型与敏捷迭代都不同,它的最小单元是领域模型(Domain Model),即能够精确反映领域中某一知识元素的载体。
业务知识的获取需要通过与该领域的业务专家进行频繁的沟通,才能将专业知识转化为领域模型。
领域模型无关技术,具有高度的业务抽象性,它能够精确的描述领域中的知识体系;同时它也是独立的,我们还需要学会如何让它具有表达性,让模型彼此之间建立关系,形成完整的领域架构。
1-2、通用语言
领域专家与软件设计人员对于一些专业名词的理解有时候是南辕北辙的。
为了避免沟通中出现误解给之后的工作带来不可弥补的损失,必须约定好双方都认可的,可以明确表达领域模型的一种通用语言。
这种通用语言没有标准答案,只要双方都认可,并且都可以熟练使用即可。
一种方案是UML。UML擅长表述类、属性和相互之间的关系。但是当领域模型数量急剧膨胀以后,UML就会变得混乱起来。
另一种方案是文档。但是冗长的文档需要花费很多的时间和精力,而且有时很难跟得上领域模型自身的修正和迭代。
具体如何取舍完全取决于团队自身的实际状况。
但是有一点是肯定的,在建立领域模型的过程中必须将领域建模与软件设计密切结合起来,模型在构建时就考虑
到软件和设计。开发人员应当适当参与建模的过程,适时给出反馈。
一个无法被软件架构设计实现的领域模型无疑是缺乏实用性的。
1-3、分层架构
DDD分层设计- 用户界面/展现层:负责向用户展现信息以及解释用户命令
- 应用层:很薄的一层,用来协调应用的活动。它不包含业务逻辑。它不保留业务对象的状态,但它保有应用任务的进度状态。
- 领域层:本层包含关于领域的信息。这是业务软件的核心所在。在这里保留业务对象的状态,对业务对象和它们状态的持久化被委托给了基础设施层。
- 基础设施层:本层作为其他层的支撑库存在。它提供了层间的通信,实现对业务对象的持久化,包含对用户界面层的支撑库等作用。
1-4、entity
实体与面向对象中的概念类似,是领域模型的基本元素。在领域模型中,实体应该具有唯一的标识符。
1-5、value object
值对象和编程中数值类型的变量不同,它仅仅是没有唯一标识符的实体,比如有两个收获地址的信息完全一样,那它就是值对象,并不是实体。
值对象在领域模型中是可以被共享的,他们应该是“不可变的”(只读),当有其他地方需要用到值对象时,可以将它的副本作为参数传递。
1-6、service
DDD中每一个领域模型都应该具有属性、状态和行为。
注意DDD中的领域模型与Java等语言中的POJO不同,POJO可以使用贫血模型来实现,但是DDD中每一个领域模型都应该包含于自身关系密切的行为。比如一个Model叫做“用户登录信息”,那么它自身就应该包含“修改密码”这一行为。
问题是领域中的一些行为是无法映射到具体的对象中的,我们不能强行将其放入在某一个模型对象中,而将其单独作为一个方法又没有地方,此时就需要服务。
例如,为了从一个账户向另一个账户转钱,这个功能应该放到转出的账户还是在接收的账户中?放在这两个中的哪一个也不对劲。当这样的行为从领域中被识别出来时,最佳实践是将它声明成一个服务。
服务的 3 个特征:
- 服务执行的操作涉及一个领域概念,这个领域概念通常不属于一个实体或者值对象。
- 被执行的操作涉及到领域中的其他的对象。
- 操作是无状态的。
关于上面的第三点,服务是无状态的,而对象是有状态的。所谓状态,就是对象的基本属性。服务本身也是对象,但它却没有属性(只有行为),因此说是无状态的。从便于理解的角度来说,可以把服务看做是utils。
从分层上来讲,服务属于Domain层。
1-7、模块
为了降低系统复杂性,通常的做法是将高关联度的模型分组到一个模块以提供尽可能大的内聚,同时消除耦合。
模块应该由在功能上或者逻辑上属于一体的元素构成,以保证内聚。
同时模块应该具有定义好的接口,这些接口可以被其他的模块访问,同时将内部的模块保护起来,不再对外暴露,组织外部的直接操作。
1-8、聚合(Aggregates)
聚合被看作是多个模型单元间的组合,它定义了模型的关系和边界。
每个聚合都有一个根,根是一个实体,并且是唯一可被外界访问的。
聚合的一个最重要的作用,是来处理数据变化的情况的。彼此之间有关联(不管1:1、1:N、M:N)、一个变化会引发其他变化的一组模型,组合成一个聚合。
聚合的重要作用是保持数据一致性,并且可以强化不变量,因为其他模型都参考聚合的根。要想改变其他对象,只能通过聚合的根去操作。根如果没有了,那么聚合中的其他对象也将不存在。
1-9、工厂
与设计模型中的工厂模型的作用相同。
为复杂的模块或者聚合提供创建对象的方法,用来代替简单的构造器(依赖反转)。
1-10、资源库
从代码角度来说,可以认为资源库就是一层缓存。
资源库的目的是封装所有获取对象引用所需的逻辑。领域对象不需处理基础设施,以得到领域中对其他对象的所需的引用。只需从资源库中获取它们,使得模型自身更加专注于业务逻辑,变更更加清晰。
是领域模型同需要保存的对象和它们的引用中解耦,由资源库自行判断是获取已经存在的对象,还是从数据库中获取,或是干脆调用工厂或者类似的办法来直接创建。
二、DDD进阶
// TODO
三、DDD与CQRS
// TODO
四、杂七杂八的一些思考
// TODO