DDD学习笔记五

模型引力场:聚合

  • 强作用力体现:
    某个领域模型是另一些模型存在的前提,没有前者,后者就失去了生存的意义。
    一组领域模型之间存在关联的领域逻辑,任何时候都不能违反。
    一组领域模型必须以一个完整的、一致的状态呈现给外部,而不能是某个中间彼此不和谐的状态。

  • 当模型之间的相互作用力强到一定程度的时候,将产生质变,必须把它们当作一个整体,我们称之为聚合

  • 聚合是一组相关对象的集合,可以称其为数据修改单元或者事务一致性组合单元。
    每个聚合都有一个根(root)和一个边界(boundary)。
    边界定义了聚合内部包含的内容,
    根则是聚合所包含的一个特定的实体(entity)。

  • 聚合概念的必要性:
    1)保证模型存在的意义。聚合内的成员脱离聚合根的存在是没有意义的,比如支付之于订单。这不是一个技术问题,而是一个领域逻辑问题,领域专家和用户不会在意一个没有订单的支付对象。
    2)时刻保持领域内在逻辑不被违反。这里的关键字是“时刻”,即不允许在任何时候把不和谐的状态暴露给用户。相关逻辑一般体现在聚合根内。
    3)划定事务的合理边界。在某种程度上,划定聚合就是划定事务边界。如果事务范围过大,锁定机制会导致多个用户之间毫无意义的互相干扰,而如果事务范围过小,会产生实时一致性问题,有把不完整的模型状态呈现给客户的风险。并不是强求时刻一致性就好,因为有性能代价,但无视客户体验也并非良策。聚合代表着两者的平衡。
    4)聚合根提供了业务操作入口和界面。前三点偏技术,这一点对领域专家来说可能是最重要的。
    聚合对于使用者来说是一个“整体”,不能把其中的成员当作独立的成员来看待。

  • 聚合内的约束一般归纳为以下6条:
    1)只有聚合根具有全局标识,它最终负责领域内在规则的检查。有标识意味着聚合根必须是一个实体,而不是值对象。聚合根负责校验聚合中的规则而不是其他成员或在聚合外部。
    2)聚合范围内的实体具有本地标识,这些标识不需要全局范围内唯一,保证在聚合范围内唯一即可。为何不需要全局标识呢?这是由第三条约束决定的。
    3)只能通过聚合根才能访问聚合内的其他元素,聚合根是其所在聚合所有模型的唯一操作界面。不要略过订单去操控支付对象,不要忽略车辆而去保养轮胎,不要省掉棋盘去访问棋子,不要不看新闻就去看评论。聚合保证了对象存在的意义,避免产生不符合逻辑的操作。这些例子中,脱离聚合根去访问对象是没有实际意义的。
    聚合根可以把对内部实体的引用传递给外部,但只是临时使用这些引用,而不能一直保持引用。这意味着外部获得的是内部对象的一个副本,对这个副本的修改丝毫不影响聚合内真实的对象。
    4)只有聚合根才能直接通过数据库查询获取,其他对象必须通过遍历关联发现。当然,聚合的获取一般通过工厂和存储库
    5)删除根元素,必须一次删除聚合内所有对象。这个问题在有垃圾回收机制的语言中不用过分操心,因为缺少外部引用,当聚合根被删除之后,其他对象会被自动回收。
    6)当聚合内部任何对象被修改时,整个聚合内的所有规则都必须被满足。这里不是指有延迟的最终一致性,而是任何时间点都不存在中间状态。

  • 7条聚合设计法则
    (1)与生命周期保持一致
    如果一个部分脱离整体后不再有业务意义,那么该部分应当属于一个聚合;如果一个模型脱离了聚合根仍有其业务价值,那么它就不属于该聚合。比如,发动机与汽车虽然存在明显的从属关系,但因为发动机有自己的编号且脱离汽车还会被单独跟踪,所以它不属于汽车这个聚合。当然,是不是一个聚合不会影响汽车和发动机正常的关联关系
    (2)围绕领域内在逻辑
    在定义聚合时,需要识别出满足一个用例共同工作的对象组合,并且这些对象彼此必须时刻保持一致以满足业务用例
    (3)与事务的粒度保持一致
    (4)不滥用聚合
    (5)作用范围宜小不宜大
    大的聚合会影响性能。成员数据在聚合被创建时都需要从数据库中加载,当集合成员快速增长时,对内存的占用也不可忽视
    小的聚合不仅有利于性能,还有助于事务的成功执行,可以减少事务提交的冲突,系统的可用性也获得了提升(不再锁定资源)
    (6)通过标识符引用其他聚合
    聚合是小巧的,但关联是丰富的。一个聚合可以引用另一个聚合的聚合根来完成自己的任务。但被引用的聚合根不应该放在引用聚合的内部,这不符合聚合的规则。另外,在引用聚合根时,应该优先考虑全局唯一标识(ID)而不是通过直接的对象引用
    (7)利用最终一致性更新其他聚合
    聚合内的规则是时刻不能被违反的,但聚合间的规则要弱一些,所以跨越多个聚合的规则不立刻保持一致是可以接受的(容忍时长取决于业务),我们可以应用最终一致性规则来更新其他聚合
    最终一致性意味着不同聚合间的数据在一段时间内会存在一定的不一致状态,但它们最终会保持一致,否则就不符合规则了

  • 实现方法

  • (1)如何限定访问
    尽量不要开放聚合内部成员的可见性为public。如果确实需要引用,也不要开放该属性的设置(set)功能,可以设为私有可见性,从而保护其不被修改。

  • (2)如何验证规则
    验证规则是在聚合根完成的,保证规则的方法就是封装聚合的责任和行为,在对应的业务操作和方法中验证领域内在规则,而不是暴露成员

  • (3)如何同生共消亡
    往往采用工厂方法来实现同生。工厂一次性创建聚合的所有成员,并且按照一定规则组装。工厂可以放置在聚合根内,也可以是一个单独的领域服务。
    不要为聚合成员提供单独的构造函数,理论上也不应将聚合组装的权力交给用户,因为组装体现的是领域逻辑而不是给用户的自由。聚合成员最好只依赖于工厂而被创建,避免被越级使用而产生副作用。
    共消亡机制要用到存储库模式,存储库将只为客户端提供对聚合根的访问,我们无法通过存储库去访问不是聚合根的对象,也就不会越过聚合根获得其成员的引用。
    在有垃圾回收机制的语言中,没有引用的对象会被定时回收,比如Java、C#。当然,也可以在聚合根使用析构函数或实现IDispose接口,来显式释放其他成员占用的资源。

  • (4)实现事务一致性
    第一,不要在一个事务中更新一个以上的聚合。
    第二,聚合作为一个整体,持久化时必须在一个事务中以保证内在的一致性。聚合是存储在一个表或多个表中并不重要,重要的是当聚合被持久化时,需要在单个事务中提交,以确保在持文化失败时,聚合不会以不一致的状态被存储。
    实现事务一致性很大程度上依赖于所采用的持久化技术。ORM(Object Relational Mapping,对象关系映射)框架如Hibernate和NHibernate提供了对几乎一切数据库命令的显式事务的支持。

  • (5)实现最终一致性
    实现最终一致性保证的是聚合间的一致性,而不是聚合内的一致性。常见策略是采用异步方法来处理数据不一致问题,这与本地事务有所区别。虽然异步方法会导致聚合间数据不一致的时间较长,但其实现更为可靠且不会对并发性能产生影响。之所以说更为可靠,是因为当操作失败时通常可以重试,但必须采用kafka、MQ等消息传递技术。
    聚合间的最终一致性可以通过异步领域事件来实现,使用EventBus等事件发布和订阅框架可以很方便地发布和订阅领域事件。

