资源库里还没找到好人呢。转载本文请联系codeeasy公众号。以UOW方式实现Repository很多人在看完了?这篇文章后会问这个Repository是如何实现的。这个Repository的实现背后是一个称为“工作单元(UOW)”的模型:维护受业务事务影响的对象列表,并协调更改的写出和并发问题的解决。UnitofWork——MartinFowlerUOW模式是跟踪一个业务用例运行中对象的所有变化(增删改查),并将所有变化的对象保存在一个列表中。在业务用例的最后,通过一个事务,一次性提交所有的变更,保证数据的完整性和有效性。总而言之,UOW协调了这些对象的持久化和并发问题。很多实现UOW模式的框架都是采用保存快照的方式来跟踪对象状态的变化。如上图所示,通过比较对象开始时的状态和对象编辑后的状态,决定如何更新数据库。这样做的好处是可以按需增量更新。重点是最后一次保存一次更改。没有必要跟踪对象状态的变化。让我们看看IDDD_Sample是如何实现的。IDDD_Sample使用LevelDB来存储数据。以agilepm.port.adapter.persistence.LevelDBSprintRepository为例:write(primaryKey,aSprint);}LevelDBUnitOfWork的write方法实现如下:.getBytes());}它序列化整个聚合并存储它。由于没有跟踪对象的变化,无法实现增量更新,所以只能将最新的聚合序列化,完全覆盖之前的聚合存储。但是,这种方法对于我们的大部分场景来说用处不大。一个原因是我们用的最多的是关系型数据库,另一个原因是这个非增量更新的开销还是比较大的。合适的才是最好的。我有一个朋友使用MongoDB作为存储。使用这种模式,效果非常好。他做的应用,数据量小,并发度低。这种方式大大节省了开发。和维护成本。那我们看看在使用关系型数据库的时候有哪些框架。使用JPA实现RepositoryJPA(JavaPersistenceAPI)是一种Java持久化规范。最流行的实现是Hibernate,它可以大大简化数据库的操作。但是JPA在国内并不流行:国内外Hibernate和MyBatis的使用对比,以UOW方式实现Repository,使用JPA仍然是最好的选择。您几乎不需要自己做任何工作,只要您映射聚合中的对象和表即可。JPA/Hibernate还提供了一个易于使用的乐观锁功能。维护聚合根上的乐观锁非常简单。JPA/Hibernate在国内不流行的一个重要原因不是它不好用,而是它太好用了——它隐藏了很多实现细节有时显得死板,提供了太多的高级功能那些不好用又容易踩坑的。因此,在使用JPA时,请遵循以下建议:仅使用其功能的一个子集,例如禁用Many-to-Many映射、禁用延迟加载等;记得上一篇CQRS的文章,很多查询场景并不需要聚合中的所有数据,所以对于一些Query实现,可以使用原始的SQL代替JPA来查询,比如JDBC或者MyBatis;了解CQRS,这些技术可以很好的结合使用;确保聚合中的数据不需要分库分表。即使使用Proxy方式的分库分表中间件,使用JPA还是有问题的。后面会写一篇文章,说说为什么会出现问题,以及如何解决这个问题;有很多人嫌弃JPA不够精简,以至于Spring官方推出了Spring-Data-JDBC这个专门为DDD聚合存储设计的ORM框架。它比JPA更轻、更简单。但是,为了轻便和简单,它不跟踪对象状态修改。因此,在保存聚合时,数据库不能像JPA那样按需更新,而是像IDDD_Sample一样,会粗略地覆盖更新,甚至在重新插入之前删除聚合下的所有子实体(不管子实体数据有没有改变了)。可能导致不可控的性能问题。如果我们要用关系型数据库,又不能用JPA,有没有别的办法?前段时间尝试自己写代码实现资源库:手写Repository的实现,在聚合之前从库中保存一个聚合到数据库中,比较两个聚合中的对象(diff),找出差异,生成操作数据库的SQL语句,增量更新数据库。这样做的问题是需要编写大量的Repository代码,而且容易出错,不易维护。尝试做一些抽象来简化代码,最后发现抽象的越多越像JPA。另一个问题是它需要在保存之前加载一次。如果想避免这个问题,可以看《DDD之聚合持久化应该怎么做?| https://zhuanlan.zhihu.com/p/334344752》,但是这个方法还是没有避免代码写多的问题。自己编码实现的好处是可控,比如分库分表的问题很容易处理。但实现起来过于复杂,编写和维护成本高,容易出问题,极大地打击了人们使用富领域模型的积极性。总之,没有人能实现Repository。再看看端口适配器型号。我们提到了DDD提倡的六边形模型,即端口适配器模型。存储库就是一个例子。比如接口agilepm.domain.model.product.sprint.SprintRepository就是一个接口,也就是所谓的端口,和领域对象在同一个包里;而agilepm.port.adapter.persistence.LevelDBSprintRepository的实现是在另一个叫做adapter包下的,这背后就是依赖倒置的原理,让领域层和应用层不依赖具体的技术实现。Repository的实现只是一种适配器。下一篇我们会讲到如何在另外一个上下文中访问服务,本质上是一种端口/适配器,但是有更多不同的细节需要注意。
