实现资源库还没找到称手的家伙
时间:2025-11-05 13:18:47 出处:IT科技阅读(143)

本文转载自微信公众号「codeasy」,实现手作者阎华。资源转载本文请联系codeasy公众号。库还
用UOW模式实现Repository
看了《无法实施富领域模型的没找罪魁祸首找到了》这一篇文章后,很多人都会问这种Repository是家伙这么实现的。这种Repository的实现手实现背后用了一个叫做 “Unit of Work (UOW)”的模式:
Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.Unit of Work --Martin Fowler
UOW模式是在业务用例的操作中跟踪对象的所有更改(增加、删除和更新),资源并将所有更改的库还对象保存在一个列表中。在业务用例的没找终点,通过事务,家伙一次性提交所有更改,实现手以确保数据的资源完整性和有效性。总而言之,库还UOW协调这些对象的没找持久化及并发问题。

很多实现了UOW模式的家伙框架都采用了保存快照的方式来跟踪对象状态的变化,如上图所示,通过对比开始时对象的状态和编辑后的对象的云南idc服务商状态,从而决定如何更新数据库。这样做的好处是可以增量按需更新。
重点是最后一次性保存变更,跟踪对象状态的变更不是必须的,我们看看IDDD_Sample是怎么实现的。
IDDD_Sample使用了LevelDB来存储数据,以 agilepm.port.adapter.persistence.LevelDBSprintRepository 为例:
void save(Sprint aSprint, LevelDBUnitOfWork aUoW) { LevelDBKey primaryKey = new LevelDBKey(PRIMARY, aSprint.tenantId().id(), aSprint.sprintId().id()); aUoW.write(primaryKey, aSprint); }其中 LevelDBUnitOfWork 的write方法是这么实现的:
public void write(LevelDBKey aKey, Object aValue) { String serializedValue = this.serializer.serialize(aValue); this.batch.put(aKey.keyAsBytes(), serializedValue.getBytes()); }它把整个聚合序列化后存储了。由于没有跟踪对象变更,所以也无法实现增量的更新,只能粗暴地用最新的聚合序列化后完全覆盖之前的聚合存储了。 但这种方式对我们大部分场景参考性不大,一个原因是我们最常用的还是关系型数据库, 另一个原因是这种非增量的更新开销还是企商汇比较大的。
合适的就是最好的,我有个朋友曾用MongoDB作存储,使用了这样的模式,效果很好,他所做的那个应用数据量不大,并发不高,用这种方式大大节约了开发和维护的成本。
那我们看看当使用关系型数据库的时候有什么框架可以选择。
使用JPA实现Repository
JPA (Java Persistence API) 是一个Java 持久化规范,最流行的一个实现是Hibernate,它可以大大简化对数据库的操作,然而,JPA在国内不受待见:

