当前位置: 首页 > 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.jsqmd.com/news/946974/

相关文章:

  • Python 爬虫进阶技巧:批量解析 html 实体转义字符还原原始文本
  • Xcode 15开发者的终端效率手册:除了CMD+R运行,你的快捷键还缺这一块
  • 从Demo到量产:Davinci工程添加自定义模块与变体文件的完整指南(以BRS模块为例)
  • 告别WebView黑盒:用Chrome DevTools调试Android混合开发页面(附Androidx-WebKit实战)
  • 钢材表面缺陷检测实战工程:含NEU-DET数据集与YOLOv5/v8多版本训练配置
  • 2026深度测评10款降AI率软件红黑榜!优缺点全曝光,达标率直接对标行业天花板
  • 绝区零自动化脚本终极指南:3分钟快速上手完整教程
  • 用FPGA控制步进电机是种什么体验?从状态机到分频器,详解Verilog驱动A4988全流程
  • 企业级AI角色扮演对话系统
  • MATLAB图像质量评价避坑指南:为什么你的PSNR/SSIM结果和OpenCV差那么多?
  • 你的旧笔记本别扔!巧用闲置MiniPCIe接口,低成本变身4G物联网网关或监控终端
  • Apex Legends智能压枪助手终极指南:10分钟掌握精准射击
  • 零基础如何学会Appium自动化测试
  • 用MATLAB复现DWA算法:从二维到三维,手把手教你搞定无人机避障路径规划
  • 1、VTK+QT + cmake编程 三维圆柱体
  • 保姆级教程:华为交换机DHCP地址池配置与查询全流程(含防IP冲突指南)
  • 如何2分钟搞定iPhone在Windows上的网络共享:终极驱动安装方案
  • Spring AI Alibaba-ChatClient
  • MATLAB环境下可直接运行的KNN分类代码包:含主程序、核心函数与调用说明
  • 2026学术写作新范式:Gemini 3.1 Pro、Claude 3.5与GPT-4o协同润色实战指南
  • Appium Inspector 保姆级配置指南:从Desired Capabilities到元素定位,一次搞定
  • 别再死记硬背CSRF原理了!用Pikachu靶场实战Get/Post/Token三种攻击,手把手教你复现
  • 保姆级教程:用C#和ABB PC SDK 6.08搞定机器人上位机通信(从环境配置到一键连接)
  • 别再到处找地图JSON了!手把手教你用ECharts-GL + 阿里云DataV下载并配置离线3D地图
  • 保姆级教程:I3C总线初始化与动态地址分配实战(基于SDR模式)
  • FlagOS实现DeepSeekV4八芯片Day0适配技术解析
  • Arduino读取FlySky接收机PWM信号:从硬件连接到代码实现
  • 5个关键步骤:使用FanControl实现Windows系统风扇的智能精准控制
  • ESP-Prog驱动安装避坑指南:从FT2232HL识别到VSCode成功连接ESP32的全流程
  • WeChatExporter终极指南:3步永久保存你的微信聊天记录,告别数据丢失