一、什么是DDD?
DDD(Domain Driver Designer)领域驱动设计是基于领域知识解决复杂业务的方法论。
主要做什么
1) 开发与领域专家统一”通用语言”并对业务划分领域
2) 利用设计模式,将领域模型注入到程序模型中
优势
1) 促进团队沟通、理解领域知识
2) 服务间职责分明,更专于核心业务上
劣势
1) 学习成本高,
2) 需要领域专家参与,否则很难划分领域
总结
相对于传统开发,从表设计再以此为根据编写, 也就是所谓数据驱动编程.相对而言,DDD先拥有模型,模型在对应生命周期的动作,而不在意最后持久化落库的.
二、DDD战略设计
1、DDD战略设计相关核心概念的理解
1)领域
领域即问题域、问题空间,领域是一种边界、范围。所以,一个领域代表了一个问题域的边界,也可以理解为是一个业务的边界。领域边界越大,业务范围就越大,反之则相反;通常我们大家交流都比较喜欢用业务这一词,比如这块业务,那块业务,业务的边界,我是一个业务开发人员(区分于我是一个中间件开发人员)。而领域一词,相对比较抽象,不是那么容易懂。
2)子域
领域既然是一个边界,所以可以划分领域的大小,即领域划分,划分出来的子领域简称子域,每个子域对应一个小的问题域和和小的业务;当然,不同的子域的重要性也是不同的,所以才有了核心子域、支撑子域、通用子域。
① 核心域(Core Domain)
- 整个产品的核心竞争力,最有价值、最赚钱的部分
- 需要花费最大心力去开发
- 如果核心域(Core Domain) 不是你最重要的部分,那代表你要回去再找找
- 如在购物网站中的人工智能推荐
② 支撑域(Supporting Domain)
- 未提供核心竞争力,但支援核心所需功能
- 通常市場上可能沒有现成方案
- 如在购物网站中的购物需求
③ 通用域(Generic Domain)
- 未提供核心竞争力,但整个系统都可能会用到它 (包含 Supporting Subdomain)
- 市場上已有很好的解决方案,所以通常会使用现成工具或外包 (不重复造轮子)
- 如用户管理系统
3)业务模型
每个业务都有一个对应的业务模型(注意这个业务模型不是领域模型,而是一个业务概念的模型),这个业务模型设计的时候,完全不需要考虑任何软件设计的思想,比如对象的抽象、继承、存储、性能,等。我们是从业务本身出发,分析业务边界范围内的各种业务概念,以及业务概念之间的关系,通常我们可以使用一个业务模型的图来表达这些业务概念以及业务概念之间的关系。
4)解决方案
我们在进行DDD领域驱动设计的实践时,会进行需求分析、领域划分、领域建模等工作。而我们的系统要落地,则需要有一套解决方案。例如,我们要实现一个电商平台,需要一个复杂的系统解决方案,但是如果这个解决方案过大,各模块、组件都揉在一起,那么就不利于整个系统的维护、演进、伸缩,等。所以,我们需要把解决方案拆分为一个个独立的小的解决方案;所以,我们可以发现,领域和解决方案,是两个完全不同的概念,领域代表问题空间,解决方案代表解决方案空间。
- 如何拆解解决方案?发现解决方案的拆分的维度可能有很多,没有一个单一的在任何情况下都合理的切分维度。所以出现DDD经典四层架构、CQRS架构等技术架构
####
5)微服务边界-限界上下文(Bounded Context)
领域上下文是一个显式的边界,领域模型便存在于这个边界之内。创建边界的原因在于:每个模型概念,包括它的属性和操作,在边界之内都具有特殊的含义。在很多情况下, 在不同模型中存在名字相同或相近的对象,但是它们的意思却不同。 当模型被一个显式的边界所包围时,其实每个概念的含义便是确定的了。
- 考虑一个图书出版机构,它需要处理图书生命周期的不同阶段
- 概念设计,计划出书。此时,连书名都没有
- 联系作者,签订合同
- 图书编辑、设计布局、插图。此时,图书是一些列稿件、注释、校正
- 出版纸质书
- 市场营销。此时,营销人员只关心书的简介
- 将图书卖给销售商或读者。此时,重点是书的价格、重量、物流目的地。
-
如果整个系统只有一个Book对象,概念混淆、意见分歧和争论是不可避免的。如果我们将系统划分为3个上下文,每个上下文都有Book
- 创作上下文,Book 是一个“作品”
- 出版上下文,Book 可以视为一个印刷品(可能不准确)、出版物
- 销售上下文,Book 可以视为一个商品
如果你在不同的界限上下文中看到了完全相同的对象,通常意味着你的模型是错误的。有些相似的对象拥有不同的属性和行为(一个对象在不同上下文的“分身”),此时通常可以认为上下文边界的划分是合理的。
6)领域模型
领域模型是DDD软件设计方法论中的核心概念,它是业务分析、软件设计的综合结果,是一个系统设计模型。领域模型存在于某个粒度解决方案空间里。所以,任何一个领域模型,都是在特定的限界上下文边界内才有意义。
领域模型包含了聚合、聚合根、实体、值对象等
① 聚合与聚合根
聚合(Aggreate)是一系列由相关的事物组合,它可以作为一个状态变更单位。每个Aggreate都需要一个实体(Entity)作为它的聚合根(Aggregate Root), 并且只有一个聚合根,任何的改变都通过聚合根进行传递,再到内部对应的实体或值对象操作。
-
聚合(Aggregate)的设计原则
- 聚合根(Aggregate Root)的实体(Entity)必须在Bounded Context中有唯一的标识性,它的ID不能与其他的聚合根(Aggregate Root)重复
- 聚合根(Aggregate Root)负责检测边界内所有固定规则
- 外部无法直接引用聚合内的实体或值对象
- 聚合根(Aggregate Root)才能通过Repo查询获取,其他内部实体或值对象都需要通过聚合根(Aggregate Root)才能获取
- 聚合只能引用其他聚合根(Aggregate Root)的ID
- 删除聚合时,必须连同内部的实体和值对象
- 当保存聚合时,也必须一并保存内部的实体和值对象(即聚合根内就是一个整体)
-
关于聚合存储问题
- 在存储聚合时,一次性将聚合内的关联的实体、值对象写入数据库中,需要锁住聚合内相关的表(Table)、总感觉很耗费性能以及不切实际的,特殊仓储层为Mysql时。但是为了保证统一生命周期内只有一个聚合存在的原则,我们在设计聚合时应该尽量的找小的聚合,这也是一种优化手段。
-
如何找到聚合
-
第一步:先找出大聚合
- 根据逻辑与业务问题、我们很容易先将一个业务包成一个大聚合,但是没有关系,找出聚合之后才进行拆分。
- 以商城为例,最容易得到的聚合就是商店(Shop)这个大聚合,里面包含有Order、Product、Discount等实体。
- 当我们需要更新Shop的标题、简介的信息时,为了保证唯一性,就连其他不更新的实体的表(Table)也要锁住,这时创建订单都无法执行,需要等待商品(shop)更新完成。
-
第二步:大聚合里分小聚合
- 聚合(Aggregate)越大、复杂度越低、性能越差。聚合(Aggregate)越小、复杂度越高、性能越好。
- 有了大聚合之后,可以通过更多的案例对聚合做更多分析及设计。特别注意那些由两个以上使用者同时修改一个聚合(Aggregate)的情况
- 以商城为例,订单是属于用户的,同时订单也会给用户奖励积分,于是将Order归到User这个聚合下,这样就会出现在更新User积分的情况下,当前用户无法下单,这说明聚合拆得不够细。因此我们又将Order和User分开成2个聚合,但是Order这个聚合会带有UserID来保持与User的关系引用。
-
② 实体
实体(Entity)DDD中的一类对象,拥有唯一标识符,经历各种状态变更后仍然可以保持一致,对这类对象而言,重要的是延续性和标识。
如一个人,身份证是他的唯一标识,他可通过读书提升自己的学历,但是在整个过程中,他一直保持着唯一性。
-
实体的特征:
- 只有唯一的标识符(如ID)
- 两个实体无论状态如何改变,只要唯一标识(ID)相同,它就是同一个实体
- 除了唯一标识符(ID),其他状态都是可变的
- 实体可能存在很长时间、甚至不会被删除
-
如何设计一个实体(Entity)
- 找出或设计主体(Entity)的ID
- 找出关键信息,将有用属性留在,其他可以考虑为值对象(Value Object)
- 找出关键的行对应的业务规则,然后通过通用语言命名后使用
③ 值对象
一个东西没有概念上的标识、而你只关心它的属性。如姓名、货币、地址甚至可以时更复杂的。值对象具有描述性、不变性、概念整体性、替换行、相等性、无副作用
-
值对象的特征
- 它度量或描述领域中的某项概念
- 不变性 (Immutability)
- 将相关属性足证一个整体概念((Conceptual Whole)
- 当度量与描述改变时,可以用另外的值对象替换
- 相等性
- 无副作用
2、领域模型的建立 - 事件风暴
1)什么是事件风暴
事件风暴是一种用于复杂业务领域协作探索的灵活工作坊模式
####
2)如何利用事件风暴建立领域模型
事件风暴是建立领域模型的主要方法,它是一个从发散到收敛的过程
- 发散:采用用例分析、场景分析和用户旅程分析,尽可能全面不遗漏地分解,业务领域,并梳理领域对象之间的关系,事件风暴过程会产生很多的实体、命令、事件等领域对象,这是一个发散的过程。
- 收敛:我们将这些领域对象从不同的维度进行聚类,形成如聚合、限界上下文等边界,建立领域模型
① 在事件风暴中梳理业务过程中的用户操作、事件以及外部依赖关系等,根据这些要素梳理出领域实体等领域对象。
② 根据领域实体之间的业务关联性,将业务紧密相关的实体进行组合形成聚合,同时确定聚合中的聚合根、值对象和实体。在这个图里,聚合之间的边界是第一层边界,它们在同一个微服务实例中运行,这个边界是逻辑边界,所以用虚线表示。
③ 根据业务及语义边界等因素,将一个或者多个聚合划定在一个限界上下文内,形成领域模型。在这个图里,限界上下文之间的边界是第二层边界,这一层边界可能就是未来微服务的边界,不同限界上下文内的领域逻辑被隔离在不同的微服务实例中运行,物理上相互隔离,所以是物理边界,边界之间用实线来表示。
DDD 主要关注:从业务领域视角划分领域边界,构建通用语言进行高效沟通,通过业务抽象,建立领域模型,维持业务和代码的逻辑一致性。
三、DDD 与架构整合
1、DDD四层架构
1) 什么是DDD四层架构
DDD分层架构中有很重要的依赖原则:每层只能与位于下方的层发生耦合,类似于网络的7层或TCP/IP的4层模型架构,每一层各司其职,并且只关心向下一层的实现,而不会出现各层耦合
DDD分层架构中包含四层:从上到下分别是用户接口层,应用层,领域层和基础层。
与三层架构的对比
2) 了解应用服务与领域服务
应用服务(ApplicationService)
在对应前面战略设计中,应用服务对应实现表达用例和用户故事。
它负责展现层与领域层之间的协调,协调业务对象来执行特定的应用程序任务。它不包含业务逻辑,主要负责编排和转发,即将实现的功能委托给一个或多个领域对象来实现,它本身只负责处理业务用例的执行顺序以及结果的拼装。通过这样一种方式,它隐藏了领域层的复杂性及其内部实现机制。
-
应用服务的职责
- 应用服务可以操作领域对象去完成业务需求,并把计算与业务逻辑交给领域对象。
-
应用服务专注在系统的使用案例,并不在乎外部系统的IO互动细节,交给外层处理
-
那什么可以放在应用服务?
- 消息验证
- 错误处理
- 监控
- 事务(Transaction)
- 鉴权(认证与授权)
- 领域对象编排
领域服务(DomainService)
业务软件的核心,负责表达业务概念,业务状态信息以及业务规则。
应用服务跟领域服务的边界很容易混淆,当领域中的某个操作过程或转换过程不是实体或值对象的职责时,我们便应该将该操作放在一个单独的接口中,即领域服务。请确保该服务和通用语言时一致的;并且保证它是无状态的。
-
领域服务的职责
- 执行一个业务处理流程
- 对领域对象进行转换
-
对多个领域对象(聚合)进行计算
-
什么时候应该将应用服务得代码放到领域服务
- 有计算逻辑,如加减乘除
- 有if/else判断领域概念时
2、洋葱架构
如下图,整个架构像洋葱内部一样,同心圆代表软件的不同部分,从里向外依次是领域模型,领域服务,应用服务和外层的基础设施和用户终端。
洋葱架构根据依赖原则,定义了各层的依赖关系,越往里依赖程度越低,代码级别越高,越是核心能力。外圆代码依赖只能指向内圆,内圆不需要知道外圆的情况,这种架构也是典型的分层架构,和DDD分层架构一样,都体现了高内聚,低耦合的设计特性。洋葱架构也常作为指导微服务设计的重要架构之一。
3、六边形架构
六边形架构又名“端口适配器架构”。追溯微服务架构的渊源,一般都会涉及到六边形架构。
六边形架构将应用分为内六边形和外六边形两层,内六边形实现应用的核心业务逻辑。外六边形完成外部应用,基础资源等的交互和访问,对于与不同的外部系统交互,由外六边形的适配器负责协议转换,保证内六边形业务逻辑的干净。
总结:DDD架构的实现都是提现了高内聚,低耦合的设计特性