07.工厂与仓库

285 阅读5分钟

仓库与工厂

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查询一个订单,工厂要做的事情就是这些

1746264528434.png

职责辨析

有的人使用Repository创建领域对象,这并不好

  1. Repository是面向聚合根的,更多的是作为仓储来保存或者加载聚合根。如果也由Repository创建领域对象,那么Repository就承担了过多的职责
  2. 可能一些普通的实体和值对象比较复杂,也需要封装创建的过程,如果由Repository进行创建,就与只有聚合根才有Repository产生了矛盾。因此,建议使用Factory来创建领域对象

注意

要注意Repository加载聚合根和Factory创建领域对象两者过程的区别:

  1. Repository是已经存在了聚合根,只是将其从持久化到数据库加载到内存中,并将其重新组装为聚合根
  2. 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先到缓存中进行查找:

  1. 查找到了,则直接返回,不需要查询数据库
  2. 没有找到,则通知工厂,工厂调用DAO去数据库中查询,然后装配成领域对象返回给仓库

仓库在收到这个领域对象以后,在返回给客户程序的同时,将该对象放到缓存中

案例(二)

通过某些条件查询订单的过程又是怎么做呢?查询订单的操作同样是交给订单仓库去完成

  • 订单仓库会先通过订单DAO去查询订单表,但这里是只查询订单表,不做Join操作
  • 订单DAO查询了订单表以后,会进行一个分页,将某一页的数据返回给订单仓库
  • 这时,订单仓库就会将查询结果交给订单工厂,让它去补填其对应的用户与订单明细,完成相应的装配,最终将装配好的订单对象集合返回给仓库