web-dev-qa-db-ja.com

リッチドメインモデル-正確には、動作はどのように適合しますか?

リッチドメインモデルと貧血ドメインモデルの議論では、インターネットは哲学的なアドバイスに満ちていますが、信頼できる例は不足しています。この質問の目的は、適切なドメイン駆動設計モデルの明確なガイドラインと具体例を見つけることです。 (理想的にはC#で。)

実際の例では、このDDDの実装は間違っているようです。

以下のWorkItemドメインモデルは、Entity Frameworkがコードファーストデータベースに使用するプロパティバッグにすぎません。ファウラーによると、それは 貧血 です。

WorkItemServiceレイヤーは、明らかにドメインサービスの一般的な誤解です。 WorkItemのすべての動作/ビジネスロジックが含まれています。イエメリヤノフなどによると、それは 手続き型 です。 (6ページ)

以下が間違っている場合、どうすれば正しくできますか?
動作、つまりAddStatusUpdateまたはCheckoutは、 WorkItemクラスに属していますか?
WorkItemモデルにはどのような依存関係がありますか?

enter image description here

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年後

2年間研究した後でも、DDDの「正しい方法」を知っているとは約束できません。ユビキタス言語、集約されたルーツ、および行動主導型設計へのそのアプローチは、DDDの業界への貴重な貢献です。持続性の無知とイベントの調達は混乱を引き起こし、私はそのような哲学がより広範な採用を妨げていると思います。しかし、私がこのコードを何度もやり直さなければならない場合、私が学んだことで、次のようになると思います。

enter image description here

有効なドメインモデルのベストプラクティスコードを提供するこの(非常にアクティブな)投稿に対する回答を歓迎します。

89
RJB

最も有用な回答はAlexey Zimarevによって与えられ、モデレーターが元の質問の下のコメントに移動する前に、少なくとも7票の投票を得ました。

彼の答え:

VimeoでJimmy BogardのNDC 2012セッション「Crafting Wicked Domain Models」を視聴することをお勧めします。彼は、リッチドメインがどうあるべきか、そしてエンティティに振る舞いを持たせることで実際にそれらを実装する方法を説明します。例は非常に実用的で、すべてC#で記述されています。

http://vimeo.com/4359819

私のチームの利益のためにビデオを要約するために、そしてこの投稿でもう少し即時の詳細を提供するためにいくつかのメモを取った。 (ビデオは1時間の長さですが、時間がある場合は1分ごとに本当に価値があります。ジミーボガードは彼の説明に多くのクレジットを与えるに値します。)

  • 「ほとんどのアプリケーションについて...開始時に複雑になることはわかりません。それらはそのようになるだけです。」
    • コードと要件が追加されると、複雑さが自然に増大します。 CRUDのように、アプリケーションは非常に簡単に開始できますが、動作/ルールが組み込まれる可能性があります。
    • 「いいことは、複雑に始める必要がないことです。貧血ドメインモデルから始めることができます。これは単なるプロパティバッグであり、標準のリファクタリング手法だけで真のドメインモデルに移行できます。」
  • ドメインモデル=ビジネスオブジェクト。ドメインの動作=ビジネスルール
  • 動作は多くの場合、アプリケーションで非表示になっています-PageLoad、Button1_Click、または 'FooManager'や 'FooService'などのヘルパークラスにある場合があります。
  • ドメインオブジェクトとは別のビジネスルールは、それらのルールを「覚えておく必要があります」。
    • 上記の個人的な例では、1つのビジネスルールはWorkItem.StatusHistory.Add()です。ステータスを変更するだけでなく、監査用にアーカイブしています。
  • ドメインの振る舞いは、「一連のテストを作成するよりもはるかに簡単にアプリケーションのバグを排除します。」テストでは、それらのテストを書くことを知っている必要があります。 ドメインビヘイビアーは、テストするための正しいパスを提供します
  • ドメインサービスは、「異なるドメインモデルエンティティ間のアクティビティを調整するヘルパークラス」です。
    • ドメインサービス!=ドメインの動作。エンティティには動作があり、ドメインサービスはエンティティ間の単なる仲介者です。
  • ドメインオブジェクトは、必要なインフラストラクチャ(IOfferCalculatorServiceなど)を所有するべきではありません。インフラストラクチャサービスは、それを使用するドメインモデルに渡す必要があります。
  • ドメインモデルは、何ができるかを伝えるために提供する必要があり、それらはそれらのことを行うことができるだけである必要があります。
  • ドメインモデルのプロパティは、プライベートセッターで保護する必要があります。これにより、モデルのみが独自の動作を介して独自のプロパティを設定できるようになります。それ以外の場合は「無差別」です。
  • ORMの単なるプロパティバッグである貧血ドメインモデルオブジェクトは、「薄いベニア-データベース上で強く型付けされたバージョン」にすぎません。
    • 「データベースの行をオブジェクトに入れるのは簡単ですが、それは私たちが得たものです。」
    • 'ほとんどの永続オブジェクトモデルはそれだけです。 貧血のドメインモデルと実際には動作しないアプリケーションを区別するのは、オブジェクトにビジネスルールがあるが、それらのルールがドメインモデルに見つからない場合です。 '
  • 「多くのアプリケーションでは、実際のビジネスアプリケーションロジックレイヤーを構築する必要はまったくありません。データベースとやり取りできるものであり、そこにあるデータを簡単に表す方法でもあります。」
    • つまり、特別なビジネスオブジェクトや動作ルールのないCRUDだけを実行している場合は、DDDは必要ありません。

含める必要があると思われるその他の点、またはこれらのノートのいずれかが適格ではないと思われる場合は、遠慮なくコメントしてください。できるだけ直接引用または言い換えることを試みた。

64
RJB

あなたの例は間違っているので、あなたの質問に答えることはできません。具体的には、動作がないためです。少なくとも、ドメインの領域ではありません。 AddStatusUpdateメソッドの例はドメインロジックではなく、そのドメインを使用するロジックです。この種のロジックは、外部の要求を処理する、ある種のサービスの内部にあることに意味があります。

たとえば、特定のワークアイテムが特定のステータスのみを持つことができる、またはそれがNステータスのみを持つことができるという要件があった場合、それはドメインロジックであり、メソッドとしてWorkItemまたはStatusHistoryの一部である必要があります。

混乱の理由は、ガイドラインを必要としないコードにガイドラインを適用しようとしているためです。ドメインモデルは、複雑なドメインロジックが多数ある場合にのみ関連します。例えば。エンティティ自体で機能し、要件から派生するロジック。コードが外部データからのエンティティの操作に関するものである場合、それはおそらくドメインロジックではありません。しかし、作業しているデータとエンティティに基づいて多くのifsを取得した瞬間、それがドメインロジックになります。

真のドメインモデリングの問題の1つは、複雑な要件の管理に関することです。そのため、その真の力と利点を単純なコードで紹介することはできません。真にメリットを確認するには、何十ものエンティティが必要です。繰り返しますが、この例は単純すぎてドメインモデルが本当に輝きません。

最後に、いくつかのOT私が言及することは、実際のOOP設計の真のドメインモデルは、Entity Frameworkを使用して永続化するのが本当に難しいことです。ORMがtrue OOP構造をリレーショナル構造にマッピングして設計されていますが、まだ多くの問題があり、リレーショナルモデルはしばしばOOPモデルにリークします。nHibernateを使用しても、これはEFよりもはるかに強力だと思いますが、これは問題になる可能性があります。

8
Euphoric

WorkItemに関連付けられたビジネスロジックを「ファットサービス」にカプセル化するというあなたの仮定は、私が主張する固有のアンチパターンであるとは必ずしも言えません。

貧血ドメインモデルについてのあなたの考えに関係なく、基幹業務.NETアプリケーションに典型的な標準パターンと実践は、さまざまなコンポーネントで構成されるトランザクションの階層化アプローチを奨励します。ビジネスロジックをドメインモデルから分離し、他の.NETコンポーネントだけでなく、さまざまなテクノロジスタックや物理層にまたがるコンポーネント間での共通ドメインモデルの通信を促進します。

この一例は、SOAP単純なデータ型を含む、たまたまSilverlightクライアントアプリケーションと通信する.NETベースのDLL Webサービスです。これはドメインエンティティプロジェクトを.NETアセンブリまたはSilverlightアセンブリに組み込むことができます。このDLLを持つ関心のあるSilverlightコンポーネントは、コンポーネントでのみ利用可能なコンポーネントに依存する可能性があるオブジェクトの動作に公開されません。サービス。

この議論についてのあなたのスタンスに関係なく、これはマイクロソフトが発表した採用され承認されたパターンであり、私の専門家の意見では、それは間違ったアプローチではありませんが、独自の動作を定義するオブジェクトモデルも必ずしもアンチパターンではありません。この設計を進める場合は、ドメインモデルを表示する必要がある他のコンポーネントと統合する必要がある場合に遭遇する可能性があるいくつかの制限と問題点を理解して理解するのが最善です。その特定のケースでは、オブジェクト指向スタイルのドメインモデルを特定の動作メソッドを公開しない単純なデータオブジェクトに変換するようにTranslatorに指示することができます。

5
maple_shaft

この質問はかなり古いので、この回答は後世のためのものです。理論に基づく例ではなく、具体的な例でお答えしたいと思います。

次のように、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の例です。

5
Don