DDD强调领域模型的模型在创建、搜索、持久化时不应该考虑技术实现细节,对于不仅仅在内存中使用的领域对象持久化是必须的,为了让领域逻辑和基础架构代码之间分离可以采用存储库,存储库用于将技术的复杂性保存在领域模型之外。
1.存储库
存储库用于管理聚合的之持久化以及确保在领域模型和数据模型之间存在隔离的同时进行检索,
存储库和传统的访问模式不同点 ?
1.仅允许通过聚合根进行检索和持久化来限制领域对象的访问,确保聚合根处理所有的不变条件。
2.通过隐藏用于持久化和检索聚合的底层技术保持透明。
3.定义领域模型和数据模型的边界。
一个存储库的案例
image.png
这里定义的就是一组接口,接口内的函数名都是目的明确的,这个以前接触到定义一个功能强大的通用函数不一样。这个接口的实现可以由任何方式实现,比如数据可能是存储在redis、postgre、mysql或者缓存中,上层不必关心实现细节。
2.一种被误解的模式
2.1 常见误解
- 误解1:
存储库有时看来是一个多于的仪式,抽象这个接口是不必要的,尤其是定义的方法是比较明确的,不如定义一个功能全的方法具有通用性,比如查询顾客列表,方法接收参数直接接收一个query条件让外面组合就行。
对于没有丰富的领域模型的情况下,比如就是简单的数据查询业务,这个确实是不必要的,在为复杂领域建模解决方法时,存储库是模型扩展,它定义的方法会揭示领域检索的意图,并且能够基于领域而非技术编写。
-
误解2:存储库隐藏了持久化细节
这正是存储库的意义所在,通过定义接口的函数名并限制聚合级别的访问让检索变的显示,调整查询就会变的多,能够让以领域专家理解的术语而不是SQL表达查询的目的,而不是盲目的允许所有的CURD方法,无论他们多么合适。
存储库的意义不在于让代码测试更容易或者替换持久化技术更容易,而是保持领域模型和持久化分离,没有存储库,持久化技术很容易渗透进领域模型中,削弱完整性和可用性,不能盲目的使用仓储库模式
2.2 领域模型和数据模型区别
数据模型:也就持久化模型,如果用关系数据库,其实是数据表的数据面向对象的表现形式,它表达是领域模型一个特定时间状态的数据存储结构,它是有表和列组成。
领域模型:实体和值对象组成,富含行为和语言,领域模型和数据模型分离可以方便的演化领域模型,不必过多考虑实现持久化实现。
image.png领域模型最终是要持久化的,在持久化遇到困难时要务实的做出妥协,但是设计的方向是优先考虑领域模型,而不是先考虑数据表(数据模型)
如果领域模型和数据模型类似,在采用关系数据库的时候很多ORM框架提供的功能很容易实现将数据模型映射到领域模型。
不应该为了通用性提供大而全的查询接口,接口要目的和语义明确才能起到限制作用
3.聚合实现持久化的方式
- 非关系数据库,聚合成JSON
-
关系数据库,ORM框架
image.png
-
领域模型所有属性提供get方法
不推荐,会导致领域模型状态保持无效,因为任何人都可以绕过约定的方法,直接调用get方法。 -
备忘录模型
领域模型提供获取特定方法,比如定义一个toJson方法,内部实现持久化的数据内容。
image.png -
领域事件模式
使用事件流,类似备忘录,但是不会存储特定时间的快照,存储库存储的是这些事件,监听领域事件将其映射到数据模型,需要一个工厂根据这些事件重构和演化这些事件以便重新构建聚合。
image.png4.存储库的职责
- 充当数据防腐层
- 提供id生成的功能
3.集合汇总,通过仓储库可以知道对外提供了多少个和聚合相关的接口。
4.并发性控制
还是以前说的乐观和悲观控制,聚合保存的聚合的时候,仓储库可以抛出特定的异常。
5.审计追踪
如果数据模型需要领域模型不相关的数据,可以用仓储库满足实现,比如update_at对于领域模型没有意义,仓储库可以实现,审计记录和日志追踪也可以用仓储库实现(记录数据操作记录或者变更记录)
.
5. 仓储库的禁止事项
1.不要提供过于开发的接口,要显示定义需要的方法。
image.png2.避免延迟加载
某些ORM提供了延迟加载功能,但是聚合是围绕不变条件构建的,所以要么全部加载要么都不加载,如果延迟加载的数据聚合部分数据,聚合边界就是错误的。
3.不要报告需要使用存储库
有些UI展示的信息和聚合非常不匹配,有的时候仅仅需要聚合的个别属性,而且跨越多个聚合,这个时候如果用存储库的话需要把所有聚合查出来然后再提取属性后,返回给前端,这种情况下用存储库是不必要的,可以直接用SQL框架查询读取存储。
处于报告(这里理解为仅仅读取展示)查询数据模型的时候,不需要抽象持久化框架(不需要存储库)
网友评论