首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >运输模块的DDD域建模

运输模块的DDD域建模
EN

Software Engineering用户
提问于 2020-03-27 23:21:41
回答 2查看 443关注 0票数 5

我正在尝试用C#和ERP对一个ERP系统的运输模块进行建模。该服务负责管理客户的皮卡和公司所有的卡车交付.客户皮卡包含订单列表,卡车包含停靠站列表,而这些站包含订单列表。

用于管理皮卡和卡车的主要接口是通过一个基于REST的api。通过服务总线队列从订购模块接收订单创建/更新/取消事件。

业务规则

  • 在订单输入时,为订单分配一个属性,以指定它是客户的皮卡还是通过卡车进行的交付。但是,这个运输模块中的用户可以将这些订单与特定的皮卡或卡车/停车实例联系起来。
  • 在给定的时间内,订单只能与单个皮卡或卡车停靠站相关联。
  • 订单具有包括状态和发货度量(维度、权重)在内的属性。
  • 卡车上的订单不能超过一定的体积或总重量。
  • 订单更新可以在任何时候接收,甚至在完成发货任务之后。这些更新可以更改托运类型(用户决定获取订单,而不是将订单发送给他们)或订单构建,这将改变发货度量。如果托运类型更改,则订单应与任何当前的皮卡或卡车停站无关。
  • 卡车处于开放/过境/关闭状态,卡车上的每一站都处于开放/交付状态。
  • 只有当所有订单都处于生产状态时,用户才能将卡车标记为在运输途中.
  • 一旦卡车在运输中,用户就会在运输完成后将每一站标记为已交付。只有在所有站点标记为已交付后,才能将卡车状态更新为关闭状态。
  • 如果订单被取消,则需要将其从可能关联的皮卡或卡车中自动删除。

我们使用关系数据库( Server)存储各个实体。我的问题实际上是如何建模这些不同的聚合根/实体/值对象/域服务/域事件以及支持它们的数据库表。

初始思想

  • 有皮卡,卡车和订单的集合体。
  • 收件有一个包含链接订单ids的值对象列表。
  • 卡车有一个停站实体列表和一个包含链接订单ids、订单状态和订单运送度量的值对象列表。
  • 考虑的其他选项--直接添加可空外键,以命令引用皮卡或卡车站;附加选项是有一个OrderAssociations表,该表将订单映射到皮卡或卡车站。

不过,为了执行卡车的业务规则,事情变得有点有趣了。如果将订单添加到卡车上,则需要考虑所有其他订单的总重和数量,如果订单将导致卡车超过规定的阈值,则应返回错误。如果收到已与卡车关联的订单的订单更新,并且该订单将导致卡车超过允许的阈值,则需要发射警报,并且在问题解决之前不能将卡车标记为正在运输中。

问题

  • 当收到订单更新时,我们需要知道它是否与皮卡或卡车停站相关联,如果是,则需要通知该对象。如果我们为PickupOrders和TruckStopOrders有单独的表,那么确定关联似乎不太有效,因为需要对这两个表进行查询。此外,我们还需要加载整个stops/orders卡车,以调用卡车聚合上的更新订单方法。您建议如何处理此订单更新?这是一个应用程序级事件处理程序来更新订单实体本身以及卡车吗?订单实体是否得到更新并引发卡车被某种方式通知的域事件?对于这个逻辑是属于域层还是应用层的想法很好奇。
  • 卡车站是价值物品还是实体?它们只存在于卡车的范围内。但是,它们确实具有与其关联的状态(打开/交付)和关联订单列表。
  • 这将是很好的订单保持一个参考皮卡或卡车站,他们是相关的。但是,这样做会将卡车站/皮卡和订单耦合在一起,因此任何订单更新都需要在同一事务中更新。不确定这是否会通过域服务或应用层进行管理?
  • 如果维护一个单独的卡车停站订单表,那么保持此表中的订单状态/指标与订单表同步的最佳方法是什么?

如有需要,乐意提供更多的澄清。任何想法都很感激。

EN

回答 2

Software Engineering用户

发布于 2020-03-28 01:46:17

作为一般经验规则(尤其是考虑到加载整个卡车的效率),您希望将聚合保持在较小的范围内,并且只更新一个事务中的单个聚合。这确实意味着跨越多个聚合的业务规则最终只能是一致的,但这通常不会导致任何实际问题。

在此基础上,你可能会考虑把你目前的设计翻个底朝天:而不是那些装有订单列表的皮卡和卡车站,而是创建一个一流的集合(比如PickupOrderTruckStopOrder)来模拟订单与皮卡或卡车站的关联。

