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


84

リッチドメインモデルと貧血ドメインモデルの議論では、インターネットは哲学的なアドバイスに満ちていますが、権威のある例は不足しています。この質問の目的は、適切なドメイン駆動設計モデルの決定的なガイドラインと具体例を見つけることです。(理想的には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#の主題に関する完璧なビデオであるベストアンサーを提供しましたが、リンク以外の情報を十分に提供しなかったため、以下のコメントに移動したようです。以下の回答でビデオを要約したメモの大まかなドラフトがあります。修正については、回答に自由にコメントしてください。ビデオは1時間ですが、見る価値があります。

更新-2年後

DDDの成熟期の兆候だと思います。2年間勉強した後でも、「正しい方法」を知っているとは約束できません。ユビキタス言語、集約されたルート、および動作駆動設計へのアプローチは、DDDの業界への貴重な貢献です。永続性の無知とイベントソーシングは混乱を引き起こします。そのような哲学はそれを広く採用することを妨げていると思います。しかし、私が学んだことでこのコードをもう一度やり直さなければならないとしたら、次のように見えると思います。

ここに画像の説明を入力してください

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


6
すべての哲学理論は、あなたがそれらを語るときに地面に落ちる"I don't want to duplicate all my entities into DTOs simply because I don't need it and it violates DRY, and I also don't want my client application to take a dependency on EntityFramework.dll"。Entity Frameworkの専門用語で「エンティティは、」「ドメインモデル」のような「実体」と同じではありません
フェデリコBerasategui

Automapperのような自動化ツールを使用して、ドメインエンティティをDTOに複製しても問題ありません。私はそれが一日の終わりにどのように見えることになっているのかわからないだけです。
RJB

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

ありがとう、私はビデオの途中で、これはこれまでのところ完璧です。これが間違っていた場合、どこかに「正しい」答えがなければならないことは知っていました
。...-RJB

2
私もJavaに対する愛を要求します:/
uylmz

回答:


59

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

彼の答え:

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

http://vimeo.com/43598193

この投稿では、チームの利益のためにビデオを要約し、もう少し詳細を提供するためにいくつかのメモを取りました。(ビデオの長さは1時間ですが、時間があれば1分ごとに本当に価値があります。ジミーボガードは、彼の説明に多大な功績があります。)

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

含めるべきだと思う他のポイント、またはこれらのノートのいずれかが規格外であると思われる場合は、お気軽にコメントしてください。できるだけ直接引用するか、言い換えることを試みました。


特に、ツールでリファクタリングがどのように機能するかを確認できる素晴らしいビデオです。ドメインオブジェクトを適切にカプセル化することが重要です(一貫性を保つため)。彼は、オファーやメンバーなどのビジネスルールを伝えるのに素晴らしい仕事をしています。彼は、不変という言葉を数回言及しています(これは、契約ベースのドメインモデリングです)。.netコードは、正式なビジネスルールとは何かをよりよく伝えることを望んでいます。それらは変更され、それらを維持する必要があるからです。
Fuhrmanator

6

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

たとえば、特定の作業項目が特定のステータスのみを持つことができる、またはN個のステータスのみを持つことができるという要件がある場合、それはドメインロジックであり、いずれかWorkItemまたはStatusHistoryメソッドの一部である必要があります。

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

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

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


良い点。AddStatusUpdateメソッドは、DataまたはInfrastrctureの別のプロジェクトのどこに属しますか?WorkItemに理論的に属する可能性のある動作の例は何ですか?擬似コードまたはモックアップは大歓迎です。私の例は実際には読みやすくするために単純化されています。他にもエンティティがあります。たとえば、AddStatusUpdateには追加の動作があります。実際にはステータスカテゴリ名を受け取り、そのカテゴリが存在しない場合はカテゴリが作成されます。
RJB

@RJB先ほど言ったように、AddStatusUpdateはドメインを使用しているコードです。したがって、ドメインクラスを使用するWebサービスまたはアプリケーションのいずれかの種類。そして、私が言ったように、OOPドメインモデルの真の利点を示すには、プロジェクト全体を非常に複雑にする必要があるため、モックアップや擬似コードを期待することはできません。
陶酔

5

WorkItemに関連付けられたビジネスロジックを "脂肪サービス"にカプセル化することは、私が主張する固有のアンチパターンであるという前提は必ずしも必要ではありません。

貧血領域モデルに関するあなたの考えに関係なく、基幹業務.NETアプリケーションに典型的な標準パターンとプラクティスは、さまざまなコンポーネントで構成されるトランザクションレイヤードアプローチを推奨します。ドメインモデルからビジネスロジックを分離することを推奨し、他の.NETコンポーネントや異なる技術スタックや物理層のコンポーネント間で共通のドメインモデルの通信を促進します。

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

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


1
1)ビジネスモデルをドメインモデルから分離するにはどうすればよいですか?これは、このビジネスロジックが存在するドメインです。そのドメイン内のエンティティは、そのビジネスロジックに関連付けられた動作を実行しています。現実の世界にはサービスがなく、ドメインの専門家の頭の中にも存在しません。2)あなたと統合することを希望する任意のコンポーネントは、そのニーズがあるため、独自のドメインモデルを構築する必要があります異なり、それがされます、あなたのドメインモデル上の異なる見解を持っています。共有できるドメインモデルを1つ作成できるのは、長年の使命です。
ステファンビリエット

1
@StefanBillietこれらはユニバーサルドメインモデルの誤fallについての良い点ですが、以前にこれをやったように、より単純なコンポーネントとコンポーネントの相互作用では可能です。私の意見では、ドメインモデル間の変換ロジックは多くの退屈で定型的なコードを作成する可能性があり、安全に回避できる場合、それは適切な設計選択になる可能性があります。
maple_shaft

1
率直に言って、私が唯一の良い設計選択は、ビジネスの専門家が推論できるモデルだと思います。そのドメイン内の特定の問題を解決するためにビジネスが使用するために、ドメインのモデルを構築しています。ドメインエンティティからサービスに振る舞いを分割すると、関係者全員にとって困難になります。ドメインの専門家の言うことを、現在の会話にほとんど似ていないサービスコードに常にマップする必要があるからです。私の経験では、ボイラープレートを入力するよりも多くの時間を失います。もちろん、ボイラープレイスのコードを回避する方法がないというわけではありません。
ステファンビリエット

@StefanBilliet完璧な世界では、ビジネスの専門家が開発者と座る時間があるという点であなたに同意します。ソフトウェア業界の現実は、ビジネスの専門家がこのレベルに関与することに時間や関心を持っていないか、さらに悪いことですが、開発者は曖昧なガイダンスのみでそれを理解することが期待されています。
maple_shaft

本当ですが、それはその現実を受け入れる理由ではありません。このような追求を続けることは、開発者の時間(そしておそらく評判)と顧客のお金を無駄にすることです。私が説明したプロセスは、時間をかけて構築する必要がある関係です。多くの労力がかかりますが、より良い結果が得られます。「ユビキタス言語」がDDDの最も重要な側面であるとしばしば考えられる理由があります。
ステファンビリエット

5

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

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の例です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.