将CQRS应用于库存管理

时间:2012-03-25 02:59:04

标签: domain-driven-design cqrs

我仍然试图将如何应用DDD以及最近的CQRS应用于真正的生产业务应用程序。就我而言,我正在研究库存管理系统。它作为基于服务器的应用程序运行,通过REST API向多个客户端应用程序公开。我的重点是使用API​​和客户端的域层。

域的命令端用于创建新订单,并允许修改,取消,将订单标记为已完成并已发货/已完成。当然,我有一个查询,它从存储库返回系统中的订单列表(作为只读,轻量级DTO)。另一个查询返回仓库员工用来从货架上提取物品以完成特定订单的PickList。为了创建PickList,必须对计算,规则等进行评估,以确定哪些订单已准备就绪。例如,如果所有订单行项目都有库存。我需要阅读相同的订单列表,遍历列表并应用这些规则和计算来确定哪些项应包含在PickList中。

这不是一个简单的查询,那么它如何适合模型?

更新

虽然我可以维护(存储)一组PickLists,但在员工检索下一个PickList之前,它们确实是动态的。请考虑以下情形:

收到当天的第一个订单。我可以引发一个触发AssemblePickListCommand的域事件,该事件应用所有规则和逻辑为该订单创建一个或多个PickLists。

收到第二份订单。现在,事件处理程序应该使用在两个待处理订单中优化的一个或多个新PickLists替换原始PickLists。

同样在收到第三个订单后。

我们假设我们现在在'队列'中有两个PickLists,因为优化规则拆分列表,因为组件位于仓库的两端。

仓库员工#1请求一个PickList。拉出并打印第一个PickList。

收到第四份订单。和以前一样,处理程序从队列中删除第二个PickList(剩下的唯一一个),并根据第二个PickList和新订单重新生成一个或多个PickList。

每当收到新订单时,PickList'汇编程序'将重复此逻辑。

我的问题是,在更新PickList队列时,请求必须阻塞,或者我的最终一致性问题与客户想要的行为相反。每次他们请求PickList时,他们都希望根据收到的所有订单进行优化。

3 个答案:

答案 0 :(得分:1)

  

虽然我可以维护(存储)一组PickLists,但在员工检索下一个PickList之前,它们确实是动态的。请考虑以下情形:

     

收到当天的第一个订单。我可以引发一个触发AssemblePickListCommand的域事件,该事件应用所有规则和逻辑来为该订单创建一个或多个PickLists。

     

收到第二份订单。事件处理程序现在应该使用在两个待处理订单中优化的一个或多个新PickLists替换原始PickLists。

这对我来说听起来像是在纠结尝试使用与您正在使用的域名实际上不匹配的语言。

特别是,如果PickList“队列”是真实的,我不相信你会遇到这些建模问题。我认为有一个OrderItem集合存在于某个聚合中,您可以向该聚合发出命令以生成PickList。

也就是说,我希望流程看起来像

onOrderPlaced(List<OrderItems> items)
    warehouse.reserveItems(List<OrderItems> items)
        // At this point, the items are copied into an unasssigned
        // items collection.  In other words, the aggregate knows
        // that the items have been ordered, and are not currently
        // assigned to any picklist
        fire(ItemsReserved(items))

onPickListRequested(Id<Employee> employee)
    warehouse.assignPickList(Id<Employee> employee, PickListOptimizier optimizer)
         // PickListOptimizer is your calculation, rules, etc that know how
         // to choose the right items to put into the next pick list from a
         // a given collection of unassigned items.  This is a stateless domain
         // *domain service* -- it provides the query that the warehouse aggregate needs
         // to figure out the right change to make, but it *doesn't* change
         // the state of the aggregate -- that's the aggregate's responsibility

        List<OrderItems> pickedItems = optimizer.chooseItems(this.unassignedItems);
        this.unassignedItems.removeAll(pickedItems);

        // This mockup assumes we can consider PickLists to be entities
        // within the warehouse aggregate.  You'd need some additional
        // events if you wanted the PickList to have its own aggregate
        Id<PickList> = PickList.createId(...);
        this.pickLists.put(id, new PickList(id, employee, pickedItems))
        fire(PickListAssigned(id, employee, pickedItems);

onPickListCompleted(Id<PickList> pickList)
    warehouse.closePicklist(Id<PickList> pickList)
        this.pickLists.remove(pickList)
        fire(PickListClosed(pickList)

onPickListAbandoned(Id<PickList> pickList)
    warehouse.reassign(Id<PickList> pickList)
        PickList list = this.pickLists.remove(pickList)
        this.unassignedItems.addAll(list.pickedItems)
        fire(ItemsReassigned(list.pickedItems)

不是很好的语言 - 我不会说仓库。但它涵盖了您的大部分要点:每次生成新的PickList时,它都是根据仓库中最新状态的待处理项目构建的。

存在一些争用 - 您无法将项目分配到选择列表并同时更改未分配的项目。这些是对同一聚合的两个不同的写入,并且我认为只要客户端每次都坚持使用完美优化的选项列表,您就不会解决这个问题。如果第二个最佳选择列表不时分配,与领域专家坐下来探讨业务的实际成本可能是值得的。毕竟,下订单到达仓库之间已经存在延迟......

答案 1 :(得分:0)

我真的不明白你的具体问题是什么。但首先要想到的是,选择列表创建不仅仅是一个查询,而是一个应该明确建模的全面业务概念。然后可以使用AssemblePicklist命令创建它。

答案 2 :(得分:0)

您似乎有两个角色/流程,也可能还有两个聚合根 - 销售人员使用订单,仓库工人使用选项列表。

AssemblePicklistsCommand()由订单处理触发,并重新创建所有当前未分配的选项列表。

仓库工人触发AssignPicklistCommand(userid),试图选择最合适的未分配选项列表并将其分配给他(如果他已经有一个有效的选项列表,则无所事事)。然后,他可以使用GetActivePicklistQuery(userid)获取选项列表,选择PickPicklistItemCommand(picklistid, item, quantity)项目,最后选择MarkPicklistCompleteCommand()来表示他已完成订单。

AssemblePicklist和AssignPicklist应该相互阻塞(串行处理,乐观的可靠性?)但是AssignPicklist和GetActivePicklist之间的关系是干净的 - 要么你有一个选择列表,要么你没有。