** 模型装配线:工厂**

  • 在DDD中,工厂是生产领域模型的地方,特别是聚合

  • 工厂模式的主要目的是生产对象的实例并提供给调用者

  • 为什么需要工厂
    (1)解耦:分离领域职责与创建工序
    工厂的主要目的是分离模型的领域职责及其复杂的创建工序
    (2)通用语言:让创建过程体现业务含义
    工厂是领域层中承载领域逻辑的对象
    (3)验证:确保所创建的聚合处于正确状态
    在工厂中创建新对象时,可以添加逻辑验证以确保创建出的聚合符合领域内在规则。
    例如,在聚合根账户上创建订单,要满足账户必须有足够的信用额度
    (4)多态:为一种接口生产多个组件
    (5)重建:重建已存储的对象
    基于持久化机制的对象重建一定要封装在工厂之内。
    重建工厂与创建新对象的工厂有以下两个不同点:
    1)创建实体对象时,新对象是生成新的标识符,而重建工厂则是获取已有的标识符ID。
    2)模型或聚合的内在领域逻辑不满足时,新对象工厂可以直接拒绝生成对象,而重建工厂生成的对象违背规则时,需要设计师采用一种纠错机制,比如默认值等策略来处理冲突。

  • 厂址选择
    (1)聚合根上的工厂
    如果往一个聚合内添加元素,可以在聚合根上添加一个工厂方法,这样聚合内部的元素的生成细节,外部就无须关心了。同时,因为聚合的内在原则检查都在聚合根内,所以可以保证添加的元素都符合领域内在规则
    (2)“信息专家”工厂
    信息专家模式是把职责分配给具有完成该职责所需信息的那个模型
    (3)领域服务类工厂
    将工厂单独地构建为领域服务是一种不错的方法,也是最常用的工厂形式
    (4)只需使用构造函数的场合
    是否任何模型的创建都要经过工厂呢?恰恰相反,我们应该优先使用构造函数而不是工厂,因为领域模型并不一定都是复杂对象或聚合。如果在不需要解耦、不需要创建聚合、不需要表达通用语言、没有内在规则或不需要多态的场合,应该直接使用构造函数new,因为构造函数更简单、方便
    若满足以下条件,则可直接选择简单的、公共构造函数。
    对象是值对象,且不是任何相关层次结构的一部分,而且不需要创建对象多态性。
    客户关心的是具体类,而不是只关心接口。
    客户可以访问对象的所有属性,且模型没有嵌套对象的创建。
    构造环节并不复杂,客户端创建代价不高。
    构造函数必须满足工厂的相同规则:创建过程必须是一个原子操作,且能满足领域内在规则。
    (5)厂名选择
    1)选择与领域含义相关的命名,如BookTicket(预订车票)、ScheduleMeeting(安排会议)。
    2)将Create与要创建的类型名连在一起,以此来命名工厂方法,如CreateWhite-Board。
    3)将创建的类型名与Factory连接在一起,以此来命名工厂类型。例如,可以将创建Role对象的工厂类型命名为RoleFactory。
    模型货架:存储库

  • 领域模型要想保持自己的独立性,离不开存储库将其与持久化机制解耦

  • 存储库承担了4个角色:隔离墙、冰箱和菜单、体现通用语言和管理员

  • (1)隔离墙:隔离领域模型与持久化技术,保证领域模型独立性
    存储库模式的第一个意义在于保持领域模型与技术持久化机制的分离。这保证了领域模型的独立性,使模型能够在不受底层技术影响的情况下进行演化,让我们可以独立开发领域模型,无须关注架构的技术细节
    在这里插入图片描述

  • 存储库分为两部分
    1)存储库接口:位于领域层内,既有标准化的方法,如增加和删除,又有体现通用语言的、业务上、特殊的检索需求。
    2)存储库实现:位于基础设施层,实现存储库接口。图6-7是一个典型的依赖倒置架构,领域模型无须关心存储库的实现部分,而只需要和存储库接口打交道即可。

  • 2)冰箱和菜单:保鲜且提供菜肴而不是食材
    存储库要负责所有对象的持久化工作,同时提供这些对象的访问接口。如果把模型比作一道道菜,存储库就是存放这些菜的冰箱,当你下次使用它们时,依然保持着上次放入冰箱的状态。存储库要保证只保存和提供成品菜肴,也就是聚合
    一个聚合提供一个存储库,即一个聚合对应一个存储库,代码实现上也是一对一存在的

  • (3)体现通用语言:让检索请求体现业务含义
    除了通用的接口方法,每个聚合的存储库必须支持通用语言的特定查询。存储库不仅是CRUD接口,还是领域模型的扩展,应当以领域专家的术语来编写,应当基于用例实现来构建查询接口,而不是类似于CRUD数据访问的角度来构建。

  • (4)管理员:集合的统计与汇总
    作为模型仓库,存储库的另一个实用的功能是扮演仓库管理员,给我们提供关于集合的统计信息,比如对象的数量、集合中所有匹配对象的某个数值属性的总和等。此时,存储库提供的方法不再返回一个聚合根,而是一个值对象。对于“最好贴近原始数据进行计算”的计算原则来说,存储库的这个功能非常强大

  • 实现存储库接口时,我们要注意以下几点:
    要把Add()方法实现为幂等,即使向集合重复添加相同聚合(一般由ID判断),实际效果仅为一个聚合实例。
    如果持久化机制支持对对象变化的跟踪,那么它会自动将内存中的更改保存到数据库中,比如Hibernate框架,在存储库接口定义中不需要添加Save()方法。但如果持久化机制不支持对变化的跟踪,则上述接口中需要添加Save()方法,并在领域对象被改变后,显式调用该方法,以使更改在数据库中生效。
    不要在领域模型上维护已更改的标识符,也不要让持久化逻辑干扰领域模型的纯洁性。将跟踪变化的工作交由存储库来处理。

  • 存储库与工厂的设计出发点有些相似,但有区别。:
    1)负责模型生命周期的不同阶段。工厂负责模型生命周期的开始,而存储库管理模型生命周期的中间和结束。工厂的作用之一是重建已存储的对象,这与存储库有些类似,但此时对象还不存在或未成型,因此具有“新建”的含义。而在存储库中的对象,则让用户感觉是一直存在于内存集合中的既有对象。
    2)存储库专注于封装持久化机制,而工厂专注于创建新对象。工厂用数据和领域逻辑来初始化和装配一个复杂的对象(聚合)。新对象创建好之后,需要调用存储库的Add()方法将其添加到存储库中,由存储库负责其在数据库中的存储。
    3)工厂更注重于对象创建的多态机制,而存储库分为两个部分,接口部分更注重符合业务需求和通用语言的支持,实现部分的重点则是对持久化技术的封装。

  • 存储库与数据访问对象的区别
    存储库面向的是内存中的集合方式,而DAO面向数据库表提供CRUD操作。
    存储库的设计更加贴近领域,而DAO中方法的业务意图并不明显。
    存储库接口必须位于领域层内,且只提供聚合根的访问,DAO没有这种约束。

  • 存储库实现的注意事项
    (1)不要提供无条件随机查询接口
    (2)可以像工厂一样在存储库中使用多态,返回子类或实现类
    (3)充分利用存储库接口与实现解耦的特点
    (4)存储库中不要涉及事务

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/759642.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

