【问题标题】:Applying CQRS to Inventory Management将 CQRS 应用于库存管理
【发布时间】:2012-03-25 02:59:04
【问题描述】:

我仍在努力思考如何将 DDD 以及最近的 CQRS 应用于实际的生产业务应用程序。就我而言,我正在开发一个库存管理系统。它作为基于服务器的应用程序运行,通过 REST API 向多个客户端应用程序公开。我的重点一直是领域层以及 API 和要遵循的客户端。

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

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

更新

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

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

收到第二份订单。事件处理程序现在应该用一个或多个针对两个挂起订单优化的新 PickList 替换原始 PickList。

收到第三份订单后也是如此。

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

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

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

PickList 'assembler' 将在收到新订单时重复此逻辑。

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

【问题讨论】:

    标签: domain-driven-design cqrs


    【解决方案1】:

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

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

    收到第二个订单。事件处理程序现在应该用一个或多个针对两个挂起订单优化的新 PickList 替换原始 PickList。

    在我看来,您在尝试使用与您工作的领域不匹配的语言时遇到了麻烦。

    特别是,如果 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 时,它都是根据仓库中待处理项目的最新状态构建的。

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 在我所阅读的内容中,就其性质而言,命令是单向的。换句话说,我没有看到任何示例或阅读以这种方式使用的命令的描述。我知道这是可能的,只是我没有看到有人讨论过。
      • 我不确定我们是否在谈论同一件事。 Assemble Picklist 命令及其用法与任何其他命令没有区别。这里可能有一些误解。 “以这种方式使用的命令”到底是什么意思?
      • 抱歉,我没有收到您评论过的提醒。
      • 我的意思是,我看到的每个示例以及我读过的所有材料都将命令视为单向操作。换句话说,它们不返回数据。
      • 您是否使用命令来封装您的查询以及处理您的域对象?
      【解决方案3】:

      您似乎有两个角色/流程,并且可能还有两个聚合根 - 销售人员处理订单,仓库工人处理拣货清单。

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

      仓库工作人员触发AssignPicklistCommand(userid),它会尝试选择最合适的未分配拣货清单并将其分配给他(如果他已经有一个活动的拣货清单,则什么也不做)。然后,他可以使用GetActivePicklistQuery(userid) 获取选择列表,使用PickPicklistItemCommand(picklistid, item, quantity) 选择项目,最后使用MarkPicklistCompleteCommand() 表示他已完成订单。

      AssemblePicklist 和 AssignPicklist 应该相互阻止(串行处理,乐观并发?)但是 AssignPicklist 和 GetActivePicklist 之间的关系是干净的 - 要么分配了一个选项列表,要么没有分配。

      【讨论】:

        猜你喜欢
        • 2012-04-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-09
        • 1970-01-01
        • 2019-09-22
        相关资源
        最近更新 更多