DDD领域驱动
2024-06-07 17:38:34 0 举报
AI智能生成
领域驱动知识
作者其他创作
大纲/内容
菱形对称架构
菱形对称架构模式将整个限界上下文分为内部的领域层和外部的网关层,网关层根据调用方向分为北向网关和南向网关。北向网关体现了“封装”的设计思想,根据通信方式的不同分为远程服务与应用服务;南向网关体现了“抽象”的设计思想’将抽象与实现分离’分为端口与适配器
对象模型
贫血模型
领域对象中只有数据,没有行为
充血模型
领域对象里既包含数据,也包含行为
细节对比
仓储是以聚合为单位的,每个聚合有一个仓储,而 DAO 是以表为单位的,每个表有一个 DAO
领域场景分析
6W 模型
Who、What、Why、Where、When 与 hoW
分析方法
用例
用例图
用例的一种模型抽象,通过可视化的方式来表示参与者与用例之间的交互,用例与用例之间的关系以及系统的边界
用例图是领域专家与开发团队可以进行沟通的一种可视化手段,简单形象,还可以避免从一开始就陷入到技术细节中 —— 用例的关注点就是领域
用例之间的协作关系
包含 include
“包含” 关系意味着子用例是主用例中不可缺少的一个执行步骤,如果缺少了该子用例,主用例可能会变得不完整
扩展 extend
对主用例的一种补充或强化,即使没有该扩展用例,对主用例也不会产生直接影响,主用例自身仍然是完整的
领域服务
领域中的一些概念不太适合建模为对象,即归类到实体对象或值对象,因为它们本质上就是一些操作,一些动作,而不是事物。这些操作或动作往往会涉及到多个领域对象,并且需要协调这些领域对象共同完成这个操作或动作。
如果强行将这些操作职责分配给任何一个对象,则被分配的对象就是承担一些不该承担的职责,从而会导致对象的职责不明确很混乱。
但是基于类的面向对象语言规定任何属性或行为都必须放在对象里面。所以我们需要寻找一种新的模式来表示这种跨多个对象的操作,DDD 认为服务是一个很自然的范式用来对应这种跨多个对象的操作,所以就有了领域服务这个模式
和领域对象不同,领域服务是以动词开头来命名的,比如资金转帐服务可以命名为 MoneyTransferService。当然,你也可以把服务理解为一个对象,但这和一般意义上的对象有些区别。因为一般的领域对象都是有状态和行为的,而领域服务没有状态只有行为
领域服务还有一个很重要的功能就是可以避免领域逻辑泄露到应用层
因为如果没有领域服务,那么应用层会直接调用领域对象完成本该是属于领域服务该做的操作,这样一来,领域层可能会把一部分领域知识泄露到应用层。因为应用层需要了解每个领域对象的业务功能,具有哪些信息,以及它可能会与哪些其他领域对象交互,怎么交互等一系列领域知识。因此,引入领域服务可以有效的防治领域层的逻辑泄露到应用层。对于应用层来说,从可理解的角度来讲,通过调用领域服务提供的简单易懂但意义明确的接口肯定也要比直接操纵领域对象容易的多
虽然领域服务是领域设计建模的最后选择,但服务一词过于宽泛,很容易在分配职责时形成领域服务的扩大化
领域服务名为 ShippingService,是否可以把运输相关的职责都分配给它?要估算运费,和运输相关,放到ShippingService中;要处理分段运输,和运输相关,放到ShippingService中......长此以往,领域服务就会称为存放领域逻辑的超级大筐,失去了设计约束的领域服务,会在看似合理的职责分配下变得越来越庞大,聚合内的实体及值对象就会被削弱,最后领域模型设计又走回了贫血模型加事务脚本的老路
为避免将领域服务中的方法设计为一个过程式的事务脚本,可考虑控制领域服务的粒度,如保证它履行的职责为一个单一职责的领域行为
要求领域服务的名称必须包含动词,以体现领域服务的行为本质
领域建模的重要性
领域模型是对具有某个边界的领域的一个抽象,反映了领域内用户业务需求的本质;领域模型是有边界的,只反应了我们在领域内所关注的部分
领域模型只反映业务,和任何技术实现无关;领域模型不仅能反映领域中的一些实体概念,如货物,书本,应聘记录,地址,等;还能反映领域中的一些过程概念,如资金转账,等
领域模型确保了我们的软件的业务逻辑都在一个模型中,都在一个地方;这样对提高软件的可维护性,业务可理解性以及可重用性方面都有很好的帮助
领域模型能够帮助开发人员相对平滑地将领域知识转化为软件构造
领域模型贯穿软件分析、设计,以及开发的整个过程;领域专家、设计人员、开发人员通过领域模型进行交流,彼此共享知识与信息;因为大家面向的都是同一个模型,所以可以防止需求走样,可以让软件设计开发人员做出来的软件真正满足需求
领域模型是整个软件的核心,是软件中最有价值和最具竞争力的部分;设计足够精良且符合业务需求的领域模型能够更快速的响应需求变化
传统三层架构存在的问题
1. 接口清晰度
1. 如一个注册方法 register(String, String, String) 参数名在编译时会丢失。三个参数类型一致,只有在运行时才会发现bug
2. 数据验证和错误处理
在系统建设过程中,我们经常会看到这样的情形:A 负责提出需求,B 负责需求分析,C 负责系统设计,D 负责代码实现,这样的流程很长,经手的人也很多,很容易导致信息丢失。最后,就很容易导致需求、设计与代码实现的不一致,往往到了软件上线后,我们才发现很多功能并不是自己想要的,或者做出来的功能跟自己提出的需求偏差太大
聚合
聚合的本质就是建立了一个比对象粒度更大的边界,聚集那些紧密关联的对象,形成了一个业务上的对象整体。使用聚合根作为对外的交互入口,从而保证了多个互相关联的对象的一致性
通过把对象组织为聚合,在基本的对象层次之上构造了一层新的封装。封装简化了概念,隐藏了细节,在外部需要关心的模型元素数量进一步减少,复杂性下降
聚合划分的原则
生命周期一致性
问题域一致性
场景一致性
聚合内的元素尽可能少
聚合出现的本质是解决一致性问题带来的复杂性
聚合内部的对象之间可以相互引用,但是聚合外部如果要访问聚合内部的对象时,必须通过聚合根开始导航,绝对不能绕过聚合根直接访问聚合内的对象,也就是说聚合根是外部可以保持 对它的引用的唯一元素
聚合根负责与外部其他对象打交道并维护自己内部的业务规则
聚合的由来
1. 类的关系
泛化关系
体现了通用父类与子类之间的关系,父类定义通用特征,子类除继承父类额特征外还定义了符合自身特性的特殊实现
泛化关系导致子类与父类之间的强耦合,父类发生的任何变更都会传递给子类,形成所谓的脆弱的基类
关联关系
即组合关系
合成关系
不仅代表整体与部分的包含关系,还体现强烈的所有权ownership特征,也即使得组成该关系的两个对象属于同一生命周期
在UML中使用实心的菱形标记合成关系
如学校拥有对教室的所有权,学校被销毁后那么教室也就不复存在
聚合关系
同样代表整体与部分的包含关系,却没有所有权特征,不会约束他们的生命周期
在UML中使用空心的菱形来标记集合关系
如教室未曾拥有学生的所有权,教室被销毁后,学生依旧存在
依赖关系
代表一个类使用了另一个类的信息或服务
依赖关系存在方向,因此在UML中,使用带箭头的虚线表示
2. 模型的设计约束
领域对象模型表达领域概念映射的类及类之间的关系,类的关系导致对象之间的耦合。若不对类的关系加以控制,耦合就会蔓延。一旦需要考虑数据持久化、一致性、对象之间的通信机制以及加载数据的性能等设计约束,网状的耦合关系就会成为致命毒药,直接影响领域设计模型的质量
1.控制类的约束
去除不必要的关系
降低耦合强度
避免双向耦合
2. 引入边界
对关系的控制可以让对象模型中类之间的关系变得更简单,同时还需引入边界来降低和限制领域类之间的关系,不能让关系之间的传递无线蔓延
领域设计模型并非真实世界的直接映射。若真实世界缺乏清晰的边界,在设计时我们就应该给它清晰的划定边界
这种边界不是限界上下文形成的控制边界,因为它限制的粒度更细,可认为是类层次的边界。每个边界都有一个主对象,总体负责与外部的协作。此种类层次的边界即集合,边界内的主对象即聚合根
聚合的定义和特征
聚合是包含了实体和值对象的一个边界
聚合内包含的实际及值对象形成一棵树,只有实体才能作为这棵树的根。这个根称为聚合根,实体称为根实体
外部对象只允许持有聚合根的引用,以起到边界的控制作用
聚合作为一个完整的领域概念整体,其内部会维护这个领域概念的完整性,体现业务善的不变量约束
由聚合根统一提供履行该领域概念职责的行为方法,实现内部各个对象之间的行为协作
聚合的设计原则
完整性
独立性
不变量
一致性
领域模型和微服务边界的划定步骤
1. 在事件风暴中梳理业务过程的用户操作、事件以及外部依赖关系等,根据这些要素梳理出领域实体等领域对象
2. 根据领域实体之间的业务关联性,将业务紧密相关的实体进行组合形成聚合,同时确定聚合中的聚合根、值对象和实体
3. 根据业务及语义边界等因素,将一个或者多个聚合划定在一个限界上下文内,形成领域模型
历史架构演进
微服务与DDD区别
DDD是一种架构设计方法。主要关注:从业务领域视角划分领域边界,构建通用语言进行高效沟通,通过业务抽象,建立领域模型,维持业务和代码的逻辑一致性。
微服务是一种架构风格。主要关注:运行时的进程间通信、容错和故障隔离,实现去中心化数据管理和去中心化服务治理,关注微服务的独立开发、测试、构建和部署。
实体
身份标识
属性
领域行为
变更状态的领域行为
自给自足的领域行为
互相协作的领域行为
值对象
不变性
值对象设计为不可变的,无需考虑并发访问问题,因而比实体更容易维护,测试及优化
领域行为
自我验证
自我组合
自我运算
优势
内建数据类型无法展现领域概念
内建数据类型无法封装领域逻辑
内建数据类型缺乏验证能力
案例
员工与客户的地址address属性都被定义为一个相同的Address类型。在领域模型中,员工与客户属于两个不同的聚合,他们对Address类的复用意味着需要让Address在两个聚合中形成副本,故需将Address及ZipCode定义为值对象
0 条评论
下一页