DDD领域驱动模型理解篇
参考
https://www.cnblogs.com/laozhang-is-phi/p/9806335.html
https://www.cnblogs.com/uoyo/category/1602382.html
领域驱动设计整体架构
Presentation Layer:表现层,负责显示和接受输入
Application Layer:应用层,包含工作流控制逻辑,不包含业务逻辑
Domain Layer:领域层,包含整个应用的所有业务逻辑
(1)实体、聚合、值对象
(2)领域服务、领域事件、工厂
(3)仓储接口
Infrastructure:基础层,提供整个应用的基础服务
(1)基础服务
(2)仓储实现
贫血模型和充血模型
贫血模型:是指Model 中,仅包含状态(属性),不包含行为(方法),采用这种设计时,需要分离出DB层,专门用于数据库操作。
充血模型:Model 中既包括状态,又包括行为
框架结构
一切从领域开始定义
基础层对领域进行实现
应用层定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题
DDD的四种模型
(1)数据模型:面向持久化,数据的载体
(2)领域模型:面向业务,行为的载体
(3)视图模型:面向UI(向外),数据的载体
(4)命令模型:面向UI(向内),数据的载体
CQRS读写分离
任何一个对象的方法可以分为俩大类
(1)命令(Command):不会返回任何结果(void),但会改变对象的状态
(2)查询(Query):返回结果,但是不会改变对象的状态,对系统没有副作用
本篇博客主要是记录了学习DDD的经历以及自己的理解
主要参考了https://www.cnblogs.com/uoyo/category/1602382.html
1.值对象
1.1什么是值对象
1.1.1值对象是基于上下文的,一切脱离了上下文的值对象是没有作用的,当前上下文的值对象可能是另一个上下文的实体
1.2.运用值对象
1.2.1尽量避免使用基元类型
1.2.2值对象是内聚并且具有行为
值对象是不可变的:一旦创建好之后,值对象就永远不能变更了。相反,任何变更其值得尝试,
其结果都应该是创建带有期望值的整个新实例
1.3.值对象的持久化
(1)将值对象映射在表的字段中
(2)将值对象化单独用作表来存储
2.实体
2.1什么是实体
(1)实体(Entity,又称为Reference Object) 很多对象不是通过他们的属性定义的,而是通过一连串的连续事件和标识定义的。
(2)主要由标识定义的对象被称为ENTITY。
2.2运用实体
2.2.1当前上下文的值对象可能是另一个上下文的实体。所以实体一定是基于领域当前环境(上下文)的。
2.2.2实体是高度内聚和自治的
贫血模型:仅仅具有类的属性
充血模型:通过将实体赋予它应用的行为所建立出来的实体
应该专注于实体的行为而非数据
3.领域服务
3.1什么是领域服务
在某些情况下,最清楚、最实用的设计会包含一些特殊的操作,这些操作从概念上讲不属于任何对象
与其把它们强制归于哪一类,不如顺其自然地在模型中引入一种新的元素,这就是Service(服务)
3.2领域服务的特点
(1)领域服务处理的是领域中的对象,比如实体、值对象等
(2)领域服务是负责对领域中一系列对象的编排处理
(3)当我们发现一个操作无法赋予一个实体或者值对象,且该操作又对业务流程很重要时,往往需要使用领域服务
(4)领域服务中的操作,从领域的角度,它是一个整体
如果你在进行下面的操作时,可能证明你需要一个领域服务:
(1)通过A和B,得到一个C
(2)A需要一个繁琐的内部策略才能得到一个结果B
3.3领域服务和应用服务
(1)应用服务是用来表达用例和用户故事(User Story)的主要手段
(2)应用层通过应用服务接口暴露系统的全部功能。在应用服务的实现中,它负责编排和转发,它将要实现的功能委托给一个或多个领域对象来实现,它本身只负责处理业务用例的执行顺序以及结果的拼装。
应用层保持了对领域层的引用关系,所以应用层也具备了编排领域对象的能力。
应用服务是为了能够运用并且支撑对外的用户能够访问领域对象和执行领域逻辑的一层
常见的认证授权是应用服务,但如果是权限系统,它可能会被定义在领域中
当尝试强制将行为适配到实体中的任何一个时,就需要领域服务
不要过多的使用领域服务
尝试将部分调度权限分配给应用服务
不要将过多的行为都给了领域服务
4.聚合
4.1什么是聚合
在具有复杂关联的模型中想要保证对象更改的一致性是很困难的。不仅互不关联的对象需要遵守一些固定规则,而且紧密关联的各组对象也要遵守一些固定规则。过于谨慎的锁定机制又会导致多个用户之间毫无意义地互相干扰,从而使系统不可用
每个AGGREGATE都有一个根(root)和一个边界(boundary)。边界定义了AGGREGATE的内部都有什么。根则是AGGREGATE中所包含的一个特定Entity。在AGGREGATE中,根是唯一允许外部对象保持对它的引用的元素,而边界内部的对象之间则可以互相引用。除根以外的其他Entity都有本地标识,外部对象除了根Entity之外看不到其他对象。
在聚合关系中,被我们选取出来作为边界范围的实体就是聚合根。
当是被出了一个聚合根的时候,就要保证聚合的内部是自治的,我们不能从外界直接访问聚合根内部的任何领域对象。
4.2聚合的一些特性
(1)聚合是一个明确的边界
(2)聚合的出现是为了解决领域模型之间的复杂关联关系的
(3)聚合封装了一系列的相关对象,它是这些对象的集合
(4)聚合应该有一个根,并且这个根是通过集合中的一个实体选出来的
(5)聚合外部的事物想引用聚合只能通过根的ID来访问
4.2.1通过ID引用
当一个聚合需要引用到另外一个聚合的时候,千万不要直接使用类型的强引用方式实现,而是通过使用引用聚合的ID来维持聚合与聚合的关系。
4.2.2聚合真的不变的吗
如果需求确实需要单独访问目前聚合根里面的实体,它可能会被单独提升为一个聚合。而且通过ID之间的引用保持对原有聚合根之间的关联关系。
4.2.3小的聚合
当聚合A中的实体EntityA存在大量数据的时候,我们访问聚合A不得不去加载它们,这样会让性能造成大量损失,哪怕建模是正确的的,我们还是会考虑折中的办法,将EntityA提升为一个单独的聚合供外界单独访问
4.2.4一致性
聚合中的所有对象都应该保持一致的变更。必须保证多个聚合之间的一致性。(最终一致性)
5.存储库
5.1什么是存储库
为每种需要全局访问的对象类型创建一个对象,这个对象就相当于该类型的所有对象在内存中的一个集合的"替身"。
通过一个众所周知的接口来提供访问。提供添加和删除对象的方法,用这些方法来封装在数据存储中实际插入或删除数据的操作。
提供根据具体标准来挑选对象的方法,并返回属性值满足查询标准的对象或对象集合(所返回的对象是完全实例化的),从而将实际的存储和查询技术封装起来。只为那些确实需要直接访问的Aggregate提供Repository。
让客户始终聚焦于型,而将所有对象存储和访问操作交给Repository来完成。
5.2如何运用存储库
5.2.1存储卡是为聚合提供操作
存储库是为聚合而服务的。
5.2.3存储库是一个明确的约定
在许多场景下,我们需要根据某种条件来从数据库中读取对应的模型并讲其转换为领域聚合对象
尽量避免在存储库中写灵活而没有任何明确检索意图的方法接口,会慢慢使应用层越来越迷惑。
存储库不是一个对象。它是一个程序边界以及一个明确的约定,在其上命名方法时它需要的工作量与领域模型中的对象所需的工作量一样多。你的存储库约定应该是特定的以及能够揭示意图并对领域专家具有意义
具有领域意图的东西我们都应该放在领域层,而类似于数据库访问实现这类基础架构应该放在基础设施层。所以抽象出来的仓储接口应该放在领域层,而仓储的实现可以放在基础设施层。
5.2.4审计追踪
当聚合根在领域服务或者领域用例已经完成操作时,将它传递给存储库持久化之前就可以让存储库为它加上审计信息。
5.2.5汇总
汇总的功能也可以交给存储库来完成。但不要因为汇总就将存储库写的开放而过于灵活。
5.3不要使用过多特性干扰您的领域对象
因为我们在构建领域对象的时候不应该考虑数据持久层面的问题,而构建出来的领域对象也应该保持干净
在EFCore中提供了Fluent API的方式配置模型,该方式可以很好的让领域对象保持干净。
5.4不要为了显示而使用存储库
(1)使用AOD.NET运用原生的sql达到查询效果
(2)DDD的另外一种模式:CQRS
5.5工作单元
在持久化的过程中,必须保证聚合所有部分一同保持成功,或者一个用例的多个聚合同时保存成功(分布式中可能最求最终一致性)。所以我们必须保证存储库是有事务的,而事务的管理是由工作单元来提供的。
5.6持久化中的困难
如何将领域对象通过ORM持久化到数据库
领域模型:领域模式是问题域的抽象,富含行为和语言。
数据模型:数据模型是一种包含指定时间领域状态的存储结构,ORM可以将特定的对象(C#的类)映射到数据模型。
存储库的作用就是保持着俩个模型的独立并且不让它们变得模糊不清
6.持久化领域对象的方法实践
6.1将值对象持久化为字段
值对象不应该有ID
6.2将值对象持久化为表
6.2.1将集合值对象存为json或string
缺点:
(1)无法在集合中的单个项中执行有效搜索
(2)如果集合中有很多项,这种方法可能会影响性能
(3)不支持多层值对象
6.2.2将集合值对象存为表
结果:
6.3基于快照的数据存储对象
领域模型是问题域的抽象,富含行为和语言;数据模型是一种包含特定时间领域模型状态的存储结构,ORM可以将特定的对象(C#的类)映射到数据模型
7.领域事件
7.1什么是领域事件
领域专家所关心的发生在领域中的一些事件。
将领域中所发生的活动建模成一系列的离散事件。每个事件都用领域对象来表示,领域事件是领域模型的组成部分,表示领域中所发生的事情。
7.2如何使用领域事件
当事件发生的时候,与该事件关联的对象都将收到波及。
在领域驱动设计建模过程中,如果发现有一项动作发生了之后,与之关联的其他领域对象都将收到波及。
领域事件一方面充当了描述领域信息的作用,一方面承接了不同聚合根之间的交互。当然事件不一定只有一个,
被影响的领域对象也不一定只有一个。
当要使用领域事件时,您将认同您的项目需要以事件作为中心。而项目中的各个领域对象都将以产生、发布领域事件完成一系列的交互流程。
内部事件发生在边界之内,外部事件发生爱边界之外。
域事件和外部事件
从语义上域事件和集成事件是相同的:都是对已发生事件的通知。
域事件是推送到域事件调度程序的消息,可基于IOC容器或其他方法作为内存中转存进程实现。
集成事件的目的是将已提交事务和更新传播到其他子系统,无论他们是其他服务、绑定上下文,还是外部应用程序。因此,它们应仅在成功保存实体时发生,否则如果整个操作从未发生一样。
集成事件必须基于多个微服务(其他绑定上下文)或外部系统/应用程序之间的异步通信。
因此,事件总线接口需要一些基础结构来实现远程服务之间的进程间和分布式通信。可以基于商用服务总线、队列、作为邮箱的共享数据库或其他分布式和基于推送的消息传送系统。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析