DDD-013:仓储(Repository)
DDD-013:仓储(Repository)
13.1 仓储的职责与概念
13.1.1 什么是仓储?
【原理】
仓储(Repository)是 DDD 中用于管理聚合持久化的机制,它提供了一种类似集合(Collection)的接口来访问和存储领域对象。仓储封装了数据访问的细节,让领域层专注于业务逻辑。
Eric Evans 对仓储的定义:
“仓储是一种机制,用于封装存储、检索和搜索行为,它模拟一个内存中的对象集合。”
仓储的核心特征:
- 集合语义:像操作内存集合一样操作持久化数据
- 聚合视角:以聚合为单位进行存取
- 领域语言:方法命名使用领域语言
- 隐藏实现:隐藏数据库访问的复杂性
13.1.2 仓储的职责
【职责清单】
| 职责 | 说明 |
|---|---|
| 加载聚合 | 根据ID加载完整的聚合 |
| 保存聚合 | 持久化聚合的所有变更 |
| 删除聚合 | 从存储中移除聚合 |
| 查询聚合 | 按领域条件查询聚合 |
13.1.3 与集合的类比
【代码示例】
// 内存中的集合操作Set<Order>orders=newHashSet<>();// 添加orders.add(order);// 查找Orderfound=orders.stream().filter(o->o.getId().equals(orderId)).findFirst().orElse(null);// 移除orders.remove(order);// 仓储操作(类似集合)OrderRepositoryrepository=...;// 添加/保存repository.save(order);// 查找Orderfound=repository.findById(orderId).orElse(null);// 移除repository.delete(order);13.2 仓储 vs DAO
13.2.1 概念区别
【对比分析】
| 维度 | DAO(Data Access Object) | Repository(仓储) |
|---|---|---|
| 关注点 | 数据表(Table) | 聚合(Aggregate) |
| 抽象层级 | 数据访问层 | 领域层 |
| 返回类型 | PO(持久化对象) | 领域对象 |
| 操作单位 | 数据行 | 聚合(可能包含多个表) |
| 查询方式 | SQL/表字段 | 领域语言/业务概念 |
| 设计理念 | 面向数据库 | 面向领域 |
13.2.2 代码对比
【历史架构问题】
// ❌ 传统DAO:面向数据表publicinterfaceOrderDao{// 操作数据表OrderPOselectById(Longid);List<OrderPO>selectByCustomerId(LongcustomerId);intinsert(OrderPOpo);intupdate(OrderPOpo);intdelete(Longid);// 问题1: 返回的是PO,不是领域对象// 问题2: 只关注单表,不关注聚合// 问题3: 命名是数据库操作,不是领域语言}// DAO实现直接操作数据库@RepositorypublicclassOrderDaoImplimplementsOrderDao{@AutowiredprivateJdbcTemplatejdbcTemplate;@OverridepublicOrderPOselectById(Longid){Stringsql="SELECT * FROM orders WHERE id = ?";returnjdbcTemplate.queryForObject(sql,newOrderPORowMapper(),id);}// 需要手动转换 PO -> 领域对象}【DDD 如何解决】
// ✅ DDD仓储:面向聚合publicinterfaceOrderRepository{// 操作聚合Optional<Order>findById(OrderIdid);List<Order>findByCustomerId(CustomerIdcustomerId);List<Order>findByStatus(OrderStatusstatus);voidsave(Orderorder);voiddelete(Orderorder);// 优点1: 返回领域对象// 优点2: 以聚合为单位// 优点3: 使用领域语言命名}// 仓储实现负责聚合的完整加载和保存@RepositorypublicclassJpaOrderRepositoryimplementsOrderRepository{@PersistenceContextprivateEntityManagerentityManager;@OverridepublicOptional<Order>findById(OrderIdid){// 加载完整的聚合(包括内部实体)Orderorder=entityManager.find(Order.class,id);returnOptional.ofNullable(order);}@Overridepublicvoidsave(Orderorder){// 保存完整的聚合(级联保存内部实体)if(order.getId()==null||entityManager.find(Order.class,order.getId())==null){entityManager.persist(order);}else{entityManager.merge(order);}}}13.2.3 使用场景选择
| 场景 | 推荐使用 |
|---|---|
| DDD架构 | Repository |
| 领域模型复杂 | Repository |
| 聚合边界明确 | Repository |
| 简单CRUD应用 | DAO |
| 表驱动设计 | DAO |
| 遗留系统维护 | DAO |
13.3 仓储的接口设计
13.3.1 基本操作接口
【代码示例】
/** * 泛型仓储接口 * * @param <T> 聚合类型 * @param <ID> 标识符类型 */publicinterfaceRepository<T,ID>{/** * 根据ID查找 */Optional<T>findById(IDid);/** * 查找所有 */List<T>findAll();/** * 保存(新增或更新) */voidsave(Tentity);/** * 删除 */voiddelete(Tentity);/** * 根据ID删除 */voiddeleteById(IDid);/** * 是否存在 */booleanexistsById(IDid);/** * 统计数量 */longcount();}13.3.2 领域特定接口
【代码示例】
/** * 订单仓储接口 * 继承泛型仓储,添加领域特定方法 */publicinterfaceOrderRepositoryextendsRepository<Order,OrderId>{// ========== 领域特定查询 ==========/** * 根据客户ID查找订单 */List<Order>findByCustomerId(CustomerIdcustomerId);/** * 根据订单状态查找 */List<Order>findByStatus(OrderStatusstatus);/** * 根据客户和状态查找 */List<Order>findByCustomerIdAndStatus(CustomerIdcustomerId,OrderStatusstatus);/** * 查找指定时间范围内的订单 */List<Order>findByCreatedAtBetween(LocalDateTimestart,LocalDateTimeend);/** * 查找待处理的订单 */List<Order>findPendingOrders();/** * 查找超时未支付的订单 */List<Order>findUnpaidTimeoutOrders(Durationtimeout);// ========== 聚合操作 ==========/** * 保存并发布领域事件 */defaultvoidsaveAndPublishEvents(Orderorder,ApplicationEventPublisherpublisher){save(order);order.getDomainEvents().forEach(publisher::publishEvent);order.clearDomainEvents();}}13.3.3 分页与排序
【代码示例】
/** * 分页参数 */publicclassPageRequest{privatefinalintpageNumber;privatefinalintpageSize;privatefinalSortsort;publicstaticPageRequestof(intpageNumber,intpageSize){returnnewPageRequest(pageNumber,pageSize,Sort.unsorted());}publicstaticPageRequestof(intpageNumber,intpageSize,Sortsort){returnnewPageRequest(pageNumber,pageSize,sort);}publiclonggetOffset(){return(long)pageNumber*pageSize;}// Getters...}/** * 分页结果 */publicclassPage<T>{privatefinalList<T>content;privatefinallongtotalElements;privatefinalinttotalPages;privatefinalintpageNumber;privatefinalintpageSize;publicPage(List<T>content,longtotalElements,intpageNumber,intpageSize){this.content=content;this.totalElements=totalElements;this.pageNumber=pageNumber;this.pageSize=pageSize;this.totalPages=(int)Math.ceil((double)totalElements/pageSize);}publicbooleanhasNext(){returnpageNumber<totalPages-1;}publicbooleanhasPrevious(){returnpageNumber>0;}// Getters...}/** * 支持分页的仓储接口 */publicinterfaceOrderRepositoryextendsRepository<Order,OrderId>{/** * 分页查询客户订单 */Page<Order>findByCustomerId(CustomerIdcustomerId,PageRequestpageRequest);/** * 分页查询所有订单 */Page<Order>findAll(PageRequestpageRequest);}13.4 仓储的实现策略
13.4.1 内存实现(测试用)
【代码示例】
/** * 内存仓储实现(用于单元测试) */publicclassInMemoryOrderRepositoryimplementsOrderRepository{privatefinalMap<OrderId,Order>store=newConcurrentHashMap<>();@OverridepublicOptional<Order>findById(OrderIdid){returnOptional.ofNullable(store.get(id));}@OverridepublicList<Order>findAll(){returnnewArrayList<>(store.values());}@Overridepublicvoidsave(Orderorder){store.put(order.getId(),order);}@Overridepublicvoiddelete(Orderorder){store.remove(order.getId());}@OverridepublicList<Order>findByCustomerId(CustomerIdcustomerId){returnstore.values().stream().filter(order->order.getCustomerId().equals(customerId)).collect(Collectors.toList());}@OverridepublicList<Order>findByStatus(OrderStatusstatus){returnstore.values().stream().filter(order->order.getStatus()==status).collect(Collectors.toList());}// 用于测试的辅助方法publicvoidclear(){store.clear();}publicintsize(){