这是来自Node.js路线有关混合两种ORM模式Active Record(活动记录模式)和Data Mapper(数据映射模式)的思考。其实质是引发了失血模型与充血模型的区别,更深层次是引出了掌握 DDD 的必要性。
首先这两种持久模式的特点如下:
Active Record(活动记录模式)是领域模型对象字段和数据表字段之间存储1:1的关系,也就是一个模型字段对应一个数据表字段;然后模型对象提供一个save()方法用来将模型对象持久化到存储层中去;模型是知道数据层的,也就是和数据持久层耦合的。
Data Mapper(数据映射模式)则是将领域模型对象和数据表完全松耦合, 领域对象只负责处理业务逻辑,根本不知道数据层,也就是和数据层是解耦的;使用一个实体管理器来将模型对象持久化到存储层中;模型对象的字段可以是任何名称,只要符合业务模型即可,可以映射到数据层数据表的不同字段。
很显然,Active Record比较简单,但是不够灵活,而Data Mapper则是很灵活,但是多了一个实体管理器,增加了复杂性。
其实Data Mapper类似Repository仓储模式,只需要在模型生命开始时从数据表抓取记录创建出一个模型对象即可,而一旦模型对象被创建出来以后,我们就可以直接和模型对象打交道,无需再经过Data Mapper(Repository),这样,我们能获得Data Mapper的灵活性优点,模型对象的字段不必和数据表的字段一一对应了。同时又具有Active Record的简单性。具体步骤如下:
1. 我们创建一个领域模型和一个仓储
2.当我们需要一个新的模型对象或者需要从数据表中抓取一个已经存在的模型对象时,我们调用UserRepository.{method}(); 而不是像Active Record那样直接使用模型对象的方法User.{method}();
3.模型对象的字段内容发生变化,需要保存时,我们从用user.save()切换到使用UserRepository.save(user)
总体来说,这种思路也就是将Active Record中的创建与save方法移植到仓储Repository,使用函数操作数据的方式,这样能利用仓储Repository的函数性和灵活性。
同时,将一个统一的Entity Manager实体管理器改变为每个模型一个仓储Repository,也只有这样才真正实现SOLID原则,而原来的Data Mapper是一个实体管理器对象负责所有对象的持久化,并不符合SOLID原则。
原文:
A Hybrid ORM Idea - Lee Mason
banq观点:如果了解 DDD ,其中定义仓储是用数据库这个仓库储存领域模型对象,故称为仓储Repository,那么显然模型对象的创建和保存这些与数据库“仓库"有关的操作都应该交由仓储Repository实现,因为创建与保存实则是模型对象与数据库这个仓库的同步行为,不应当作为模型对象的自身行为。
另外一篇文章: 贫血与充血领域对象的平衡 中提出指导行为方法如何放置到正确的地方:
将行为放入领域对象不应该与这样情况矛盾:通过分层将领域逻辑从持久性和表现性等职责中分离出来,(也就是从数据库和界面显示分离出来),在领域对象中的逻辑应该是领域逻辑,比如校验 计算和业务规则。
比如订单这个对象中有一个订单总价计算的方法getTotalPrice,根据上述原则,这个方法是有关订单总价这个业务逻辑计算的,因此应该是放在Order这个对象中,而不是OrderService中。
那么领域对象与持久层数据库打交道的行为怎么办?比如创建与保存等方法,这些方法是领域对象的外部状态,因为对象是存在于JVM内存中,而持久层数据库中对应这个对象的数据表是属于JVM的外部,因此是内存领域对象的外部状态,协调领域对象内部状态与外部状态之间的同步不应该是领域对象的固有行为,可以通过Repository/Dao实现,在OrderService服务中调用执行。
领域对象应该只负责内部可变状态的变化,提供导致所在JVM内存状态变化的所有行为。
而Service服务/Manager管理器应当是代表orchestrater组织者, 不是领域专家。也就是说是总经理,不是总工。