国内外使用Hibernate和MyBatis的对比
然而要实现UOW模式的Repository,使用JPA依然是最佳选择,你几乎不用自己做任何的工作,只要把聚合中的服务器托管对象和表映射好就可以了。
JPA/Hibernate还提供了易用的乐观锁功能,在聚合根上维护一个乐观锁非常简单
JPA/Hibernate在国内不受待见的一个重要原因,不是它不好用,而是太好用了——隐藏了很多实现细节有时候显得不太灵活,提供了太多的高级功能用不好容易踩坑。
所以,使用JPA,请遵循以下几点建议:
只用它的功能的一个子集,比如要禁用Many-to-Many映射、禁用延迟加载的功能等; 还记得之前关于CQRS这篇文章吧,很多查询场景不需要聚合内的全部数据,所以,有些Query的实现,你完全可以不使用JPA,而是用原始的SQL去查,比如用JDBC或MyBatis;理解了CQRS,这些技术是可以很好地结合在一起使用的; 确定你的聚合中的数据不需要分库分表。即使你使用了Proxy模式的分库分表中间件,使用JPA还是有问题的,这个以后专门写一篇文章说说为什么有问题,以及如何解决这个问题;嫌弃JPA不够精简的人很多,以至于Spring的官方推出了 Spring-Data-JDBC,一个专门为DDD的聚合存储设计的ORM框架,它比JPA轻量很多,简单很多,然而,为了轻量简单,它也没有对对象状态修改进行跟踪,所以在保存聚合的时候无法像JPA一样按需更新数据库,而是如IDDD_Sample一样,粗暴地覆盖更新,甚至会先删除聚合下所有子实体后再重新插入(无论子实体数据有没有变更),这可能会带来不可控的性能问题。
如果我们又想用关系数据库,又不能使用JPA,那还有别的办法吗?
自己写代码实现资源库
前段时间我尝试过一种方法:自己手写Repository的实现,在聚合保存前,先从库里load出来一个聚合,把这两个聚合里的对象进行比较(diff),找出差异,生成操作数据库的SQL语句,去增量更新数据库。
这样做的问题是需要写很多的Repository代码,而且很容易出错也不容易维护,我试图做一些抽象来简化代码,最后发现抽象越多越像JPA了。
这样做还有一个问题是需要在保存前额外加载一次,如果想避免这个问题,可以看看《DDD之聚合持久化应该怎么做?| https://zhuanlan.zhihu.com/p/334344752》,但这种方法还是没有避免需要写很多代码的问题。
自己编码实现的好处是可控,比如容易处理分库分表的问题。但实现起来太复杂,编写和维护成本高,也容易出问题,这大大打击了使用富领域模型的热情。
总之,实现Repository,还没有一件称手的家伙。
再次审视端口适配器模型
前面我们提到过DDD提倡的六边形模型,即端口适配器模型,Repository就是一个例子。比如接口 agilepm.domain.model.product.sprint.SprintRepository 这是一个接口,即所谓的端口,它和领域对象在同一个包里;而 agilepm.port.adapter.persistence.LevelDBSprintRepository这个实现是在另外的叫 adapter 的包下,这背后体现的是依赖倒置原则,这样可以让领域层和应用层不依赖于具体的技术实现。
Repository的实现只是一种adapter,下一篇我们讲一讲如何访问另一个上下文中的服务,那本质上也是一种port/adapter,但有更多的不一样的细节需要注意。
猜你喜欢
- 电脑密码错误提示问题解决方法(为什么电脑提示密码错误,以及应对之策)
- 探索LGUF6800电视的功能与特点(一款高性能的智能电视体验)
- 小米5和5s拍照效果对比(小米5和5s相机表现如何?一图胜千言,你就知道!)
- 七彩虹GTX730显卡的性能与特点剖析(了解七彩虹GTX730显卡的卓越性能及特色功能)
- 以352x50为主题的文章——探索数字屏幕的无限可能(在352x50像素屏幕上展现创意与创新的可能性)
- 尼康200.500镜头评测(探究尼康200.500镜头的特点和优势)
- 课桌电脑置物架的安装教程(简单易懂的步骤让你轻松安装电脑置物架)
- 如何从iCloud恢复出厂设置(一步步教你恢复设备到出厂设置)
- 假如你使用大显示器或者多个显示器,Ubuntu 13.10的全局菜单将不便于操作。假如你想禁用全局菜单,你可以很容易实现。使用快捷键(Ctrl+Alt+T)打开终端并输入下面的命令:复制代码代码如下:sudo apt-get remove indicator-appmenu系统将会移除indicator-appmenu并提示释放多少空间,需要输入y确定并继续,关闭终端后会发现全局菜单已经消失,但firefox的全局菜单还需要单独修改才能禁用全局菜单。打开firefox并在地址栏中输入about:config,此时将出现警告提示,点击I’ll be careful, I promise!按钮,搜索unity会看到ui.use_unity_menubar这项,其默认Value值为true,将其改为flase即可禁用全局菜单。