私は、ERPタイプシステムの輸送モジュールをC#とEF Coreを使用してモデル化しようとしています。このサービスは、顧客集荷と会社所有のトラック配送の管理を担当します。顧客集荷には注文とトラックのリストにはストップのリストが含まれており、それらのストップにはオーダーのリストが含まれています。
集荷およびトラックを管理するための主要なインターフェースは、RESTベースのAPIを介したものです。注文作成/更新/キャンセルイベントは、サービスバスキューを介して注文モジュールから受信されます。
ビジネスルール
個々のエンティティを格納するために、リレーショナルデータベース(SQL Server)を使用しています。私の質問は、これらのさまざまな集約ルート/エンティティ/値オブジェクト/ドメインサービス/ドメインイベント、およびそれらをサポートするデータベーステーブルをモデル化する方法を中心に考えています。
最初の考え
トラックにビジネスルールを適用するために、ここで少し面白くなります。注文をトラックに追加する場合、他のすべての注文の総重量と体積を考慮に入れる必要があり、注文によってトラックが規定のしきい値を超える場合はエラーが返されます。すでにトラックに関連付けられている注文の注文更新を受信し、その注文によりトラックが許容しきい値を超える場合、アラートを発する必要があり、問題が解決するまでトラックを輸送中としてマークできません。
質問
必要に応じて追加の説明を提供させていただきます。ご意見をお待ちしております。
一般的な経験則として(特にトラック全体をロードする効率が気になるため)、アグリゲートを小さく保ち、1つのトランザクションで1つのアグリゲートのみを更新する必要があります。これは、複数のアグリゲートにまたがるビジネスルールが最終的にのみ整合性を持つことを意味しますが、これは通常、実際的な問題を引き起こしません。
これに基づいて、現在の設計を裏返しにすることを検討する場合があります。注文のリストを含む集荷およびトラックストップの代わりに、モデルをモデル化するファーストクラスの集約(たとえばPickupOrder
およびTruckStopOrder
)を作成します。集荷またはトラックストップへの注文の関連付け。
注文コンテキストハンドルUpdateOrderCommand
:
Order
集合体を更新します。OrderUpdated
イベントを発行します。トラックコンテキストがOrderUpdated
イベントを処理します:
TruckStopOrder
集合体を更新します。TruckStopOrderUpdated
イベントを発行します。トラックコンテキストがTruckStopOrderUpdated
イベントを処理します:
Truck
集合体の総体積/総重量を再計算します。トラックに注文を追加するためのUIは、Truck
集計からキャッシュされた情報を使用して、トラックがすでに満車の場合に警告を表示できます。並行システムでは、他のユーザーが同時にトラックを更新している可能性があるため、これが一貫していることは決して保証できません。
これをドメインモデル化する方法を次に示します。通常、ビジネス要件をコアオペレーションに分解します。これらのコアオペレーションを特定したら、それらのオペレーションをホストするサブドメインを特定します。
これらの操作を成功させるために必要なタイプを特定する方が簡単なので、最初に操作を特定することをお勧めします。
注:
OOP C#やJavaなどの言語が汎用であるからといって、それらがビジネスドメインのモデル化に理想的な言語であるとは限りません。したがって、静的に型付けされるFP言語は、構文が少なく、情報密度が高いため、ビジネスドメインのモデリングに自然に適合します。
データの永続性に関しては、リレーショナルデータベースは理想的ではないと思います。イベントストア(つまり、不変データベース)を使用して、データを上書きまたは削除できないようにします。結局のところ、このドメインは、更新または削除してはならない(追加のみの)履歴ドメインイベントを操作することです。
ドメインの説明から、次のモデルを提供しました。
CustomerOrder.Operations
namespace CustomerOrder
open Shared
open Language
module Operations =
type PlaceOrder = PlaceOrderSubmission -> AsyncResult<unit,Error>
type ChangeAcquisition = ChangeAcquisitionSubmission -> AsyncResult<unit,Error>
CustomerOrder.Language
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
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
module Language
open Shared
type TruckerId = string
type Trucker = {
TruckerId : TruckerId
}
type DispatchTruckerSubmission = {
Trucker : Trucker
Order : Order
}
type AvailableTruckersRequest = {
Order : Order
}
Trucker.Operations
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
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
}
共有言語
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