news 2026/6/25 14:20:46

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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(</
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/25 14:19:02

跨境电商多账号防关联,我如何用指纹浏览器解决“一锅端”问题

浏览器指纹是什么&#xff1f;从一段JS代码聊到指纹浏览器的技术实现 做跨境电商或社媒运营的朋友可能都遇到过&#xff1a;明明换了IP、清了缓存&#xff0c;几个账号还是被平台判定关联&#xff0c;一死死一片。我开始也以为是IP的问题&#xff0c;后来仔细研究才发现&#x…

作者头像 李华
网站建设 2026/6/25 14:18:35

ArduSub水下飞控系统原理与实战指南

1. 这不是遥控玩具船——ArduSub到底在解决什么问题&#xff1f;ArduSub&#xff0c;三个音节&#xff0c;背后是一整套面向水下机器人开发的开源飞控生态。它不是把无人机代码改个名扔进防水盒里就完事的“水下版Pixhawk”&#xff0c;而是针对流体环境动力学、高压密封结构约…

作者头像 李华
网站建设 2026/6/25 14:14:29

三步掌握BilibiliDown:你的B站视频离线宝库

三步掌握BilibiliDown&#xff1a;你的B站视频离线宝库 【免费下载链接】BilibiliDown (GUI-多平台支持) B站 哔哩哔哩 视频下载器。支持稍后再看、收藏夹、UP主视频批量下载|Bilibili Video Downloader &#x1f633; 项目地址: https://gitcode.com/gh_mirrors/bi/Bilibili…

作者头像 李华
网站建设 2026/6/25 14:14:06

第25篇-动态规划入门-从爬楼梯到经典状态转移

概述 上一篇我们学习了贪心算法&#xff0c;核心是局部最优能否推出全局最优。 这一篇我们进入动态规划&#xff0c;也就是常说的 DP。 动态规划是很多初学者的难点&#xff0c;因为它看起来不像排序、二分那样“有固定套路”&#xff0c;而更像是在做一件事&#xff1a; 把一个…

作者头像 李华
网站建设 2026/6/25 14:10:56

手把手教你用超算GEO 优化自家品牌

超算GEO 是一套专做 AI 搜索优化&#xff08;GEO&#xff09; 的平台。它解决的&#xff0c;是一个正在变重要、却被很多品牌忽略的问题——当用户不再打开百度、而是直接问豆包、DeepSeek、文心一言时&#xff0c;AI 的那段回答里&#xff0c;有没有你。 这事的专业门槛其实不…

作者头像 李华