基于Axon框架的CQRS购物车售罄商品处理技术咨询
一、检测ShoppingCart聚合的不一致状态
要检测购物车聚合包含售罄商品的不一致状态,可从事件联动、定期校验、查询端实时校验三个维度落地:
事件驱动联动校验
当商品领域发布StockDepleted(库存耗尽)事件时,在命令端新增事件处理器监听该事件。处理器根据售罄商品ID,从读库查询所有包含该商品的购物车ID,针对每个符合条件的购物车,发起ValidateShoppingCartItemStock命令,触发ShoppingCart聚合内部校验逻辑,直接判断商品是否已售罄。定期批量校验
针对长时间未操作的购物车(例如超过7天),通过定时任务(如Spring Task)发起BatchValidateShoppingCartStock批量校验命令。命令端接收后,逐个触发ShoppingCart聚合的库存校验逻辑,对比当前商品实时库存,标记出包含售罄商品的购物车。查询端实时校验
在查询端加载购物车数据时,直接调用商品库存服务的实时接口,逐个校验购物车中商品的库存状态,快速识别出已售罄的商品。这种方式无需修改命令端聚合状态,适合展示前的即时检测场景。
二、移除售罄商品的时机选择
结合CQRS架构特点及业务场景,可选择以下三种时机执行移除操作:
命令端主动修正(保持聚合一致性)
当通过事件联动或定期校验检测到购物车包含售罄商品时,直接在命令端发起RemoveProduct命令,触发ShoppingCart聚合执行移除逻辑并发布ProductRemoved事件。后续由Axon的事件处理器同步该事件到查询端的投影(Projection),确保命令端聚合与查询端数据完全一致。这种方式适合需要严格保持聚合数据准确性的场景,避免用户后续操作时遇到无效商品。查询端展示时过滤(优先保障用户体验)
由于购物车展示属于查询端职责,可在查询端组装购物车展示数据时,直接过滤掉已售罄的商品。同时异步触发命令端的RemoveProduct命令,后续同步聚合状态。这种方式能快速响应用户的展示请求,避免等待命令端处理的延迟,适合注重用户体验的场景。用户交互触发修正
在查询端展示购物车时,将已售罄商品标记为「已售罄」并提供移除按钮,用户确认后前端发起RemoveProduct命令,由命令端完成聚合状态的修正。这种方式将控制权交给用户,适合需要用户明确确认的业务场景。
具体Axon实现示例
事件监听触发移除命令
@Component public class StockEventHandler { private final CommandGateway commandGateway; private final ShoppingCartQueryRepository shoppingCartQueryRepo; public StockEventHandler(CommandGateway commandGateway, ShoppingCartQueryRepository shoppingCartQueryRepo) { this.commandGateway = commandGateway; this.shoppingCartQueryRepo = shoppingCartQueryRepo; } @EventHandler public void handle(StockDepletedEvent event) { // 从读库查询包含该售罄商品的所有购物车ID List<String> cartIds = shoppingCartQueryRepo.findCartIdsByProductId(event.getProductId()); for (String cartId : cartIds) { // 发起移除商品命令 commandGateway.send(new RemoveProductCommand(cartId, event.getProductId())); } } }
查询端投影实时过滤售罄商品
@Component public class ShoppingCartProjection { private final ShoppingCartQueryRepository repository; private final ProductStockService productStockService; public ShoppingCartProjection(ShoppingCartQueryRepository repository, ProductStockService productStockService) { this.repository = repository; this.productStockService = productStockService; } @EventHandler public void handle(ProductAddedEvent event) { // 常规投影更新逻辑 ShoppingCartView cart = repository.findById(event.getCartId()).orElse(new ShoppingCartView()); cart.addItem(event.getProductId(), event.getQuantity()); repository.save(cart); } // 查询方法:实时过滤售罄商品返回给前端 public ShoppingCartView getShoppingCart(String cartId) { ShoppingCartView cart = repository.findById(cartId).orElseThrow(() -> new RuntimeException("购物车不存在")); // 过滤已售罄的商品 cart.getItems().removeIf(item -> !productStockService.hasStock(item.getProductId())); return cart; } }
内容的提问来源于stack exchange,提问作者user3227507

