当前位置: 首页 > news >正文

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(){
http://www.rkmt.cn/news/1458476.html

相关文章:

  • 从Demo到量产:Davinci工程添加自定义模块与变体文件的完整指南(以BRS模块为例)
  • 企业级AI角色扮演对话系统
  • 钢材表面缺陷检测实战工程:含NEU-DET数据集与YOLOv5/v8多版本训练配置
  • 零基础如何学会Appium自动化测试
  • 用MATLAB复现DWA算法:从二维到三维,手把手教你搞定无人机避障路径规划
  • 保姆级教程:华为交换机DHCP地址池配置与查询全流程(含防IP冲突指南)
  • 别再死记硬背CSRF原理了!用Pikachu靶场实战Get/Post/Token三种攻击,手把手教你复现
  • Arduino读取FlySky接收机PWM信号:从硬件连接到代码实现
  • 别再到处找地图JSON了!手把手教你用ECharts-GL + 阿里云DataV下载并配置离线3D地图
  • WeChatExporter终极指南:3步永久保存你的微信聊天记录,告别数据丢失
  • Halcon region转图像踩坑实录:region_to_bin、region_to_label、region_to_mean到底怎么选?
  • 快手无水印下载终极指南:KS-Downloader完整使用教程
  • Python 爬虫分布式实战:Redis + 多进程爬虫实现分布式数据采集与任务分片
  • 从‘nvidia-smi’到跑通第一个CUDA核函数:给Python开发者的CentOS服务器GPU编程初体验
  • 自制Digispark开发板:从ATtiny85芯片到USB可编程硬件的完整实践
  • 别再只盯着GPS了!手把手教你用Arduino解析北斗/GPS模块的NMEA 0183数据(附完整代码)
  • 3步搞定Mac鼠标指针个性化:Mousecape完整使用指南
  • 告别玄学:给你的STM32 Bootloader跳转函数加个‘安全检查清单’(含代码详解)
  • 智能客服响应延迟骤降92%,企业AI工具整合避坑清单,仅剩最后87份内部文档模板
  • C++编写的BMP条形码定位与数字解码工具集(含预处理、频域增强与形态学操作)
  • Fan Control实战:3个技巧解决Windows风扇控制难题
  • 避坑指南:在RH850上发送超过16位SPI数据包,EDL位和CS信号时序你配对了吗?
  • Arxiv上传前必读:从专利风险到源码政策,这些“隐形坑”可能毁了你的工作
  • OV摄像头SCCB协议实战:用Arduino UNO配置OV7670图像传感器(附完整代码)
  • 深入PSINS工具箱:从`glvf`的全局变量设计,看严恭敏老师的编程哲学与工程考量
  • 2026年6月成都全屋定制品牌推荐:十大排名专业评测价格注意事项 - 品牌推荐
  • STM32期末救命指南(一):嵌入式系统概述与开发流程
  • WinCC自动化备份不求人:用VBS脚本让OnlineTableControl定时导出CSV(附完整代码)
  • 【限时开放】2024智能客服AI集成成熟度评估模型(含12维度打分表+行业基准值)
  • 告别CH340!用STM32F103C8T6的USB虚拟串口,实现免驱动调试(附完整工程)