web-dev-qa-db-ja.com

リッチドメインモデルと貧血ドメインモデル

貧血ドメインモデルよりもリッチドメインモデルを使用すべきかどうかを判断し、2つの良い例を探しています。

サービスに裏打ちされたAnemic Domain Modelを使用してWebアプリケーションを構築しています->リポジトリ->ストレージ層システム、FluentValidationをBL検証に使用し、すべてのBLをサービス層に配置しています。

私はエリック・エヴァンのDDDの本を読みましたが、彼は(ファウラーなどと一緒に)貧血領域モデルはアンチパターンであると考えているようです。

だから、私は本当にこの問題についての洞察を得たいと思っていました。

また、リッチドメインモデルの優れた(基本的な)例と、それが提供する貧血ドメインモデルに対する利点を本当に探しています。

78
Sam

Bozhidar Bozhanovは、 this ブログ投稿で貧血モデルを支持しているようです。

彼が提示する要約は次のとおりです。

  • ドメインオブジェクトはスプリング(IoC)で管理されるべきではありません。DAOまたはインフラストラクチャに関連するものが注入されるべきではありません。

  • ドメインオブジェクトには、休止状態(または永続化メカニズム)によって設定された依存するドメインオブジェクトがあります。

  • dDDの中心概念であるように、ドメインオブジェクトはビジネスロジックを実行しますが、これにはデータベースクエリやCRUDは含まれず、オブジェクトの内部状態に対する操作のみが含まれます

  • dTOの必要性はめったにありません。ほとんどの場合、ドメインオブジェクトはDTOそのものです(これにより、定型コードを節約できます)

  • サービスはCRUD操作を実行し、メールを送信し、ドメインオブジェクトを調整し、複数のドメインオブジェクトに基づいてレポートを生成し、クエリを実行します。

  • サービス(アプリケーション)レイヤーはそれほど薄くはありませんが、ドメインオブジェクトに固有のビジネスルールは含まれていません

  • コード生成は避けてください。コード生成の必要性を克服し、最終的にはコードの重複をなくすために、抽象化、設計パターン、およびDIを使用する必要があります。

