DDD-032:案例:库存管理系统实战
DDD-032:案例:库存管理系统实战本章导读库存管理是电商系统的核心模块,涉及入库、出库、调拨、预警等复杂业务场景。本章通过库存管理系统案例,展示库存聚合的设计、并发扣减处理、领域事件在库存同步中的应用,以及分布式一致性解决方案。学习目标掌握库存聚合的设计与实现学会处理库存并发扣减问题理解库存预警和领域事件应用前置知识DDD 聚合设计基础并发控制机制事件驱动架构阅读时长约 55-65 分钟【案例背景】库存管理系统一、业务需求分析1.1 核心业务场景库存管理业务场景: ┌────────────────────────────────────────────────────────────────┐ │ 库存管理系统 │ ├────────────────────────────────────────────────────────────────┤ │ │ │ 1. 入库管理 │ │ ───────────── │ │ - 采购入库:供应商采购商品入库 │ │ - 退货入库:用户退货后商品重新入库 │ │ - 调拨入库:从其他仓库调拨入库 │ │ │ │ 2. 出库管理 │ │ ───────────── │ │ - 订单出库:用户下单扣减库存 │ │ - 损耗出库:商品损耗/过期扣减 │ │ - 调拨出库:调拨到其他仓库 │ │ │ │ 3. 库存调拨 │ │ ───────────── │ │ - 仓库间调拨 │ │ - 调拨审批流程 │ │ - 调拨状态追踪 │ │ │ │ 4. 库存预警 │ │ ───────────── │ │ - 库存不足预警 │ │ - 库存积压预警 │ │ - 预警通知 │ │ │ └────────────────────────────────────────────────────────────────┘1.2 核心业务规则规则编号规则描述R1库存不能为负数R2扣减库存前必须检查可用库存R3入库/出库操作必须记录流水R4预占库存需要在一定时间内释放R5库存低于阈值时触发预警二、领域建模2.1 聚合设计库存上下文聚合设计: ┌────────────────────────────────────────────────────────────────┐ │ Inventory Context │ ├────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Inventory 聚合 │ │ │ │ │ │ │ │ ┌────────────────┐ ┌────────────────┐ │ │ │ │ │ Inventory │───│ InventoryLog │ │ │ │ │ │ 聚合根 │ │ 实体 │ │ │ │ │ └────────────────┘ └────────────────┘ │ │ │ │ │ │ │ │ 职责: │ │ │ │ - 管理单个 SKU 在单个仓库的库存 │ │ │ │ - 入库/出库操作 │ │ │ │ - 库存流水记录 │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ InventoryAllocation 聚合 │ │ │ │ │ │ │ │ 库存预占:管理订单预占的库存 │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ InventoryTransfer 聚合 │ │ │ │ │ │ │ │ 库存调拨:管理仓库间的调拨 │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ └────────────────────────────────────────────────────────────────┘2.2 Inventory 聚合详细设计┌────────────────────────────────────────────────────────────────┐ │ Inventory │ │ (Aggregate Root) │ ├────────────────────────────────────────────────────────────────┤ │ - id: InventoryId │ │ - skuId: SkuId // SKU 标识 │ │ - warehouseId: WarehouseId // 仓库标识 │ │ - totalQuantity: int // 总库存 │ │ - allocatedQuantity: int // 已预占数量 │ │ - availableQuantity: int // 可用库存 │ │ - safetyStock: int // 安全库存阈值 │ │ - logs: ListInventoryLog // 库存流水 │ ├────────────────────────────────────────────────────────────────┤ │ + inbound(quantity, reason): void // 入库 │ │ + outbound(quantity, reason): void // 出库 │ │ + allocate(quantity, orderId): Allocation // 预占 │ │ + release(allocationId): void // 释放预占 │ │ + isLowStock(): boolean // 是否库存不足 │ │ + getAvailableQuantity(): int // 获取可用库存 │ └────────────────────────────────────────────────────────────────┘ │ │ 包含 ▼ ┌────────────────────────────────────────────────────────────────┐ │ InventoryLog │ │ (Entity) │ ├────────────────────────────────────────────────────────────────┤ │ - id: InventoryLogId │ │ - type: LogType // 流水类型 │ │ - quantity: int // 数量(正/负) │ │ - beforeQuantity: int // 变更前库存 │ │ - afterQuantity: int // 变更后库存 │ │ - reason: String // 原因 │ │ - referenceId: String // 关联单号 │ │ - operator: String // 操作人 │ │ - occurredAt: Instant // 发生时间 │ └────────────────────────────────────────────────────────────────┘三、代码实现3.1 Inventory 聚合根// ✅ 库存聚合根publicclassInventoryextendsAggregateRootInventoryId{privateSkuIdskuId;privateWarehouseIdwarehouseId;// 库存数量privateinttotalQuantity;privateintallocatedQuantity;// 已预占数量privateintsafetyStock;// 安全库存// 库存流水privateListInventoryLoglogs;// ========== 工厂方法 ==========publicstaticInventorycreate(SkuIdskuId,WarehouseIdwarehouseId,intinitialQuantity,intsafetyStock){if(initialQuantity0){thrownewIllegalArgumentException("初始库存不能为负数");}Inventoryinventory=newInventory();inventory.id=InventoryId.generate();inventory.skuId=skuId;inventory.warehouseId=warehouseId;inventory.totalQuantity=initialQuantity;inventory.allocatedQuantity=0;inventory.safetyStock=safetyStock;inventory.logs=newArrayList();// 记录初始库存流水inventory.addLog(InventoryLogType.INITIAL,initialQuantity,0,initialQuantity,"初始化库存");returninventory;}// ========== 业务方法 ==========/** * 入库 */publicvoidinbound(intquantity,Stringreason,StringreferenceId,Stringoperator){if(quantity=0){thrownewIllegalArgumentException("入库数量必须大于0");}intbeforeQuantity=this.totalQuantity;this.totalQuantity+=quantity;// 记录流水addLog(InventoryLogType.INBOUND,quantity,beforeQuantity,this.totalQuantity,reason,referenceId,operator);// 发布事件registerEvent(newInventoryInboundEvent(this.id,this.skuId,this.warehouseId,quantity,this.totalQuantity));// 检查库存预警checkAndNotifyLowStock();}/** * 出库 */publicvoidoutbound(intquantity,Stringreason,StringreferenceId,Stringoperator){if(quantity=0){thrownewIllegalArgumentException("出库数量必须大于0");}// 检查可用库存intavailable=getAvailableQuantity();if(availablequantity){thrownewInsufficientInventoryException(String.format("库存不足,可用:%d,需要:%d",available,quantity));}intbeforeQuantity=this.totalQuantity;this.totalQuantity-=quantity;// 记录流水addLog(InventoryLogType.OUTBOUND,-quantity,beforeQuantity,this.totalQuantity,reason,referenceId,operator);// 发布事件registerEvent(newInventoryOutboundEvent(this.id,this.skuId,this.warehouseId,quantity,this.totalQuantity));// 检查库存预警checkAndNotifyLowStock();}/** * 预占库存 */publicInventoryAllocationallocate(intquantity,StringorderId,InstantexpiresAt){if(quantity=0){thrownewIllegalArgumentException("预占数量必须大于0");}intavailable=getAvailableQuantity();if(availablequantity){thrownewInsufficientInventoryException(String.format("可用库存不足,无法预占。可用:%d,需要:%d",available,quantity));}// 增加预占数量this.allocatedQuantity+=quantity;// 创建预占记录InventoryAllocationallocation=InventoryAllocation.create(this.id,quantity,orderId,expiresAt);// 记录流水addLog(InventoryLogType.ALLOCATE,quantity,this.totalQuantity-quantity,this.totalQuantity,"订单预占",orderId,"SYSTEM");// 发布事件registerEvent(newInventoryAllocatedEvent(/