魔行观察-烤匠麻辣烤鱼-开关店监测-时间段:2011年1月 至 2024年6月

今日监测对象:烤匠麻辣烤鱼,监测时间段:2011年1月 至 2024年6月 本文用到数据源获取地址 魔行观察http://www.wmomo.com/ 品牌介绍: 2013年,第一家烤匠在成都蓝色加勒比广场开业,随后几年成都国金中心店…

类与对象的创建

1.类是一种抽象的数据类型,他是对某一类事务整体描述/定义,但是并不能代表某一个具体的事物 eg:动物,植物,手机,电脑... Person类,Pet类,Car类,这些类都是用来描述、定义…

VMware17.0 安装过程

VMware17.0 VMware 17.0 是一款功能强大的虚拟机软件,用于在计算机上创建和管理虚拟机。它能够同时运行多个操作系统,如 Windows、Linux 等,并且在这些虚拟机之间提供无缝的切换和共享功能。 VMware 17.0 支持最新的硬件和操作系统&#xf…

Markdown、Latex编辑小工具

Markdown、Latex编辑小工具 文章说明主要代码效果展示源码下载 文章说明 本文主要为了书写Latex的书写风格,以及了解自己实现一个markdown类型的编辑器的过程;目前实现了当前的效果;书写文章进行记录,方便后续查阅 目前还未添加好…

