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

DDD-032:案例:库存管理系统实战

DDD-032:案例:库存管理系统实战

本章导读

库存管理是电商系统的核心模块,涉及入库、出库、调拨、预警等复杂业务场景。本章通过库存管理系统案例,展示库存聚合的设计、并发扣减处理、领域事件在库存同步中的应用,以及分布式一致性解决方案。

学习目标

  1. 掌握库存聚合的设计与实现
  2. 学会处理库存并发扣减问题
  3. 理解库存预警和领域事件应用

前置知识

  • 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: List<InventoryLog> // 库存流水 │ ├────────────────────────────────────────────────────────────────┤ │ + 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 聚合根

// ✅ 库存聚合根publicclassInventoryextendsAggregateRoot<InventoryId>{privateSkuIdskuId;privateWarehouseIdwarehouseId;// 库存数量privateinttotalQuantity;privateintallocatedQuantity;// 已预占数量privateintsafetyStock;// 安全库存// 库存流水privateList<InventoryLog>logs;// ========== 工厂方法 ==========publicstaticInventorycreate(SkuIdskuId,WarehouseIdwarehouseId,intinitialQuantity,intsafetyStock){if(initialQuantity<0){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(available<quantity){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(available<quantity){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(</
http://www.cnnetsun.cn/news/3003038.html

相关文章:

  • 跨境电商多账号防关联,我如何用指纹浏览器解决“一锅端”问题
  • ArduSub水下飞控系统原理与实战指南
  • 三步掌握BilibiliDown:你的B站视频离线宝库
  • 第25篇-动态规划入门-从爬楼梯到经典状态转移
  • 3分钟掌握G-Helper:让你的华硕笔记本性能翻倍,续航倍增的秘密武器
  • 手把手教你用超算GEO 优化自家品牌
  • PHPWind SSRF漏洞挖掘与防御:从原理到实战的完整指南
  • Apache Tika XXE漏洞深度剖析:从原理到实战利用与防御
  • AI旅行规划实操指南:三层坐标系与七步转化法
  • 【3500字干货】高考志愿填报怎么选专业?考虑哪些现实因素?目标院校图书馆、宿舍、对待学生态度的真实信息从哪获取?
  • 终极指南:如何在qBittorrent中一键安装20+搜索引擎插件
  • 我们是如何管理多环境(开发、测试、生产)配置的?
  • 如何快速掌握MTKClient:联发科设备深度控制完整指南
  • FastAPI配置管理避坑指南:从硬编码到 .env 与 pydantic_settings 类,连路由用法都给你捋清楚
  • Token(词元),5分钟彻底搞懂
  • SEO思维如何赋能地理智能:从搜索优化到空间决策
  • Java 开发者“优雅”转战 Python:FastAPI 是 Spring Boot 的平替吗?
  • 当漏洞来了,你知道系统里用了什么吗?——SBOM 的真正价值
  • 2026零基础录音转文字入门指南避坑教学包教包会看完可直接上手
  • 【八股学习】大模型预训练数据 || 数据污染 || MHA、MQA和GQA || RoPE || KV Cache
  • 早期停止聚合:用并行短任务加速统计推断与机器学习计算
  • 最近,架构的招聘市场已经疯掉了。。。
  • 重构数字标牌基础设施:LibreSignage的开源API驱动解决方案
  • 具身智能本地化运行:VLA模型端侧部署技术解析
  • Spark.NET:一个试图把 Django / Rails 式开发体验带回 .NET 世界的全栈 Web 框架
  • TVA在物流分拣领域的独特价值(8)
  • KPI测量不是算数,而是定义可验证的业务动作
  • SQL注入实战指南:从原理到Payload的攻防解析
  • 《HarmonyOS技术精讲-UI开发 (基于NDK构建UI)》第6篇:集成第三方C++图形库——以Skia为例
  • UVa 599 The Forrest for the Trees