リッチドメインモデルと貧血ドメインモデルの議論では、インターネットは哲学的なアドバイスに満ちていますが、信頼できる例は不足しています。この質問の目的は、適切なドメイン駆動設計モデルの明確なガイドラインと具体例を見つけることです。 (理想的にはC#で。)
実際の例では、このDDDの実装は間違っているようです。
以下のWorkItemドメインモデルは、Entity Frameworkがコードファーストデータベースに使用するプロパティバッグにすぎません。ファウラーによると、それは 貧血 です。
WorkItemServiceレイヤーは、明らかにドメインサービスの一般的な誤解です。 WorkItemのすべての動作/ビジネスロジックが含まれています。イエメリヤノフなどによると、それは 手続き型 です。 (6ページ)
以下が間違っている場合、どうすれば正しくできますか?
動作、つまりAddStatusUpdateまたはCheckoutは、 WorkItemクラスに属していますか?
WorkItemモデルにはどのような依存関係がありますか?
public class WorkItemService : IWorkItemService {
private IUnitOfWorkFactory _unitOfWorkFactory;
//using Unity for dependency injection
public WorkItemService(IUnitOfWorkFactory unitOfWorkFactory) {
_unitOfWorkFactory = unitOfWorkFactory;
}
public void AddStatusUpdate(int workItemId, int statusId) {
using (var unitOfWork = _unitOfWorkFactory.GetUnitOfWork<IWorkItemUnitOfWork>()) {
var workItemRepo = unitOfWork.WorkItemRepository;
var workItemStatusRepo = unitOfWork.WorkItemStatusRepository;
var workItem = workItemRepo.Read(wi => wi.Id == workItemId).FirstOrDefault();
if (workItem == null)
throw new ArgumentException(string.Format(@"The provided WorkItem Id '{0}' is not recognized", workItemId), "workItemId");
var status = workItemStatusRepo.Read(s => s.Id == statusId).FirstOrDefault();
if (status == null)
throw new ArgumentException(string.Format(@"The provided Status Id '{0}' is not recognized", statusId), "statusId");
workItem.StatusHistory.Add(status);
workItemRepo.Update(workItem);
unitOfWork.Save();
}
}
}
(この例は読みやすくするために簡略化されています。混乱を招く試みであるため、コードは間違いなく不格好ですが、ドメインの動作は次のとおりでした:アーカイブ履歴に新しいステータスを追加してステータスを更新します。最終的に私は他の回答に同意します。これはCRUDだけで処理できます。)
@AlexeyZimarevが最高の答えを出し、C#の主題に関する完璧なビデオがJimmy Bogardによって提供されましたが、リンクを超えて十分な情報が得られなかったため、以下のコメントに移動しました。以下の私の回答にビデオを要約したメモの下書きがあります。訂正があれば回答にコメントしてください。ビデオは1時間の長さですが、一見の価値があります。
2年間研究した後でも、DDDの「正しい方法」を知っているとは約束できません。ユビキタス言語、集約されたルーツ、および行動主導型設計へのそのアプローチは、DDDの業界への貴重な貢献です。持続性の無知とイベントの調達は混乱を引き起こし、私はそのような哲学がより広範な採用を妨げていると思います。しかし、私がこのコードを何度もやり直さなければならない場合、私が学んだことで、次のようになると思います。
有効なドメインモデルのベストプラクティスコードを提供するこの(非常にアクティブな)投稿に対する回答を歓迎します。
最も有用な回答はAlexey Zimarevによって与えられ、モデレーターが元の質問の下のコメントに移動する前に、少なくとも7票の投票を得ました。
彼の答え:
VimeoでJimmy BogardのNDC 2012セッション「Crafting Wicked Domain Models」を視聴することをお勧めします。彼は、リッチドメインがどうあるべきか、そしてエンティティに振る舞いを持たせることで実際にそれらを実装する方法を説明します。例は非常に実用的で、すべてC#で記述されています。
私のチームの利益のためにビデオを要約するために、そしてこの投稿でもう少し即時の詳細を提供するためにいくつかのメモを取った。 (ビデオは1時間の長さですが、時間がある場合は1分ごとに本当に価値があります。ジミーボガードは彼の説明に多くのクレジットを与えるに値します。)
含める必要があると思われるその他の点、またはこれらのノートのいずれかが適格ではないと思われる場合は、遠慮なくコメントしてください。できるだけ直接引用または言い換えることを試みた。
あなたの例は間違っているので、あなたの質問に答えることはできません。具体的には、動作がないためです。少なくとも、ドメインの領域ではありません。 AddStatusUpdate
メソッドの例はドメインロジックではなく、そのドメインを使用するロジックです。この種のロジックは、外部の要求を処理する、ある種のサービスの内部にあることに意味があります。
たとえば、特定のワークアイテムが特定のステータスのみを持つことができる、またはそれがNステータスのみを持つことができるという要件があった場合、それはドメインロジックであり、メソッドとしてWorkItem
またはStatusHistory
の一部である必要があります。
混乱の理由は、ガイドラインを必要としないコードにガイドラインを適用しようとしているためです。ドメインモデルは、複雑なドメインロジックが多数ある場合にのみ関連します。例えば。エンティティ自体で機能し、要件から派生するロジック。コードが外部データからのエンティティの操作に関するものである場合、それはおそらくドメインロジックではありません。しかし、作業しているデータとエンティティに基づいて多くのif
sを取得した瞬間、それがドメインロジックになります。
真のドメインモデリングの問題の1つは、複雑な要件の管理に関することです。そのため、その真の力と利点を単純なコードで紹介することはできません。真にメリットを確認するには、何十ものエンティティが必要です。繰り返しますが、この例は単純すぎてドメインモデルが本当に輝きません。
最後に、いくつかのOT私が言及することは、実際のOOP設計の真のドメインモデルは、Entity Frameworkを使用して永続化するのが本当に難しいことです。ORMがtrue OOP構造をリレーショナル構造にマッピングして設計されていますが、まだ多くの問題があり、リレーショナルモデルはしばしばOOPモデルにリークします。nHibernateを使用しても、これはEFよりもはるかに強力だと思いますが、これは問題になる可能性があります。
WorkItemに関連付けられたビジネスロジックを「ファットサービス」にカプセル化するというあなたの仮定は、私が主張する固有のアンチパターンであるとは必ずしも言えません。
貧血ドメインモデルについてのあなたの考えに関係なく、基幹業務.NETアプリケーションに典型的な標準パターンと実践は、さまざまなコンポーネントで構成されるトランザクションの階層化アプローチを奨励します。ビジネスロジックをドメインモデルから分離し、他の.NETコンポーネントだけでなく、さまざまなテクノロジスタックや物理層にまたがるコンポーネント間での共通ドメインモデルの通信を促進します。
この一例は、SOAP単純なデータ型を含む、たまたまSilverlightクライアントアプリケーションと通信する.NETベースのDLL Webサービスです。これはドメインエンティティプロジェクトを.NETアセンブリまたはSilverlightアセンブリに組み込むことができます。このDLLを持つ関心のあるSilverlightコンポーネントは、コンポーネントでのみ利用可能なコンポーネントに依存する可能性があるオブジェクトの動作に公開されません。サービス。
この議論についてのあなたのスタンスに関係なく、これはマイクロソフトが発表した採用され承認されたパターンであり、私の専門家の意見では、それは間違ったアプローチではありませんが、独自の動作を定義するオブジェクトモデルも必ずしもアンチパターンではありません。この設計を進める場合は、ドメインモデルを表示する必要がある他のコンポーネントと統合する必要がある場合に遭遇する可能性があるいくつかの制限と問題点を理解して理解するのが最善です。その特定のケースでは、オブジェクト指向スタイルのドメインモデルを特定の動作メソッドを公開しない単純なデータオブジェクトに変換するようにTranslatorに指示することができます。
この質問はかなり古いので、この回答は後世のためのものです。理論に基づく例ではなく、具体的な例でお答えしたいと思います。
次のように、WorkItem
クラスの「作業項目ステータスの変更」をカプセル化します。
public SomeStatusUpdateType Status { get; private set; }
public void ChangeStatus(SomeStatusUpdateType status)
{
// Maybe we designed this badly at first ;-)
Status = status;
}
これでWorkItem
クラスは自身を正当な状態に維持する責任があります。ただし、実装はかなり弱いです。製品の所有者は、WorkItem
に対して行われたすべてのステータス更新の履歴を必要としています。
これを次のように変更します。
private ICollection<SomeStatusUpdateType> StatusUpdates { get; private set; }
public SomeStatusUpdateType Status => StatusUpdates.OrderByDescending(s => s.CreatedOn).FirstOrDefault();
public void ChangeStatus(SomeStatusUpdateType status)
{
// Better...
StatusUpdates.Add(status);
}
実装は大幅に変更されましたが、ChangeStatus
メソッドの呼び出し元は基礎となる実装の詳細を認識していないため、変更する必要はありません。
これは、リッチドメインモデルエンティティであるIMHOの例です。