库存管理系统基于spingboot vue的前后端分离仓库库存管理系统java项目java课程设计java毕业设计

文章目录 库存管理系统一、项目演示二、项目介绍三、部分功能截图四、部分代码展示五、底部获取项目源码(9.9¥带走) 库存管理系统 一、项目演示 库存管理系统 二、项目介绍 基于spingboot和vue前后端分离的库存管理系统 功能模块&#xff…

鸿蒙开发设备管理:【@ohos.multimodalInput.inputEventClient (注入按键)】

注入按键 InputEventClient模块提供了注入按键能力。 说明: 本模块首批接口从API version 8开始支持。后续版本的新增接口,采用上角标单独标记接口的起始版本。本模块接口均为系统接口,三方应用不支持调用。 导入模块 import inputEventCli…

1、音视频解封装流程---解复用

对于一个视频文件(mp4格式/flv格式),audio_pkt或者video_pkt是其最基本的数据单元,即视频文件是由独立的视频编码包或者音频编码包组成的。 解复用就是从视频文件中把视频包/音频包单独读取出来保存成独立文件,那么如何得知packet是视频包还是…

账号和权限的管理1

文章目录 修改用户账号的属性usermod格式常用选项 用户账号的初始化配置文件文件来源主要的用户初始配置文件 组账号文件添加组账号groupadd格式常用选项其他选项 删除组账号groupdel格式 查询账号信息groups格式 id格式 finger格式 W、who、users格式 文件/目录的权限和归属访…

