ドメイン駆動型の設計:貧血ドメインを避け、現実世界の役割をモデリングする
-
26-10-2019 - |
質問
貧血ドメインモデルを避けることについて、どれだけ心配する必要があるかについてのアドバイスを探しています。私たちはDDDを始めたばかりで、単純な設計上の決定に関する分析麻痺に苦労しています。私たちが固執している最新のポイントは、特定のビジネスロジックが属する場所です。たとえば、 Order
オブジェクト Status
など。今、私は次のようなコマンドを実行しなければならないと言います UndoLastStatus
誰かが注文で間違いを犯したので、これは単に変更するほど単純ではありません Status
他の情報を記録する必要があり、プロパティが変更されます。今、現実の世界では、これは純粋な管理課題です。だから私がそれを見る方法で私は私が考えることができる2つのオプションを持っています:
オプション1:次のようなものを注文する方法を追加します
Order.UndoLastStatus()
, 、このちょっと意味がありますが、それは実際にはドメインを反映していません。またOrder
システム内の主要なオブジェクトであり、注文に関連するすべてが注文クラスに配置されている場合、物事は手に負えなくなる可能性があります。オプション2:aを作成します
Shop
オブジェクト、そしてそれに伴い、異なる役割を表す異なるサービスがあります。だから私は持っているかもしれませんShop.AdminService
,Shop.DispatchService
, 、 とShop.InventoryService
. 。この場合、私は持っているでしょうShop.AdminService.UndoLastStatus(Order)
.
2番目のオプションでは、ドメインをはるかに反映し、開発者が実際に存在する同様の役割についてビジネスの専門家と話すことができます。しかし、それはまた、貧血モデルに向かっています。一般的に行くためのより良い方法はどれですか?
解決
オプション2は確かに手続き型コードにつながります。
開発が簡単かもしれませんが、維持がはるかに困難です。
今、現実の世界では、これは純粋な管理課題です
「管理」タスクはプライベートであり、公開された完全に「ドメイン」アクションを通じて呼び出される必要があります。好ましくは、ドメインから駆動される理解しやすいコードでまだ書かれています。
私が見るように - 問題はそれです UndoLastStatus
ドメインの専門家にはほとんど意味がありません。
より多くの場合、彼らは注文の作成、キャンセル、埋めについて話している可能性があります。
これらの線に沿った何かがよりよくフィットするかもしれません:
class Order{
void CancelOrder(){
Status=Status.Canceled;
}
void FillOrder(){
if(Status==Status.Canceled)
throw Exception();
Status=Status.Filled;
}
static void Make(){
return new Order();
}
void Order(){
Status=Status.Pending;
}
}
私は個人的に「ステータス」の使用法を嫌います、それらはそれらを使用するすべてのものと自動的に共有されます - 私はそれを 不必要な結合.
だから私はこのようなものを持っているでしょう:
class Order{
void CancelOrder(){
IsCanceled=true;
}
void FillOrder(){
if(IsCanceled) throw Exception();
IsFilled=true;
}
static Order Make(){
return new Order();
}
void Order(){
IsPending=true;
}
}
秩序状態が変更されたときに関連するものを変更するために、最善の策はいわゆる使用を使用することです ドメインイベント.
私のコードはこれらの行に沿って見るでしょう:
class Order{
void CancelOrder(){
IsCanceled=true;
Raise(new Canceled(this));
}
//usage of nested classes for events is my homemade convention
class Canceled:Event<Order>{
void Canceled(Order order):base(order){}
}
}
class Customer{
private void BeHappy(){
Console.WriteLine("hooraay!");
}
//nb: nested class can see privates of Customer
class OnOrderCanceled:IEventHandler<Order.Canceled>{
void Handle(Order.Canceled e){
//caveat: this approach needs order->customer association
var order=e.Source;
order.Customer.BeHappy();
}
}
}
注文が大きすぎる場合は、何をチェックしてみてください 境界のあるコンテキスト エリック・エヴァンスが言うように、もし彼が彼の本を再び書く機会があれば、彼は境界のある文脈を最初に移動するだろう)。
要するに、それはドメインによって駆動される分解の形式です。
アイデアは比較的単純です - 異なる視点別のコンテキストから複数の注文を持っていることは問題ありません。
たとえば、ショッピングコンテキストから注文、会計コンテキストから注文します。
namespace Shopping{
class Order{
//association with shopping cart
//might be vital for shopping but completely irrelevant for accounting
ShoppingCart Cart;
}
}
namespace Accounting{
class Order{
//something specific only to accounting
}
}
しかし、通常、十分なドメイン自体が複雑さを回避し、十分によく聴くと簡単に分解できます。たとえば、OrderLifeCycle、OrderHistory、OrderDescriptionなどの専門家の用語から、分解のためにアンカーとして活用できることを聞くことができます。
NB:留意してください - 私はあなたのドメインについてゼロの理解を得ました。
私が使用している動詞はそれに完全に奇妙である可能性が非常に高いです。
他のヒント
class UndoLastStatusCommand {
public Guid OrderId { get; set; }
}
OrderServiceには、次のコマンドを処理する方法があります。
public void Process(UndoLastStatusCommand command) {
using (var unitOfWork = UowManager.Start()) {
var order = this.orderRepository.Get(command.OrderId);
if (order == null)
throw some exception
// operate on domain to undo last status
unitOfWork.Commit();
}
}
したがって、注文のドメインモデルは、注文に対応するすべてのデータと動作を公開しますが、注文サービスとサービスレイヤーは、注文で実行されるさまざまな種類の操作を宣言し、使用のためにドメインを公開することを宣言します。プレゼンテーションレイヤーなどの外部コンポーネント。
概念を調べることも検討してください ドメインイベント 貧血ドメインモデルとそれらを改善する方法を考慮します。
このドメインをテストから運転していないようです。の作品を見てください ロブ・ベンズ, 、特に、探索的モデリング、時間の反転、アクティブパッシブに関する彼の研究。