[〜#〜] update [〜#〜]

著者は最近、 this の記事を読みました。この記事では、著者が一種のハイブリッドアプローチに従うことを提唱しています。ドメインオブジェクトは、その状態(完全に貧血のモデルおそらくサービス層で行われます)

48
geoand

違いは、貧血モデルがロジックをデータから分離することです。多くの場合、ロジックは**Service**Util**Manager**Helperなどの名前のクラスに配置されます。これらのクラスはデータ解釈ロジックを実装するため、データモデルを引数として受け取ります。例えば。

public BigDecimal calculateTotal(Order order){
...
}

リッチドメインアプローチは、データ解釈ロジックをリッチドメインモデルに配置することでこれを逆にします。したがって、ロジックとデータを組み合わせると、リッチドメインモデルは次のようになります。

order.getTotal();

これは、オブジェクトの一貫性に大きな影響を与えます。データ解釈ロジックはデータをラップするため(データはオブジェクトメソッドを介してのみアクセスできます)、メソッドは他のデータの状態変化に反応できます->これをビヘイビアと呼びます。

貧弱なモデルでは、データモデルは正当な状態であることを保証できませんが、リッチドメインモデルではできます。リッチドメインモデルはOOカプセル化、情報の隠蔽、データとロジックの統合などの原則を適用するため、貧血モデルはOOの観点からのアンチパターンです。

より深い洞察のために私のブログを見てください https://www.link-intersystems.com/blog/2011/10/01/anemic-vs-rich-domain-models/

45
René Link

私の視点はこれです:

貧血ドメインモデル=オブジェクトにマップされたデータベーステーブル(フィールド値のみ、実際の動作はなし)

リッチドメインモデル=動作を公開するオブジェクトのコレクション

単純なCRUDアプリケーションを作成する場合は、おそらく古典的なMVCフレームワークを備えた貧弱なモデルで十分です。しかし、何らかのロジックを実装したい場合、貧弱なモデルはオブジェクト指向プログラミングを行わないことを意味します。

*オブジェクトの動作は永続性とは関係がないことに注意してください。別のレイヤー(Data Mappers、Repositories e.t.c.)がドメインオブジェクトの永続化を担当します。

36
George

まず、この記事の回答を貼り付けてコピーします http://msdn.Microsoft.com/en-gb/magazine/dn385704.aspx

図1は、基本的にゲッターとセッターを持つスキーマである貧血ドメインモデルを示しています。

Figure 1 Typical Anemic Domain Model Classes Look Like Database Tables

public class Customer : Person
{
  public Customer()
  {
    Orders = new List<Order>();
  }
  public ICollection<Order> Orders { get; set; }
  public string SalesPersonId { get; set; }
  public ShippingAddress ShippingAddress { get; set; }
}
public abstract class Person
{
  public int Id { get; set; }
  public string Title { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string CompanyName { get; set; }
  public string EmailAddress { get; set; }
  public string Phone { get; set; }
}

このより豊富なモデルでは、単に読み書きするプロパティを公開するのではなく、顧客の公開画面は明示的なメソッドで構成されています。

Figure 2 A Customer Type That’s a Rich Domain Model, Not Simply Properties

public class Customer : Contact
{
  public Customer(string firstName, string lastName, string email)
  {
    FullName = new FullName(firstName, lastName);
    EmailAddress = email;
    Status = CustomerStatus.Silver;
  }
  internal Customer()
  {
  }
  public void UseBillingAddressForShippingAddress()
  {
    ShippingAddress = new Address(
      BillingAddress.Street1, BillingAddress.Street2,
      BillingAddress.City, BillingAddress.Region,
      BillingAddress.Country, BillingAddress.PostalCode);
  }
  public void CreateNewShippingAddress(string street1, string street2,
   string city, string region, string country, string postalCode)
  {
    ShippingAddress = new Address(
      street1,street2,
      city,region,
      country,postalCode)
  }
  public void CreateBillingInformation(string street1,string street2,
   string city,string region,string country, string postalCode,
   string creditcardNumber, string bankName)
  {
    BillingAddress = new Address      (street1,street2, city,region,country,postalCode );
    CreditCard = new CustomerCreditCard (bankName, creditcardNumber );
  }
  public void SetCustomerContactDetails
   (string email, string phone, string companyName)
  {
    EmailAddress = email;
    Phone = phone;
    CompanyName = companyName;
  }
  public string SalesPersonId { get; private set; }
  public CustomerStatus Status { get; private set; }
  public Address ShippingAddress { get; private set; }
  public Address BillingAddress { get; private set; }
  public CustomerCreditCard CreditCard { get; private set; }
}
11
Razan Paul

リッチドメインクラスの利点の1つは、任意のレイヤーのオブジェクトへの参照があるたびに、その動作(メソッド)を呼び出すことができることです。また、一緒に共同作業する小さく分散したメソッドを書く傾向があります。貧弱なドメインクラスでは、通常、ユースケースによって駆動される脂肪の手続き型メソッド(サービス層)を記述する傾向があります。通常、リッチドメインクラスに比べて保守性が低くなります。

動作を伴うドメインクラスの例:

_class Order {

     String number

     List<OrderItem> items

     ItemList bonus

     Delivery delivery

     void addItem(Item item) { // add bonus if necessary }

     ItemList needToDeliver() { // items + bonus }

     void deliver() {
         delivery = new Delivery()
         delivery.items = needToDeliver()
     }

}
_

メソッドneedToDeliver()は、ボーナスを含む配信が必要なアイテムのリストを返します。クラス内、別の関連クラス、または別のレイヤーから呼び出すことができます。たとえば、表示にOrderを渡すと、選択したOrderneedToDeliver()を使用して、ユーザーが保存ボタンをクリックする前に確認するアイテムのリストを表示できます。 Orderを永続化します。

コメントへの返信

これは私がコントローラーからドメインクラスを使用する方法です:

_def save = {
   Order order = new Order()
   order.addItem(new Item())
   order.addItem(new Item())
   repository.create(order)
}
_

OrderとそのLineItemの作成は1つのトランザクションで行われます。 LineItemのいずれかを作成できない場合、Orderは作成されません。

私は、次のような単一のトランザクションを表すメソッドを持っている傾向があります。

_def deliver = {
   Order order = repository.findOrderByNumber('ORDER-1')
   order.deliver()       
   // save order if necessary
}
_

deliver()内のすべては、単一のトランザクションとして実行されます。単一のトランザクションで多くの無関係なメソッドを実行する必要がある場合、サービスクラスを作成します。

遅延読み込みの例外を回避するために、JPA 2.1名前付きエンティティグラフを使用します。たとえば、配信画面のコントローラーでは、delivery属性をロードし、repository.findOrderByNumberFetchDelivery()などのbonusを無視するメソッドを作成できます。ボーナス画面では、bonus属性をロードし、deliveryを無視する別のメソッド(repository.findOrderByNumberFetchBonus()など)を呼び出します。私はまだボーナス画面内でdeliver()を呼び出すことができないので、これには学問が必要です。

7
jocki

モノリシックデスクトップアプリを書くのに使用していたとき、リッチドメインモデルを作成し、それらの作成を楽しんでいた。

さて、私は小さなHTTPマイクロサービスを作成しました。貧弱なDTOを含む、可能な限り少ないコードがあります。

DDDとこの貧弱な議論は、モノリシックデスクトップまたはサーバーアプリ時代からのものだと思います。私はその時代を覚えており、貧血モデルは奇妙であることに同意します。大きなモノリシックFXトレーディングアプリを作成しましたが、モデルはありませんでした。本当にひどいものでした。

マイクロサービスでは、豊富な動作を備えた小さなサービスは、おそらくドメイン内の構成可能なモデルおよび集合体です。したがって、マイクロサービスの実装自体は、さらにDDDを必要としません。マイクロサービスアプリケーションがドメインである場合があります。

注文マイクロサービスには、RESTfulリソースとして、またはSOAPなど)で表される機能がほとんどない場合があります。注文マイクロサービスコードは非常に単純な場合があります。

より大きなモノリシックな単一(マイクロ)サービス、特にモデルをRAMに保持するサービスは、DDDの恩恵を受ける可能性があります。

6
Luke Puplett

貧血ドメインモデルはORMおよびネットワーク(すべての商用アプリケーションの生命線)を介した簡単な転送に重要ですが、OOはコードの「トランザクション/処理」部分のカプセル化と単純化に非常に重要です。

したがって、重要なのは、ある世界から別の世界を識別して変換できることです。

Anemicの名前はAnemicUserやUserDAOなどのようなものですので、開発者は使用するのに適したクラスがあることを認識し、none Anemicクラスに適切なコンストラクターを用意します。

User(AnemicUser au)

トランスポート/永続性の貧血クラスを作成するアダプタメソッド

User::ToAnemicUser() 

トランスポート/永続性以外のあらゆる場所でnone Anemic Userを使用することを目指します

0
andrew pate

以下に役立つ例を示します。

貧血

class Box
{
    public int Height { get; set; }
    public int Width { get; set; }
}

非貧血

class Box
{
    public int Height { get; private set; }
    public int Width { get; private set; }

    public Box(int height, int width)
    {
        if (height <= 0) {
            throw new ArgumentOutOfRangeException(nameof(height));
        }
        if (width <= 0) {
            throw new ArgumentOutOfRangeException(nameof(width));
        }
        Height = height;
        Width = width;
    }

    public int area()
    {
       return Height * Width;
    }
}