仓库与工厂
1.工厂
介绍、
在DDD中,对于复杂的领域对象,不管是实体还是值对象,都可以使用工厂进行创建。通过将领域对象的创建过程交由统一的对象进行管理,可以避免过多地关注其创建过程
注意
工厂是对领域知识的封装,不让领域知识泄漏到外部客户端,创建一个复杂的对象时,应该满足相关的业务规则,如果传递的参数不满足业务规则,应该抛出异常,不应该创建一个错误的领域
大部分的场景我们不需要创建复杂的领域对象,简单的new一般就可以满足了,这种情况下我们可以使用简单的new来创建对象
好处
工厂模式隐藏创建对象的好处显而易见的,不会让领域中的逻辑泄漏到应用层中,同时减轻了应用层的负担,只需要调用工厂创建出来期望的对象即可,如果后需要修改业务规则,只需要在工厂中进行修改即可,整体的流程逻辑不需要变更,符合开闭原则
代码
对于实体来说,往往需要在创建时指定唯一主键,因此Factory可以交由Spring进行实例化管理,在创建的过程中注入某些依赖的基础设施
@Component
public class EntityFactory {
/**
* ID生成器
*/
@Resource
private IdGenerator idGenerator;
public Entity newInstance () {
Entity entity = new Entity();
entity.setEntityId(new EntityId(idGenerator.nextId()));
// 省略赋值其他属性的过程
return entity;
}
}
案例
系统要通过ID查询一个订单,工厂要做的事情就是这些
职责辨析
有的人使用Repository创建领域对象,这并不好
- Repository是面向聚合根的,更多的是作为仓储来保存或者加载聚合根。如果也由Repository创建领域对象,那么Repository就承担了过多的职责
- 可能一些普通的实体和值对象比较复杂,也需要封装创建的过程,如果由Repository进行创建,就与只有聚合根才有Repository产生了矛盾。因此,建议使用Factory来创建领域对象
注意
要注意Repository加载聚合根和Factory创建领域对象两者过程的区别:
- Repository是已经存在了聚合根,只是将其从持久化到数据库加载到内存中,并将其重新组装为聚合根
- Factory是从无到有地创建领域对象
2.仓库
前言
现在创建了一个订单,订单中包含了多条订单明细并将它们做成了一个聚合
介绍
Repository是DDD中非常重要的组件,可用来保存和加载聚合根
仓储介于领域模型和数据模型之间,主要用于聚合的持久化和检索
职责
- 将聚合根持久化到数据库:在Repository的save方法中,先将聚合根转换为数据对象,再将其持久化到数据库
- 将聚合根从数据库加载到内存:再Repository的load方法,先查询数据库获取数据对象,再将数据对象组装为聚合根
- 数据库事务控制:在保存聚合根时进行事务控制,确保数据一致性
实现
表级
表级的Repository是指Repository的方法在进行查询或者更新时会操作多个聚合根。例如:
/**
* 表级别的Repository
*/
public class Repository {
public List<Entity> queryForList() {
// 省略业务逻辑
}
public void batchSave(List<Entity> eneityList) {
// 省略业务逻辑
}
}
queryForList返回多个Entity实例,batchSave方法同时保存多个聚合根实例,因此这个Repository是表级别的
表级别的Repository下也会有针对单个聚合根的操作,例如,保存单个聚合根和加载单个聚合根
行级
行级的Repository是指Repository的方法再进行查询或者更新时只操作一个聚合根。例如:
/**
* 表级别的Repository
*/
public class Repository {
public Entity load(EntityId entityId) {
// 省略业务逻辑
}
@Transactional
public void save(Entity entity) {
// 省略业务逻辑
}
}
load和save方法都只操作一个聚合根,对应数据库中的一行数据,因此这种实现是行级的
总结
在DDD中,有的人会将Repository实现为表级的,我推荐实现为行级的。表级的Repository中涉及多个聚合根的查询方法,如queryForList,应通过CQRS将其分离出去。由于一个事务只更新一个聚合,因此,Repository不应该提供更新多个聚合根的方法,如batchSave这种批量更新的方法是不推荐提供的
案例(一)
当客户程序通过ID去获取某个领域对象时,仓库会通过这个ID先到缓存中进行查找:
- 查找到了,则直接返回,不需要查询数据库
- 没有找到,则通知工厂,工厂调用DAO去数据库中查询,然后装配成领域对象返回给仓库
仓库在收到这个领域对象以后,在返回给客户程序的同时,将该对象放到缓存中
案例(二)
通过某些条件查询订单的过程又是怎么做呢?查询订单的操作同样是交给订单仓库去完成
- 订单仓库会先通过订单DAO去查询订单表,但这里是只查询订单表,不做Join操作
- 订单DAO查询了订单表以后,会进行一个分页,将某一页的数据返回给订单仓库
- 这时,订单仓库就会将查询结果交给订单工厂,让它去补填其对应的用户与订单明细,完成相应的装配,最终将装配好的订单对象集合返回给仓库