文章目录
- 领域建模
- 事件风暴
- 四色建模法
- DDD名称解析
- 领域
- 子域
- 核心域
- 通用域
- 支撑域
- 限界上下文
- 战术设计
- 实体
- 值对象
- 聚合和聚合根
- 工厂
- 资源库
- 领域服务
- 领域事件
- DDD代码的分层
- 名词解析
- 实体
- 值对象
- 聚合根
- 领域服务
- 领域事件
- VO&DTO&DO&PO
- 博客
领域建模
领域驱动设计的核心在于领域建模,架构师的水平高低在很大程度上也体现在领域建模水平上。
领域建模的主要目的是捕捉业务知识,形成统一语言,沉淀领域模型。好的领域建模就意味着对业务要有深刻的理解,能够洞察问题本质。
建模就是将一个大的需求或者系统,经过一系列方法拆分细化,变成一些小而专的小需求,小服务。
领域驱动的战略设计应该如何分析呢?我们不需要独自去摸索,总结前辈的一些经验即可:
-
通用语言:它的作用是定义上下文含义,以便限界上下文定义领域边界
-
事件风暴:由项目团队、领域专家等多人参与,采用头脑风暴的方式进行用户故事分析,找出并建立领域对象
-
四色建模:按时间发展先后顺序,识别“追溯单据”作用的“时标”概念,直达业务核心数据;它强调可追溯性与执行效率
-
限界纸笔建模:回到一百年前,在一个我们没有计算机的年代,我们要做业务设计会用什么方法呢?我们可以用纸和笔画表格并写实例,管理核心领域“恰好够用”的数据,增强数据完整性,避免过度设计
-
DCI建模:可能DCI我们听得比较少,其实DCI架构与MVC架构的提出者是同一个人。DCI建模通过角色扮演模型使得领域模型易于理解,通过小类大对象的手法避免上帝类的问题;同时它也能解决贫血模型和充血模型之争,使模块更加高内聚、低耦合;当然,DCI建模也可以与四色建模融合使用
事件风暴
事件风暴就是一个建模的过程,梳理出系统或需求中有哪些事件。
在事件风暴中,主要关注以下几个方面:
-
事件:某个动作的结果。
-
属性:事件的输入和输出。
-
命令:描述某个动作。
-
实体:执行命令的触发者。
简单理解,即谁(实体)使用什么(输入)做了什么(命令、动作),产生了什么(输出),并影响了什么(事件)。
首先邀请正确的人来一起参加:有问题的人和有答案的人。
第一步:梳理事件(橙色贴纸)
从一个个事件开始(事件是已发生且重要的事情,事件必须是既成事实,且业务关注的事情。),梳理清楚该事件发生前有哪些事件?事件一定会发生吗?事件发生后下一个事件是什么?
假设第一个事件是:专栏已订阅,那么“专栏已订阅”事件发生前前须有“订单已支付”事件。
订单一定可以创建成功吗?不是,贴上“订单创建失败”事件。
经过一系列讨论,得出“专栏已订阅”事件会发生以下事件:
当大家发现新事件的速度接近停滞的时候,就应进入梳理业务规则的阶段了。
第二步:业务规则(粉色贴纸)
业务规则或者业务逻辑,是业务中最重要的部分,主持人会提出以下问题:
事件是否一定成功?如果不是,那么成功的前提条件是什么?该事件是否会导致其他事件的发生?
例如“订单已创建”事件的业务逻辑:
- 订单已创建的前提条件是专栏可订阅,同时用户未订阅过该专栏。
- 订单创建后,会导致发起支付。
第三步:行动者(浅黄色贴纸),命令(蓝色贴纸)、阅读模型(绿色贴纸)和系统(紫色贴纸)
主持人通过问题引导:
-
是什么触发了事件,是命令还是规则
-
是谁执行了动作,是人还是系统
-
做出动作前,用户需要获取到哪些信息
通过类似上面的问题,逐步引导大家找到Actor, Command, Read Model
第四步:热点问题(红色贴纸)
业务痛点、瓶颈、模糊点可以用红色贴纸记录,这些问题需要业务带回去讨论确定清楚,不要在事件风暴中尝试解决所有的hotspot。
第五步:故事串讲
邀请一名现场参与成员,按事件发生的时间顺序串讲业务,过程中,听众注意到不一致的地方,提出问题;大家一起讨论,调整相关的事件、逻辑来达成一致。
第六步:产出架构
通过事件风暴,业务流程和处理逻辑应该已经很清楚了,接下来就由架构师产出对应的架构。可以依据事件风暴产出领域模型、用例图、状态图、活动图、序列图等关键架构交付物。
四色建模法
待学习。。。
DDD名称解析
在DDD中可以分为战略设计
和战术设计
,各自包含的内容如下图所示:
战略设计指的是对整个领域进行分析和规划(宏观),确定领域中的概念、业务规则和领域边界等基础性问题。在战略设计中,需要对领域进行全面的了解和分析,探究业务的规则和本质,并且需要考虑到领域的未来发展趋势和可能的变化。领域、子域和限界上下文属于战略设计的范畴。
领域
领域(Domain)其实就是一个组织所要做的整个事情,以及这个事情下所包含的一切内容,指在特定的范围或边界内要解决的业务问题域,它的核心思想是将业务问题域逐级细为子域/核心域/通用域/支撑域,降低业务理解和系统实现的复杂度。
这是一个范围概念,而且是面向业务的(注意这里不是面向技术的,更不是面向数据库的持久化的),每个组织都有自己的人员、规则和流程,当你为该组织开发软件的时候,你面对的就是这个组织的领域。
例如,在“智学公司”的“智慧课堂”中,领域就是知识付费领域。
子域
子域是指在一个大的领域中,可以进一步划分出来的独立的业务子领域,它们有着自己的业务概念、规则和流程等。
为了区分重要性的不同,我们又会将子域划分成核心域、通用域以及支撑域。
例如,在“智慧课堂”中,子域可以有订阅域、金融域、专栏域等。
核心域
决定公司和产品核心竞争力的子域就是核心域,它是业务成功的主要因素。核心域直接对业务产生价值。
例如,在“智慧课堂”中,订阅域就是核心域。
通用域
没有太多个性化的诉求,同时被多个子域使用的、具有通用功能的子域就是通用域。通用域间接对业务产生价值。
例如,在“智慧课堂”中,权限域、登录域就是通用域。
支撑域
支撑其他领域业务,具有企业特性,但不具有通用性。支撑域间接对业务产生价值。
例如,在“智慧课堂”中,专栏域、评论域就是支撑域。
限界上下文
限界上下文就是业务边界的划分,这个边界可以是一个子域或者多个子域的集合。如何进行划分,一个行之有效的方法是一个界限上下文必须支持一个完整的业务流程,保证这个业务流程所涉及的领域都在一个限界上下文中
。限界上下文是微服务拆分的依据,即每个限界上下文对应一个微服务。
例如,在“智慧课堂”中,可以有一个限界上下文叫“专栏订阅上下文”,它就包含了订单域和订阅域。
战术设计
战术设计则是在战略设计的基础上,对领域中的具体问题进行具体的解决方案设计。战术设计关注的是领域中的具体情境和场景,需要针对具体的问题进行具体的分析和设计,以满足业务需求。实体、值对象、聚合、工厂、资源库、领域服务和领域事件就属于战术设计的范畴。
实体
实体是拥有唯一标识和状态,且具有生命周期的业务对象。实体通常代表着现实世界中的某个概念,实体与领域模型密切相关,它是领域模型中多个属性、操作或者行为的载体。
例如:在“智慧课堂”中,专栏、课程文章、订阅都是实体。
实体的代码形态一般有四种形态:
- 失血模型:模型仅仅包含数据的定义和getter/setter方法,业务逻辑和应用逻辑都放到服务层中。这种类在Java中叫POJO。
- 贫血模型:贫血模型中包含了一些业务逻辑,但不包含依赖持久层的业务逻辑。这部分依赖于持久层的业务逻辑将会放到服务层中。
- 充血模型:充血模型中包含了所有的业务逻辑,包括依赖于持久层的业务逻辑。
- 胀血模型:胀血模型就是把和业务逻辑不想关的其他应用逻辑(如授权、事务等)都放到领域模型中。
值对象
通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体。在 DDD 中用来描述领域的特定方面,并且是一个没有标识符的对象,叫作值对象。值对象没有唯一标识,没有生命周期,不可修改,当值对象发生改变时只能替换。
值对象的业务形态:大多数情况下实体具有很多属性,这些属性一般都是平铺,但有的属性进行归类和聚合后能够表达一个业务含义,就将这些属性封装到一起形成值对象,从而方便沟通而无需关注细节,因此可以说值对象就是用来描述实体的特征。当然实体的单一属性也是值对象。
值对象的代码形态:值对象有两种:单一属性的值对象,例如字符串、整型、枚举等;多个属性的值对象,这时候设计成class,包含多个属性,但是没有ID,值对象中可以嵌套值对象。
一本书对象中,书本的颜色,尺寸等描述就是值对象。
聚合和聚合根
聚合是一种更大范围的封装,把一组有相同生命周期、在业务上不可分隔的实体和值对象放在一起考虑,只有根实体可以对外暴露引用,这个根实体就是聚合根,聚合也是一种内聚性的表现。
领域、子域、限界上下文、聚合都是用来表示一个业务范围,那他们的关系是怎样的呢?
领域、子域、限界上下文属于战略设计,而聚合属于战术设计,聚合的范围是小于前三者的,范围大小图如下:
值得注意的是,一个微服务最小不要小于一个聚合,避免引入分布式事务的复杂度。
一个用户对象,一个地址对象,用户对象内包含地址对象,那么用户对象就是聚合根,关于用户及地址对象内的描述信息就是值对象。
工厂
工厂是一种重要的设计模式;
在DDD中,工厂负责将创建复杂对象和聚合的职责分配给一个单独的对象,该对象本身并不承担领域模型中的职责,但是依然是领域设计的一部分。工厂应该提供一个创建对象的接口,该接口封装了所有创建对象的复杂操作过程,同时,它并不需要客户去引用那个实际被创建的对象。对于聚合来说,我们应该一次性地创建整个聚合,并且确保它的不变条件得到满足。
资源库
资源库(Repository)是一种模式,用于封装数据访问逻辑,提供对数据的持久化和查询
。它旨在将数据访问细节与领域模型分离,使领域模型更加独立和可测试。资源库提供了一种统一的接口,使得领域模型可以与不同的数据存储方式(如关系数据库、文档数据库、内存数据库等)进行交互,同时也提供了一些查询操作,以便在领域层中进行数据查询。如果我们使用MyBatis的话,Mapper就是对资源库的一种实现。
例如持久化到数据库,可以实现一个MyBatis的持久化类,存储到磁盘,就可以实现一个刷盘类等等。
领域服务
有些领域中的动作看上去并不属于任何对象。它们代表了领域中的一个重要的行为,不能忽略它们或者简单地把它们合并到某个实体或者值对象中。当这样的行为从领域中被识别出来时,推荐的实践方式是将它声明成一个服务,这个服务就是领域服务。
例如,在“智慧课堂”中,订阅(Subscribe)行为是一个非常重要的领域概念,它涉及到订单创建、支付、增加订阅记录等和多个实体相关联的操作,将该行为放到任何一个实体中都不合适,在这种情况下,将“订阅”识别为领域服务是比较合适的。
领域事件
领域事件是发生在领域中且值得注意的事件。而领域事件通常意味着领域对象状态的改变。领域事件在系统中起到了传递消息、触发其他动作的作用,是解耦领域模型的重要手段之一。我们往往利用消息队列来传递领域事件。
例如,在“智慧课堂”中,当用户订阅了一个专栏后,会产生一个“专栏订阅成功”的领域事件,用户成长域会根据这个领域事件决定增加用户积分。
在DDD中,领域模型是核心。领域模型是一个抽象的概念,它代表着业务领域中的实体、值对象、聚合根、领域服务等。在建模领域模型时,需要从业务需求出发,将领域模型与业务模型相对应,并将它们映射到代码实现中。
领域驱动设计则是将二者有机地结合起来,以统一的领域通用语言(不一定是UML)进行系统建模,进行系统的分析和设计工作,即该领域专业的业务人员(领域专家) 和 软件开发人员通过领域模型进行需求沟通,彼此共享领域知识,确立符合真实业务需求的领域模型。
其本质上就是对面向对象分析过程的一个扩展和延伸。
建模及名词原文-知乎
DDD代码的分层
-
Interfaces(用户接口层):它主要存放用户接口层与前端交互、展现数据相关的代码。前端应用通过这一层的接口,向应用服务获取展现所需的数据。这一层主要用来处理用户发送的 Restful 请求,解析用户输入的配置文件,并将数据传递给 Application 层。数据的组装、数据传输格式以及 Facade 接口等代码都会放在这一层目录里。
-
Application(应用层):它主要存放应用层服务组合和编排相关的代码。应用服务向下基于微服务内的领域服务或外部微服务的应用服务完成服务的编排和组合,向上为用户接口层提供各种应用数据展现支持服务。应用服务和事件等代码会放在这一层目录里。
-
Domain(领域层):它主要存放领域层核心业务逻辑相关的代码。领域层可以包含多个聚合代码包,它们共同实现领域模型的核心业务逻辑。聚合以及聚合内的实体、方法、领域服务和事件等代码会放在这一层目录里。
-
Infrastructure(基础层):它主要存放基础资源服务相关的代码,为其它各层提供的通用技术能力、三方软件包、数据库服务、配置和基础资源服务的代码都会放在这一层目录里。
箭头表示该层次可以调用的其他层次,领域层不能调用其他领域层,必须通过应用层来调用。
四层架构的作用
- 用户交互层:web请求,rpc请求,mq消息等外部输入均被视为外部输入的请求,可能修改到内部的业务数据。
- 业务应用层:与MVC中的service不同的不是,service中存储着大量业务逻辑。但在应用服务的实现中(以功能点为维度),它负责编排、转发、校验等。(在设计和开发时,不要将本该放在领域层的业务逻辑放到应用层中实现。因为庞大的应用层会使领域模型失焦,时间一长你的服务就会演化为传统的三层架构,业务逻辑会变得混乱。)
- 领域层:或称为模型层,系统的核心,负责表达业务概念,业务状态信息以及业务规则。即包含了该领域(问题域)所有复杂的业务知识抽象和规则定义。该层主要精力要放在领域对象分析上,可以从实体,值对象,聚合(聚合根),领域服务,领域事件,仓储,工厂等方面入手。
- 基础设施层:主要有2方面内容,一是为领域模型提供持久化机制,当软件需要持久化能力时候才需要进行规划;一是对其他层提供通用的技术支持能力,如消息通信,通用工具,配置等的实现。
分层 | 英文 | 描述 |
---|---|---|
表现层 | User Interface | 用户界面层,或者表现层,负责向用户显示解释用户命令 |
应用层 | Application Layer | 定义软件要完成的任务,并且指挥协调领域对象进行不同的操作。该层不包含业务领域知识。 |
领域层 | Domain Layer | 或称为模型层,系统的核心,负责表达业务概念,业务状态信息以及业务规则。即包含了该领域(问题域)所有复杂的业务知识抽象和规则定义。该层主要精力要放在领域对象分析上,可以从实体,值对象,聚合(聚合根),领域服务,领域事件,仓储,工厂等方面入手 |
基础设施层 | Infrastructure Layer | 主要有2方面内容,一是为领域模型提供持久化机制,当软件需要持久化能力时候才需要进行规划;一是对其他层提供通用的技术支持能力,如消息通信,通用工具,配置等的实现; |
战术设计下的分层:
类型 | 英文 | 描述 |
---|---|---|
值对象 | value object | 无唯一标识的简单对象 |
实体 | entity | 充血的领域模型, 有唯一标识 |
聚合(聚合根) | aggregate | 实体的聚合,拥有聚合根,可为某一个实体 |
领域服务 | service | 无法归类到某个具体领域模型的行为 |
领域事件 | event | 不常用 |
仓储 | repository | 持久化相关,与基础设施层关联 |
工厂 | factory | 负责复杂对象创建 |
模块 | module | 子模块引入,可以理解为子域划分 |
代码结构描述
后端Java代码工程为例:
├─com.company.microservice
├─com.company.microservice
│ │
│ ├─apis API接口层/表现层
│ │ └─controller 控制器,对外提供(Restful)接口
│ │
│ ├─application 应用层
│ │ ├─model 数据传输对象模型及其装配器(含校验)
│ │ │ ├─assembler 装配器,,实现模型转换eg. apiModel<=> domainModel
│ │ │ └─dto 模型定义(含校验规则)
│ │ ├─service 应用服务,非核心服务,跨领域的协作、复杂分页查询等
│ │ ├─task 任务定义,协调领域模型
│ │ ├─listener 事件监听定义
│ │ └─*** others
│ │
│ ├─domain 领域层
│ │ ├─common 模块0-公共代码抽取,限于领域层有效
│ │ ├─module-xxx 模块1-xxx,领域划分的模块,可理解为子域划分
│ │ ├─module-user 模块2-用户子域(领域划分的模块,可理解为子域划分)
│ │ │ ├─action 行为定义
│ │ │ │ ├─UserDomainService.java 领域服务,用户领域服务
│ │ │ │ ├─UserPermissionChecker.java 其他行为,用户权限检查器
│ │ │ │ ├─WhenUserCreatedEventPublisher.java 领域事件,当用户创建完成时的事件
│ │ │ ├─model 领域聚合内模型
│ │ │ │ ├─UserEntity.java 领域实体,有唯一标识的充血模型,如本身的CRUD操作在此处
│ │ │ │ ├─UserDictVObj.java 领域值对象,用户字典kv定义
│ │ │ | ├─UserDPO.java 领域负载对象
│ │ │ ├─repostiory 领域仓储接口
│ │ │ │ ├─UserRepository.java
│ │ │ ├─reference 领域适配接口
│ │ │ │ ├─UserEmailSenderFacade.java
│ │ │ └─factory 领域工厂
│ │
│ ├─infrastructure 基础设施层
│ │ ├─persistence 持久化机制
│ │ │ ├─converter 持久化模型转换器
│ │ │ ├─po 持久化对象定义
│ │ │ └─repository.impl 仓储类,持久化接口&实现,可与ORM映射框架结合
│ │ ├─general 通用技术支持,向其他层输出通用服务
│ │ │ ├─config 配置类
│ │ │ ├─toolkit 工具类
│ │ │ ├─extension 扩展定义
│ │ │ └─common 基础公共模块等
│ │ ├─reference 引用层,包装外部接口用,防止穿插到Domain层腐化领域模型等
│ │ │ ├─dto 传输模型定义
│ │ │ ├─converter 传输模型转换器
│ │ │ └─facade.impl 适配器具体实现,此处的RPC、Http等调用
│ │
│ └─resources
│ ├─statics 静态资源
│ ├─template 系统页面
│ └─application.yml 全局配置文件
DDD的核心诉求就是将业务架构映射到系统架构上,在响应业务变化调整业务架构时,也随之变化系统架构。而微服务追求业务层面的复用,设计出来的系统架构和业务一致;在技术架构上则系统模块之间充分解耦,可以自由地选择合适的技术架构,去中心化地治理技术和数据。
名词解析
层次概念
代码层次
实体
当一个对象由其标识(而不是属性)区分时,这种对象称为实体(Entity)。
例:最简单的,公安系统的身份信息录入,对于人的模拟,即认为是实体,因为每个人是独一无二的,且其具有唯一标识(如公安系统分发的身份证号码)。
在实践上建议将属性的验证放到实体中。
值对象
当一个对象用于对事务进行描述而没有唯一标识时,它被称作值对象(Value Object)。
例:比如颜色信息,我们只需要知道 {“name”:“黑色”,”css”:“#000000”} 这样的值信息就能够满足要求了,这避免了我们对标识追踪带来的系统复杂性。它具有不变性、相等性和可替换性。
在实践中,需要保证值对象创建后就不能被修改,即不允许外部再修改其属性。在不同上下文集成时,会出现模型概念的公用,如商品模型会存在于电商的各个上下文中。在订单上下文中如果你只关注下单时商品信息快照,那么将商品对象视为值对象是很好的选择。
值对象很重要,在习惯了使用数据库的数据建模后,很容易将所有对象看作实体。使用值对象,可以更好地做系统优化、精简设计。
聚合根
Aggregate(聚合)是一组相关对象的集合,作为一个整体被外界访问,聚合根(Aggregate Root)是这个聚合的根节点。
聚合由根实体,值对象和实体组成。
领域服务
一些重要的领域行为或操作,可以归类为领域服务。它既不是实体,也不是值对象的范畴。
当我们采用了微服务架构风格,一切领域逻辑的对外暴露均需要通过领域服务来进行。如原本由聚合根暴露的业务逻辑也需要依托于领域服务。
领域事件
领域事件是对领域内发生的活动进行的建模。
VO&DTO&DO&PO
每一层都有自己特定的数据,可以做如下区分:
- VO(View Object):视图对象,主要对应界面显示的数据对象。对于一个WEB页面,或者SWT、SWING的一个界面,用一个VO对象对应整个界面的值。
- DTO(Data Transfer Object):数据传输对象,主要用于远程调用等需要大量传输对象的地方。比如我们一张表有100个字段,那么对应的PO就有100个属性。但是我们界面上只要显示10个字段,客户端用WEB service来获取数据,没有必要把整个PO对象传递到客户端,这时我们就可以用只有这10个属性的DTO来传递结果到客户端,这样也不会暴露服务端表结构.到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就转为VO。在这里,我泛指用于展示层与服务层之间的数据传输对象。
- DO(Domain Object):领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。
- PO(Persistent Object):持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性。最形象的理解就是一个PO就是数据库中的一条记录,好处是可以把一条记录作为一个对象处理,可以方便的转为其它对象。
PO是数据库表结构的一一映射,DO是数据库结构在领域服务层的处理对象。多个DO可以组成一个PO,一个DO不一定包含PO所有值。
前端发送请求(VO) -> 系统接收请求 (VO->DTO) -> 系统各个模块处理请求(DTO->DO) -> 持久化到库 -> (DO->PO)
博客
微信公众号博客
爪哇缪斯
是下雨天啊
VO、DTO、DO、PO的概念