1、引言
仓储是DDD中产生的概念,也就是说,如果应用程序不是基于领域驱动设计的,那在设计中使用仓储是不是会有不合适的地方呢?
Eric Evans 在《领域驱动设计-软件核心复杂性应对之道》定义中明确的那样:协调领域和数据映射层,两个关键字领域和数据映射层,这里面的领域是指领域模型(实体和值对象),这是桥的一头,另一头就是数据映射层,也就是我们常说的 ORM 工具。
除了这两个关键词,还有一个动词就是协调,仓储协调的是什么?怎么协调的?这个概念需要明确下,桥的一头-领域模型(主要是实体对象),这个就不多说了,桥的另一头-ORM(对象关系映射),其实仓储协调的是 ORM 中的“O”,也就是对象的概念,它是在数据映射层之上的,是一种概念,而不是一种实现。
2、DDD中的仓储
仓储代表一个聚合的集合,仓储用来存储和删除聚合,但同时提供针对聚合的显式查询以及汇总。
2.1、仓储与数据访问层的区别
- 仓储限定了只能通过聚合根来持久化和检索领域对象,以确保所有改动和不变性由聚合处理。
- 仓储通过隐藏聚合持久化和检索的底层技术实现领域层的的持久化无关性(即领域层不需要知道如何持久化领域对象)。
- 仓储在数据模型和领域模型定义了一个边界。
2.2、仓储举例
下面我们首先来看一个简单仓储的定义:
public interface WeChatUserRepository {
WeChatUser save(WeChatUser data);
void delete(WeChatUser data);
}
一般情况下,仓储由应用服务层调用。仓储定义应用服务执行业务用例时需要的所有的数据访问方法。而仓储的实现通常位于基础架构层,由持久化框架来支撑。以下的仓储实现是借助于ORM框架spring data jpa,它扮演一个管理员的角色,负责领域模型和数据模型的映射。
@Service
public class WeChatUserServiceImpl implements WeChatUserService{
@Autowired
private WeChatUserRepository weChatUserRepository;
@Override
public WeChatUser save(WeChatUser data) {
return weChatUserRepository.save(data);
}
@Override
public void delete(WeChatUserVo data) {
WeChatUser target = new WeChatUser();
BeanUtils.copyProperties(data, target);
weChatUserRepository.delete(target);
}
}
从上面我们可以看出,将领域模型的持久化转移到基础设施层,隐藏了领域模型的技术复杂性,从而使领域对象能够专注于业务概念和逻辑,保持你的领域模型和技术持久化框架的独立性,这样领域模型可以隔离来自底层持久化技术的影响。
仓储是原则上是领域模型与持久化存储之间明确的契约,对于调用仓储中方法的服务层来说也可以称之为管理员,当你需要向管理员报告拿什么东西的时候肯定要明确你要拿什么,所以仓储定义的接口方法不仅仅是CURD方法,仓储接口的定义应该根据应用程序的用例需求来创建,而不是从类似CURD的数据访问角度来构建。
3. 小结
- 仓储作为领域模型和数据模型的中介,它负责映射领域模型到持久化存储。
- 仓储实现了透明持久化,即领域层不需要关注领域对象如何持久化。
- 仓储是一个契约,而不是数据访问层。它明确表明聚合所必需的数据操作。
- ORM框架不是仓储。仓储是一种架构模式。ORM用来以面向对象的方式来表示数据模型。仓储使用ORM来协调领域模型和数据模型。
- 仓储适用于具有丰富领域模型的限界上下文。对于没有复杂业务逻辑的简单限界上下文,直接使用持久化框架即可。
- 仓储用于管理单个聚合,它不应该控制事务。