1. 业务场景描述
1.1 库存中心的业务操作
- 增加:通过采购入库,增加库存
- 锁定:下单之后锁定库存
- 解锁:订单取消之后,释放库存
- 扣减:订单支付成功之后扣减库存,扣减锁定库存
- 返还:退货之后返还库存,增加库存
1.2 库存中心三种商品库存数量
- 可销售库存数量 SaleStockQuantity,通常显示在商品详情页,用户提交订单后,可销售库存的商品数量-1
- 锁定库存数量 LockedStockQuantity,用户提交订单后,锁定库存中的商品数量+1,当用户支付订单后锁定库存中的商品数量-1
- 已销售库存数量 SaledStockQuantity,当用户支付订单之后,已销售库存数量+1
1.3 系统中需要调用库存接口的场景大概有
- 网站上的商品详情页中显示的库存信息(查询可销售库存数量)
- 采购入库之后,采购中心会调用库存中心接口,通知库存中心将某个商品的 SaleStockQuantity 累加上采购数量 PurchaseInputQuantity
- 提交订单后,订单中心调用库存中心接口,通知库存中心将商品的 SaleStockQuantity-1,同时 LockedStockQuantity+1
- 支付订单后,订单中心会调用库存中心接口,通知库存中心将商品的 LockedStockQuantity-1,同时将商品的 SaledStockQuantity+1
- 取消订单后,订单中心会调用库存中心接口,通知库存中心将商品的 LockedStockQuantity-1,同时将商品的 SaleStockQuantity+1
- 退货入库后,仓库中心会调用存库中心接口,通知库存中心将商品的 SaleStockQuantity+1,同时将商品的 SaledStockQuantity-1
2. 不使用设计模式来实现
以采购入库、提交订单为例,用伪代码来实现这两个业务流程:
InventoryFacadeServiceImpl 实现类 ```java public class InventoryFacadeServiceImpl implements InventoryFacadeService{
/**
@Description 通知库存中心,“采购入库完成”事件发生了 **/ public Boolean informPurchaseInputFinished(PurchaseInputOrder 采购入库单){ // 1. 从采购入库单,获取采购商品sku id集合 // a. 从采购入库单获取采购条目 List
PurchaseInputOrder.getItems() // b. 遍历list,goodsSkuIds.add(item.getGoodsSkuId()) // c. 返回 List List getGoodsSkuIds(采购入库单); // 2. 通过商品sku id集合,创建商品库存GoodsStock集合 // a. 遍历goodsSkuIds集合,根据sku id获取商品在库存中心的库存信息 // b. 如果没有库存信息,创建这个商品的空数据库存信息,插入数据库,且添加到GoodsStock集合 // c. 如果有库存信息,添加到GoodsStock集合 // d. 返回 List
List createGoodsStocks(List goodsSkuIds); // 3. 准备一个采购入库单条目 Map 集合 Map
itemMap // a. 从采购入库单获取采购条目集合 List // b. 遍历采购条目集合,itemMap.put(item.getSkuId(), item) Map itemMap = getItemList(采购入库单);
// 开始更新库存,遍历商品库存 GoodsStock 集合
for(GoodsStock goodsStock : GoodsStocks){
// 4. 更新商品的可销售库存数量
// a. 从itemMap获取采购入库单条目 item= itemMap.get(goodsStock.getSkuId())
// b. 将条目中的采购数量累加到商品的可销售库存
// goodsStock.setSaleStockQuantity(采购数量+原可销售库存);
updateSaleStockQuantity();
// 5. 更新商品的库存状态
// a. 如果可销售库存大于0,则状态:goodsStock.setStockStatus(有库存)
// b. 否则,goodsStock.setStockStatus(无库存)
updateStockStatus();
// 6. 更新商品库存的修改事件
// a. goodsStock.setGmtModified(当前时间);
updateGmtModified();
// 7. 执行数据库更细商品库存的操作
// a. goodsStockDAO.updateGoodsStock(goodsStock);
executeUpdateGoodsStock();
}
}
/**
@Description 通知库存中心,“提交订单”事件发生了 **/ public Boolean informSubmitOrderEvent(订单信息){ // 1. 从订单,获取商品sku id集合 // a. 从订单获取商品条目 List
order.getItems() // b. 遍历list,goodsSkuIds.add(item.getGoodsSkuId()) // c. 返回 List List getGoodsSkuIds(采购入库单); // 2. 通过商品sku id集合,创建商品库存GoodsStock集合 // a. 遍历goodsSkuIds集合,根据sku id获取商品在库存中心的库存信息 // b. 如果没有库存信息,创建这个商品的空数据库存信息,插入数据库,且添加到GoodsStock集合 // c. 如果有库存信息,添加到GoodsStock集合 // d. 返回 List
List createGoodsStocks(List goodsSkuIds); // 3. 准备一个订单商品条目 Map 集合 Map
itemMap // a. 从订单获取商品条目集合 List // b. 遍历订单商品条目集合,itemMap.put(item.getSkuId(), item) Map itemMap = getItemList(订单);
// 开始更新库存,遍历商品库存 GoodsStock 集合
for(GoodsStock goodsStock : GoodsStocks){
// 4. 更新商品的可销售库存数量
// a. 从itemMap获取订单商品条目 item= itemMap.get(goodsStock.getSkuId())
// b. 将商品的可销售库存减去商品条目中购买的数量
// goodsStock.setSaleStockQuantity(原可销售库存-购买数量);
updateSaleStockQuantity();
// 5. 更新所锁定库存数量
// a. 从itemMap获取订单商品条目 item= itemMap.get(goodsStock.getSkuId())
// b. 将商品的锁定库存累加商品条目中购买的数量
// goodsStock.setLockedStockQuantity(原锁定库存+购买数量);
updateLockedStockQuantity();
// 6. 更新商品的库存状态
// a. 如果可销售库存大于0,则状态:goodsStock.setStockStatus(有库存)
// b. 否则,goodsStock.setStockStatus(无库存)
updateStockStatus();
// 7. 更新商品库存的修改事件
// a. goodsStock.setGmtModified(当前时间);
updateGmtModified();
// 8. 执行数据库更细商品库存的操作
// a. goodsStockDAO.updateGoodsStock(goodsStock);
executeUpdateGoodsStock();
} }
<a name="KpHDY"></a>
## 3. 使用设计模式
<a name="ShcOM"></a>
### 3.1 模板方法设计模式
**模式核心思想:**<br />如果一个场景下,涉及到多个不同的处理逻辑,但是它们又有共同的基础逻辑,那么我们可以将共同的基础逻辑抽取到父类中,然后将需要子类去实现的逻辑留空,交给子类去实现。
像上面的更新库存的业务流程,其实是有一个固定的流程来更新库存,例如:先更新可销售库存、后更新锁定库存、最后更新销售库存、接着检查库存状态、设置修改时间、最后执行更新;
**在不使用设计模式的情况:**<br />可能每个接口由不同的人实现,可能逻辑都是对的,但是每个具体的流程可能执行顺序就不一样,导致不同的更新逻辑似乎差别很不一样,对后期的代码的维护和理解增加难度;
**在使用模板方法设计模式的情况下:**
- 首先,将一些通用的逻辑抽取到父类中;
- 其次,可以在父类定义模板方法,模板方法定义一些子方法,留给子类去实现。这样基于模板的模式,就可以限定每个库存更新的过程都是按照一样的步骤去执行,很清晰;
- 最后,如果后续要修改库存更新的逻辑,或者新增一种库存更新的逻辑,都是按照这个模板顺序去执行,很容易读懂,逻辑不会出错;
<a name="TbAKd"></a>
### 3.2 命令模式
**模式核心思想:**<br />一些场景下,可能会执行一些不同逻辑的操作,这些操作都实现了一个接口,但是将不同类型的逻辑操作封装成不同的命令对象;
例如,库存更新场景下,将不同类型的库存更新操作,封装成不同的库存更新命令,这里暂时只是简单的执行这个命令中的更新逻辑,但是后续可以对库存更新操作扩展一下,例如实现异步化,库存更新操作的撤销这些操作;
<a name="nQHKO"></a>
### 3.3 工厂方法设计模式
**模式核心思想:**<br />工厂模式的目的就是为了不在自己的代码里面手动 new 一个实现类出来,真正做到面向接口编程,调用方不用 care 具体是哪一个实现类;
这样的话,如果 new 出来的实现类,如果有一天需要换一个新的实现类逻辑,调用方就不用关注这些实现类的变动,由工厂类来做出修改,提供实现类的实例,调用方面向接口编程,接口不变,调用方代码就不用变;
库存更新的逻辑这里,可以实践一下工厂模式,不同的库存更新命令,需要不同的工厂来创建这些命令,但是这些工厂之间有一些通用逻辑,可以抽取到父工厂里面,采用模仿方法模式+工厂模式,子工厂可以专注干自己的事情就可以了;
<a name="s8Axd"></a>
### 3.4 具体实现
![image.png](https://cdn.nlark.com/yuque/0/2021/png/1471554/1637315934776-6d95ed04-cdeb-4270-a00e-32a4ca56a508.png#clientId=ue026d90a-18b0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=298&id=u267467fe&margin=%5Bobject%20Object%5D&name=image.png&originHeight=596&originWidth=1419&originalType=binary&ratio=1&rotation=0&showTitle=false&size=140386&status=done&style=none&taskId=u15810dc5-b051-4d34-baf2-fd1617a4cf8&title=&width=709.5)
<a name="QGJfc"></a>
#### (1) 工厂接口
```java
// 面向接口编程
public interface StockUpdaterFactory<T>{
StockUpdateCommand create(T parameter); // T parameter 可以是采购入库单、订单
}
(2) 抽象工厂父类
// 模板方法模式在父类定义一个模板方法,完成基础通用逻辑,留空特殊逻辑给子类实现
public abstract class AbstractStockUpdaterFactory<T> implements StockUpdaterFactory<T> {
// 实现接口定义的方法,作为模板方法
@Override
public StockUpdateCommand create(T parameter) {
// 1. 从订单,获取商品sku id集合
// a. 从订单获取商品条目 List<OrderItem> order.getItems()
// b. 遍历list,goodsSkuIds.add(item.getGoodsSkuId())
// c. 返回 List<sku id>
List<Long> getGoodsSkuIds(采购入库单、订单); // 通用逻辑,父类自己实现
// 2. 通过商品sku id集合,创建商品库存GoodsStock集合
// a. 遍历goodsSkuIds集合,根据sku id获取商品在库存中心的库存信息
// b. 如果没有库存信息,创建这个商品的空数据库存信息,插入数据库,且添加到GoodsStock集合
// c. 如果有库存信息,添加到GoodsStock集合
// d. 返回 List<GoodsStock>
List<GoodsStock> createGoodsStocks(List<Long> goodsSkuIds); // 通用逻辑,父类自己实现
//返回命令类,不同的子工厂返回不同的命令,留待子类工厂去实现
return createStockUpdateCommand(goodsSkuIds, parameter);
}
// 留空,待子类工厂去完善创建不同的命令对象
protected abstract StockUpdateCommand createStockUpdateCommand(
List<GoodsStockDO> goodsStockDOS, T parameter)
}
(3) 采购入库更新库存命令的工厂类
// 子工厂类,返回采购入库更新命令,完成父类中留空的逻辑
@Component
public class PurchaseInputStockUpdaterFactory<T> extends AbstractStockUpdaterFactory<T>{
// 完善父类留空的创建命令的方法,具体实现采购入库更新的命令的创建
protected abstract StockUpdateCommand createStockUpdateCommand(
List<GoodsStockDO> goodsStockDOS, T parameter){
// 3. 准备一个采购入库单条目 Map 集合 Map<sku id, PurchaseInputOrderItem> itemMap
// a. 从采购入库单获取采购条目集合 List<PurchaseInputOrderItem>
// b. 遍历采购条目集合,itemMap.put(item.getSkuId(), item)
Map<sku id, PurchaseInputOrderItem> itemMap = getItemList(采购入库单);
// 返回采购入库更新库存命令
StockUpdateCommand command = new PurchaseInputStockUpdateCommand(
goodsStockDOS, itemMap)
}
}
(4) 更新库存命令接口
// 面向接口编程
public interface StockUpdateCommand {
// 更新库存操作
Boolean updateGoodsStock();
}
(5) 更新库存命令的抽象父类
// 根据模板方法模式,在父类定义模板方法,执行一些固定步骤的流程,具体流程留空待子类实现
public abstract class AbstractStockUpdateCommand implements StockUpdateCommand{
// 构造函数
public AbstractStockUpdateCommand(List<GoodsStockDO> goodsStockDOS) {
this.goodsStockDOS = goodsStockDOS;
}
// 实现接口定义的方法,作为模板方法
// 更新库存的固定流程,可留空,待不同命令去具体实现
@Override
public Boolean updateGoodsStock(){
// 步骤1~5对库存信息对象的关键字段进行更新操作
// 步骤1. 更新可销售库存
updateSaleStockQuantity();
// 步骤2. 更新锁定库存
updateLockedStockQuantity();
// 步骤3. 更新已销售库存
updateSaledStockQuantity();
// 步骤4. 更新库存状态
updateStockStatus();
// 步骤5. 更新库存状态
updateGmtModified();
// 步骤6. 将更新后的库存信息对象,更新到数据库
executeUpdateGoodsStock();
}
// 以下方法都是通用逻辑,放在父类
private void executeUpdateGoodsStock(){
// 对库存信息对象进行持久化
}
private void updateGmtModified(){
// 更新库存的修改时间字段
}
private void updateStockStatus(){
// 更新库存的状态字段
}
// 以下方法留空,待具体的命令子类去决定实现哪些流程
protected abstract void updateSaledStockQuantity();
protected abstract void updateLockedStockQuantity();
protected abstract void updateSaleStockQuantity();
}
(6) 采购入库更新命令
// 模板方法的具体实现类
public class PurchaseInputStockUpdateCommand extends AbstractStockUpdateCommand {
// 1. 准备一个订单商品条目 Map 集合 Map<sku id, OrderItem> itemMap
Map<sku id, PurchaseInputOrderItem> itemMap;
// 2. 构造函数
public PurchaseInputStockUpdateCommand(
List<GoodsStockDO> goodsStockDOS,
Map<Long, PurchaseInputOrderItemDTO> purchaseInputOrderItemDTOMap) {
super(goodsStockDOS);
this.purchaseInputOrderItemDTOMap = purchaseInputOrderItemDTOMap;
}
@Override
protected void updateSaleStockQuantity() throws Exception {
// 更新sku id, 对应的商品库存信息中的可销售库存
}
@Override
protected void updateSaledStockQuantity() throws Exception {
// 采购入库,不需要更新已销售库存,留空
}
@Override
protected void updateLockedStockQuantity() throws Exception {
// 采购入库,不需要更新锁定库存,留空
}
}
(7) Service 中调用各种库存更新命令
@Service
public class InventoryFacadeServiceImpl implements InventoryFacadeService {
// 注入采购入库命令的工厂
@Autowired
private PurchaseInputStockUpdaterFactory<PurchaseInputOrderDTO>
purchaseInputStockUpdaterFactory;
/**
* @Description 通知库存中心,“采购入库完成”事件发生了
* @Param [purchaseInputOrderDTO] 采购入库单DTO
* @return Boolean
**/
@Override
public Boolean informPurchaseInputFinished(
PurchaseInputOrderDTO purchaseInputOrderDTO) {
try {
StockUpdateCommand goodsStockUpdateCommand =
purchaseInputStockUpdaterFactory.create(purchaseInputOrderDTO);
goodsStockUpdateCommand.updateGoodsStock();
}catch (Exception e){
LOG.error("error", e);
return false;
}
return true;
}
}