Linux实用命令练习

目录 一、常用命令 二、系统命令 三、用户和组 四、权限 五、文件相关命令 六、查找 七、正则表达式 八、输入输出重定向 九、进程控制 十、其他命令 1、远程文件复制:scp 2、locate查找 3、which命令 4、设置或显示环境变量:export 5、修…

Free Pascal语言基础学习:定义变量、数据类型、循环语句、case语句、条件判断、with语句、运算符

Pascal是一种结构化编程语言,而Free Pascal作为其现代编译器,不仅支持跨多种操作系统和处理器架构,还提供了高效的内存使用和函数重载等先进功能。Free Pascal继承了Pascal语言的核心特性,同时进行了扩展和优化,使其成…

最流行的文件同步软件

PanguFlow是一款免费的文件同步软件,他支持文件的全量同步、支持文件的增量同步、支持文件的实时备份,支持双向同步,支持三向同步甚至多向同步,支持无人值守运行。 PanguFlow数据同步软件下载地址https://pan.baidu.com/s/1GLjFR…

python实现网页自动化(自动登录需要验证的网页)

引言: python作为实现网页自动化的一个重要工具,其强大的各种封装的库使得程序运行更加简洁,只需要下载相应的库,然后调用库中的函数就可以简便的实现我们想要的网页相关操作。 正文: 我的前几篇文章写了关于初学爬虫中比较容易上手的功能,例如爬取静态网页的数据、动…

【Elasticsearch】linux使用supervisor常驻Elasticsearch,centos6.10安装 supervisor

背景: linux服务器,CentOS 6操作系统,默认版本python2.6.6,避免安装过多的依赖不升级python 在网上查的资料python2.6.6兼容supervisor版本 3.1.3 安装supervisor 手动在python官网下载supervisor,并上传到服务器 下…

解锁横向招聘:创新您的人才搜索

技能差距仍然是面试官面临的问题之一。在这些空缺职位中,很难找到合适的技能候选人,特别是高级职位或以上职位。另一方面,申请人也发现很难找到一份适合自己的工作,因为他们抱怨工作要求太窄或太具体。在具有挑战性的职位招聘环境…

扛鼎中国AI搜索,天工凭什么?

人类的创作不会没有瓶颈,但AI的热度可不会消停。 大模型之战依旧精彩,OpenAI选择在Google前一天举行发布会,两家AI企业之间的拉扯赚足了热度。 反观国内,百模大战激发了大家对于科技变革的热切期盼,而如今行业已逐渐…

生成独立的zedboard+ad9361起始项目

文件分享 链接:https://pan.baidu.com/s/17wB_9xVWjO7HhxNvmmZyuA 提取码:94zz 首先下载HDL和NO-OS项目 git clone --recursive https://github.com/analogdevicesinc/hdl git clone --recursive https://github.com/analogdevicesinc/no-OS下载…

用人工智能大模型预报气象,中国气象局示范计划公开征集火热报名中

近日,中国气象局发布了人工智能气象预报大模型示范计划(以下简称“示范计划”),推进气象大模型标准规范和有序发展,引导解决预报业务实际难题,促进人工智能气象预报大模型业务的应用转化、准入,…

【Linux】初识操作系统

一、冯•诺依曼体系结构 在学习操作系统之前,我们先来认识一下冯•诺依曼体系结构,我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。 截至目前,我们所认识的计算机&am…

Navicat上新啦

前言 Navicat,在数据库界,几乎是一个神奇的存在,似乎统治了数据库开发工具的“一片天”。且看下图: 红的蓝的绿的橙的…,可以说,留给它的color不多了。 那么商业BI到服务监控、从云托管到云协作&#xff…

VUE3-Elementplus-form表单-笔记

1. 结构相关 el-row表示一行,一行分成24份 el-col表示列 (1) :span"12" 代表在一行中,占12份 (50%) (2) :span"6" 表示在一行中,占6份 (25%) (3) :offset"3" 代表在一行中,左侧margin份数 el…