貧血ドメインモデルに対処するためのテクニック
-
03-07-2019 - |
質問
貧血ドメインモデルと懸念の分離に関する質問をいくつか読みました。貧血のドメイン オブジェクトに対してドメイン ロジックを実行/接続するための最良の手法は何ですか?私の職場ではかなり貧血なモデルがあり、現在「ヘルパー」クラスを使用してドメイン オブジェクトでデータベース/ビジネス ロジックを実行しています。例えば:
public class Customer
{
public string Name {get;set;}
public string Address {get;set;}
}
public class Product
{
public string Name {get;set;}
public decimal Price {get;set;}
}
public class StoreHelper
{
public void PurchaseProduct(Customer c, Product p)
{
// Lookup Customer and Product in db
// Create records for purchase
// etc.
}
}
アプリが購入を行う必要がある場合、StoreHelper を作成し、ドメイン オブジェクトのメソッドを呼び出します。私にとって、顧客/製品がそれ自体をリポジトリに保存する方法を知っていることは理にかなっていますが、おそらくドメイン オブジェクトに Save() メソッドを使用したくないでしょう。Customer.Purchase(Product) のようなメソッドにも意味がありますが、エンティティにドメイン ロジックを追加することになります。
以下に私が出会ったいくつかのテクニックを紹介しますが、どれが良いか悪いかはわかりません。
- Customer と Product は「Entity」クラスを継承し、一般的な方法 (おそらく ORM を使用) で基本的な CRUD 操作を提供します。
- 長所:各データ オブジェクトは自動的に CRUD 操作を取得しますが、その後データベース/ORM に関連付けられます。
- 短所:これでは、オブジェクトに対するビジネス操作の問題は解決されず、また、すべてのドメイン オブジェクトが、適切ではない可能性のある基本エンティティに関連付けられます。
- ヘルパー クラスを使用して CRUD 操作とビジネス ロジックを処理する
- 「純粋なデータベース」操作には DAO を使用し、よりビジネス固有の操作には別個のビジネス ヘルパーを使用するのは理にかなっていますか?
- これには非静的ヘルパー クラスと静的ヘルパー クラスのどちらを使用する方が良いでしょうか?
- 長所:ドメイン オブジェクトはデータベース/ビジネス ロジックに関連付けられていません (完全に貧血です)
- 短所:オブジェクト指向ではなく、アプリケーション コードでヘルパーを使用するのがあまり自然ではありません (C コードのように見えます)
- エンティティに任意のリポジトリに保存するメソッドがあるダブル ディスパッチ手法を使用します。
- 長所:懸念事項をより適切に分離する
- 短所:エンティティには追加のロジックが接続されています (ただし、分離されています)。
- C# 3.0 では、拡張メソッドを使用して、ドメイン オブジェクトに触れることなく CRUD/ビジネス メソッドをアタッチできました。
- これは有効なアプローチですか?長所/短所は何ですか?
- 他のテクニックは?
これに対処するための最良のテクニックは何ですか?私は DDD についてはかなり初心者です (エヴァンスの本を読んでいます - そうすると目が開かれるかもしれません)
解決
Martin Fowlerは、貧血ドメインモデルなど、ドメインモデルについて多くのことを書いています。また、ドメインモデルとデータベースの多くの設計パターンの簡単な説明(およびUMLクラス図)があります。&quotのカタログ;エンタープライズアプリケーションアーキテクチャのパターン" 。
アクティブレコードおよびデータマッパーパターン。問題の説明から、ヘルパークラスにはドメイン/ビジネスルールとデータベース実装の詳細の両方が含まれているようです。
アクティブレコードは、ヘルパーのドメインロジックとデータベースコードを他のドメインオブジェクト( Entity
ベースクラスなど)に移動します。データマッパーは、ヘルパーのドメインロジックをドメインオブジェクトに移動し、データベースコードを別のマップオブジェクトに移動します。どちらのアプローチも、手続き型のヘルパークラスよりもオブジェクト指向です。
Eric Evansの「ドメイン駆動設計」本は素晴らしいです。少し乾きますが、間違いなく価値があります。 InfoQには、" Domain Driven Design Quickly"があります。ミニブックは、エヴァンスの本の良い入門書です。さらに、「ドメイン駆動設計をすばやく」無料のPDFで入手できます。
他のヒント
貧血モデルを回避するには、ヘルパークラスをリファクタリングします。
次のようなロジック:
" Customer.PurchaseProduct(製品製品、支払い支払い)&quot ;,
" Customer.KillCustomer(人殺し、武器兵器)"
" Customer"の中に存在する必要があります。ドメインオブジェクト。
次のようなロジック:
" Customer.IsCustomerAlive()"
" Customer.IsCustomerHappy()"
仕様に従ってください。
次のようなロジック:
" Customer.Create()&quot ;,
" Customer.Update()"
明らかにリポジトリに行く必要があります。
次のようなロジック:
" Customer.SerializeInXml()"
" Customer.GetSerializedCustomerSizeInBytes()"
サービスにアクセスする必要があります。
複雑なコンストラクターは工場に行く必要があります。
それが私の見方です。誰かが私のDDDアプローチについての私の理解をコメントできたらうれしいです。
編集:
時々-貧血ドメインモデル避けるべきではない。
回答を編集して、DDDはパターンの取得と削除に関するものではないことを追加しました。
DDDは私たちの考え方です。
私は常に貧血領域モデルをアンチパターンと考えてきました。顧客が製品を購入することは明らかです。その機能はインターフェースの実装によって生成できます
Interface IPurchase
Purchase(Product);
。したがって、ドメインオブジェクトのいずれかが必要に応じて実装できます。そのようにして、ドメインオブジェクトに機能を導入できます-これはまさにその場所です。
言及されていないアプローチの 1 つは、AOP を使用してデータ アクセスを処理することです。このアプローチを最近使用した例としては (投稿用に大幅に簡略化されていますが)、 Account
を持っていたドメインエンティティ debit
メソッド。アカウントから正常に引き落としを行うために必要なビジネス ロジックをカプセル化します。
注:すべてのコードは AspectJ AOP 表記の Java です...
public boolean debit(int amount) {
if (balance - amount >= 0) {
balance = balance - amount;
return true;
}
return false;
}
適切なリポジトリをアスペクトに挿入した後、ポイントカットを使用してこのメソッドへの呼び出しをインターセプトしました...
pointcut debit(Account account,int amount) :
execution(boolean Account.debit(int)) &&
args(amount) &&
target(account);
...そしていくつかのアドバイスを適用しました:
after(Account account, int amount) returning (boolean result) : debit(account,amount) {
if (result) getAccountRepository().debit(account, amount);
}
私の意見では、これにより関心事がうまく分離され、ドメイン エンティティがアプリケーションのビジネス ロジックに完全に集中できるようになります。