Order上下文处理UpdateOrderCommand

  • 更新Order聚合。
  • 发布OrderUpdated事件。

卡车上下文处理OrderUpdated事件:

  • 更新TruckStopOrder聚合。
  • 发布TruckStopOrderUpdated事件。

卡车上下文处理TruckStopOrderUpdated事件:

  • 重新计算Truck集合的总体积/重量。
  • 如果违反约束,则发布警报。

要向卡车添加订单的UI可以使用来自Truck聚合的缓存信息来显示警告,如果卡车已经满了。在并发系统中,这永远不能保证是一致的,因为其他用户可能同时更新卡车。

票数 1
EN

Software Engineering用户

发布于 2020-03-29 05:59:51

我会用这个域模型..。我通常将业务需求分解为核心操作。一旦我确定了这些核心操作,我就会识别出承载这些操作的子域。

注意,我更喜欢首先识别操作,因为为了使这些操作成功,我更容易识别需要哪些类型的操作。

注:

仅仅因为OOP语言(如C#或Java )是通用的,并不意味着它们是对业务域建模的理想语言。因此,我真诚地相信静态类型的FP语言是一种自然的适合于业务域建模的语言,因为它的语法越少,信息密度越高。

关于数据持久性,我不认为关系数据库是理想的。我将使用事件存储(即不可变数据库),以便不能覆盖或删除数据。毕竟,这个域是关于对历史域事件的操作,这些事件永远不应该被更新或删除(只是附加的)。

我提供了以下模型,给出了域的描述:

CustomerOrder.Operations

代码语言:javascript
复制
namespace CustomerOrder

open Shared
open Language

module Operations =

    type PlaceOrder        = PlaceOrderSubmission        -> AsyncResult<unit,Error>
    type ChangeAcquisition = ChangeAcquisitionSubmission -> AsyncResult<unit,Error>

CustomerOrder.Language

代码语言:javascript
复制
module Language

open Shared

type AuthenticatedCustomer = TODO

type AcquisitionType = string // Ex: CustomerPickup | TruckDelivery

type PlaceOrderSubmission = {

    AuthenticatedCustomer : AuthenticatedCustomer
    Order                 : Order
    OrderRequestType      : AcquisitionType
}

type ChangeAcquisitionSubmission = {

    OrderSubmission       : PlaceOrderSubmission
    NewAcquisitionRequest : AcquisitionType
}

OrderDispatcher.Operations

代码语言:javascript
复制
namespace OrderDispatcher

open Shared
open Language

module Operations =

    type AvailableTruckers      = AvailableTruckersRequest         -> AsyncResult<TruckersOpen,Error>
    type DispatchTrucker        = DispatchTruckerSubmission        -> AsyncResult<unit,Error>
    type CancelledOrder         = OrderCancellationSubmission      -> AsyncResult<OrderCancellationReceipt,Error>
    type ChangeOrderAcquisition = OrderAcquisitionChangeSubmission -> AsyncResult<UnstartedOrder,Error>

OrderDispatcher.Language

代码语言:javascript
复制
module Language

open Shared

type TruckerId = string

type Trucker = {
    TruckerId : TruckerId
}

type DispatchTruckerSubmission = {
    Trucker : Trucker
    Order   : Order
}

type AvailableTruckersRequest = {
    Order   : Order
}

Trucker.Operations

代码语言:javascript
复制
namespace Trucker

open Shared
open Language

module Common =

    type QueryUnstartedOrders = AuthenticatedTrucker -> AsyncResult<UnstartedOrders,Error>

module NewOrderPending =

    type AcceptOrderRequest  = OrderResponseSubmission -> AsyncResult<unit,Error>
    type DeclineOrderRequest = OrderResponseSubmission -> AsyncResult<unit,Error>
    type ForfeitOrderRequest = OrderResponseSubmission -> AsyncResult<unit,Error>

module AcceptedOrder =

    type CancelAcceptance  = CancellableOrder         -> AsyncResult<unit,Error>
    type StartInTransit    = OrderProduced            -> AsyncResult<InTransitToPickupTrucker,Error>
    type InTransitToPickup = InTransitToPickupTrucker -> AsyncResult<IntransitToPickupOrder  ,Error>

    //----------------------------------------------------------------------------------------
    // Handle change of how order is acquired (i.e. pickup or delivery)
    //----------------------------------------------------------------------------------------
    type MyDelegate = delegate of obj * OrderCancelled -> unit

    type IOrderCancelled =
        [<CLIEvent>]
        abstract member OrderCancelled : IEvent<MyDelegate, OrderCancelled>

    type IncomingNotification () =
        let orderCancelled = new Event<MyDelegate, OrderCancelled> ()

        interface IOrderCancelled with
            [<CLIEvent>]
            member x.OrderCancelled = orderCancelled.Publish
    //----------------------------------------------------------------------------------------

module InTransitToDropoff =

    type CancelAcceptance   = InTransitToDropoffTrucker -> AsyncResult<OrderCancellationReceipt,Error>
    type InTransitToDropoff = InTransitToDropoffTrucker -> AsyncResult<IntransitToDropoffOrder ,Error>
    type ClaimDelivered     = InTransitToDropoffTrucker -> AsyncResult<OrderClosed          ,Error>

module OrdersCompleted =

    type CloseTruck = CloseTruckSubmission -> AsyncResult<ClosedTruckReceipt,Error>

Trucker.Language

代码语言:javascript
复制
module rec Language

open Shared

type TruckerStatus =
    | Open      of OpenedTrucker
    | InTransit of InTransitTrucker
    | Completed of CompletedTrucker

type AcceptedOrder = { 
    Trucker : OpenedTrucker
}

type IntransitToPickupOrder = { 
    Trucker : InTransitTrucker
}

type IntransitToDropoffOrder = { 
    Trucker : InTransitTrucker
}

type CompletedOrder = {
    Trucker : CompletedTrucker
}

type OrderResponseSubmission = {
    OpenedTrucker  : OpenedTrucker
    UnstartedOrder : UnstartedOrder
    Response       : Response
}

type InTransitTrucker = {
    Trucker        : AuthenticatedTrucker
    CurrentOrder   : OrderProduced
    OrdersProduced : OrderProduced seq
    OrdersClosed   : OrderProduced seq
}

type InTransitToPickupTrucker = {
    Trucker : AuthenticatedTrucker
    Order   : OrderInTransit
}

type InTransitToDropoffTrucker = {
    Trucker : AuthenticatedTrucker
    Order   : OrderInTransit
}

type CompletedTrucker = {
    Trucker      : AuthenticatedTrucker
    OrdersClosed : OrderProduced seq
}

type ArrivedAtDropoffSubmission = {
    Trucker : InTransitTrucker
}

type CancellableOrder =
    | OpenedTrucker    of OpenedTrucker
    | InTransitTrucker of InTransitTrucker

type CloseTruckSubmission = {
    OrdersClosed : OrderClosed seq
}

type ClosedTruckReceipt = {
    OrdersClosed : OrderClosed seq
}

共享语言

代码语言:javascript
复制
module Shared

type AsyncResult<'T,'error> = Async<Result<'T,'error>>
type Error = string

type OrderId         = string
type TruckerId       = string
type CustomerId      = string
type ItemId          = string
type Name            = string
type Description     = string
type Response        = string
type Address         = string
type Weight          = float
type feet            = float
type AcquisitionType = string
type CancellationId  = string
type OrderStatus     = string
type Dimensions = {
    Length : feet
    Width  : feet
    Height : feet
}

type AuthenticatedTrucker = {
    TruckerId : TruckerId
}

type OpenedTrucker = {
    Trucker : AuthenticatedTrucker
}

type Item = {
    ItemId      : ItemId
    Name        : Name
    Description : Description
    Weight      : Weight
    Dimensions  : Dimensions
}

type ItemQty = {
    Item : Item
    Qty  : int
}

type ItemQtys = ItemQty seq

type Pickup = {
    Address  : Address
    ItemQtys : ItemQtys
}

type Customer = {
    CustomerId : CustomerId
    Address    : Address
}

type Order = {
    OrderId    : OrderId
    Customer   : Customer
    Pickup     : Pickup
    Status     : OrderStatus
}

type OrderProduced = {
    Order : Order
}

type OrderInTransit = {
    OrderProduced : OrderProduced
}

type OrderClosed = {
    OrderInTransit : OrderInTransit
}

type OrderCancelled = {
    Order : Order
}

type OrderCancellationSubmission = {
    Order  : Order
    Reason : string
}

type OrderCancellationReceipt = {
    CancellationId : CancellationId
    Order          : Order
    Reason         : string
}

type OrderAcquisitionChangeSubmission = {
    Order           : OrderCancellationReceipt
    AcquisitionType : AcquisitionType
}

type UnstartedOrder = { Order: Order }

type UnstartedOrders = UnstartedOrder seq
票数 1
EN
页面原文内容由Software Engineering提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://softwareengineering.stackexchange.com/questions/407064

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档