貧血ドメインモデルを使用することの長所と短所を知りたいです(以下のリンクを参照)。
回答:
長所:
短所:
「貧血ドメインモデル」がアンチパターンであるのに、なぜこれを実装するシステムがこれほど多いのでしょうか。
いくつかの理由があると思います
1.システムの複雑さ
単純なシステム(インターネット上にあるほとんどすべての例とサンプルコード)で、実装したい場合:
注文への製品の追加
この機能を注文しました
public void Order.AddOrderLine(Product product)
{
OrderLines.Add(new OrderLine(product));
}
素晴らしく、スーパーオブジェクト指向。
ここで、製品が在庫に存在することを検証し、存在しない場合は例外をスローする必要があることを確認する必要があるとしましょう。
注文を在庫に依存させたくないので、実際にはもう注文できません。サービスを開始する必要があります。
public void OrderService.AddOrderLine(Order order, Product product)
{
if (!InventoryService.Has(product)
throw new AddProductException
order.AddOrderLine(product);
}
IInventoryServiceをOrder.AddOrderLineに渡すこともできます。これは別のオプションですが、それでもOrderはInventoryServiceに依存します。
Order.AddOrderLineにはまだいくつかの機能がありますが、通常はOrderスコープに制限されていますが、私の経験では、Orderスコープ外のビジネスロジックがはるかに多くあります。
システムが単なる基本的なCRUD以上のものである場合、ロジックの大部分はOrderServiceにあり、Orderにはほとんどありません。
2.開発者のOOPに対する見方
どのロジックがエンティティに適用されるべきかについて、インターネット上で多くの白熱した議論があります。
何かのようなもの
Order.Save
Orderは自分自身を保存する方法を知っている必要がありますか?そのためのリポジトリがあるとしましょう。
Orderは注文明細を追加できますか?簡単な英語で理解しようとしても、あまり意味がありません。ユーザーが商品を注文に追加するので、User.AddOrderLineToOrder()を実行する必要がありますか?それはやり過ぎのようです。
OrderService.AddOrderLine()はどうですか。今ではちょっと意味があります!
OOPについての私の理解は、カプセル化のために、関数がクラスの内部状態にアクセスする必要があるクラスに関数を配置するということです。Order.OrderLinesコレクションにアクセスする必要がある場合は、Order.AddOrderLine()をOrderに配置します。このようにして、クラスの内部状態が公開されることはありません。
3.IoCコンテナ
IoCコンテナを使用するシステムは、通常、完全に貧血です。
これは、インターフェイスを持つサービス/リポジトリをテストできますが、それらすべてにインターフェイスを配置しない限り、ドメインオブジェクトを(簡単に)テストできないためです。
「IoC」は現在、すべてのプログラミング問題の解決策として賞賛されているため、多くの人が盲目的にそれに従い、このようにして貧血ドメインモデルになってしまいます。
4. OOPは難しく、手順は簡単です
私はこれについて少し「知識の呪い」を持っていますが、DTOとサービスを持っている新しい開発者にとってはリッチドメインよりもはるかに簡単であることがわかりました。
おそらく、リッチドメインでは、ロジックを配置するクラスを知ることがより難しいためです。新しいクラスをいつ作成するのですか?どのパターンを使用しますか?等..
ステートレスサービスでは、最も近い名前のサービスでそれを叩くだけです。
これに続いて、私の頭の中で非常に長い間考えがありました。「OOP」という用語は、本来意図されていない意味を持っていると私は信じています。よく知られているように、アナグラムは「オブジェクト指向プログラミング」を意味します。もちろん、焦点は「指向」という言葉にあります。「オブジェクト必須プログラミング」を意味する「OMP」ではありません。ADMとRDMはどちらもOOPの例です。それらは、オブジェクト、プロパティ、メソッドインターフェイスなどを利用します。ただし、ADMとRDMには、カプセル化の選択方法に違いがあります。それらは2つの異なるものです。ADMが悪いと言うことはOOPは正確なステートメントではありません。代わりに、さまざまなレベルのカプセル化に対して異なる用語が必要になるかもしれません。さらに、アンチパターンという用語は好きではありませんでした。これは通常、反対のグループのメンバーによって何かに割り当てられます。ADMとRDMはどちらも有効なパターンであり、単純に異なる目標を念頭に置いており、異なるビジネスニーズを解決することを目的としています。DDDを実践している私たちの人々は、少なくともこれを高く評価し、ADMの実装を選択した人々を非難することによって他のレベルに落ちてはなりません。ただ私の考え。
「これはアンチパターンなので、他の開発者は、オブジェクト指向設計の概念を理解しているかどうかを尋ねます。」
「貧血ドメインモデルはアンチパターンです。アンチパターンには長所がありません。」
貧血ドメインモデルがアンチパターンであるかどうかは意見の問題です。マーティン・ファウラーはそうだと言います、OOを裏返しに知っている多くの開発者はそうではないと言います。意見を事実として述べることはめったに役に立ちません。
たとえそれがアンチパターンであると広く受け入れられていたとしても、それでも(比較的少ないとはいえ)いくらか上向きになる可能性があります。
the chances are it would still have some (though relatively little) upside.
それなら名前を付けてください!仮説を立てる代わりに、少なくとも1つ!
ファウラーの主な異議は、次の意味で、ADMはオブジェクト指向ではないということだと私には思えます。他のコードによって操作されるパッシブデータ構造を中心に「ゼロから」システムを設計する場合、これは確かにオブジェクト指向設計というよりも手続き型設計のような匂いがします。
この種のデザインを生み出すことができる力が少なくとも2つあることをお勧めします。
新しいシステムを作成するために、オブジェクト指向環境で作業する必要があるとまだ手続き上必要であると考えている(またはできると想定している)設計者/プログラマー、および
(言語に関係なく)OO以外の方法で設計されたレガシーシステムにサービスのような「顔」を配置するために作業している開発者。
たとえば、既存のCOBOLメインフレームアプリケーションの機能を公開するための一連のサービスを構築している場合、内部COBOLデータ構造を反映しない概念モデルの観点からサービスとインターフェイスを定義できます。ただし、サービスが新しいモデルをレガシーデータにマッピングして、既存の非表示の実装を使用する場合、新しいモデルは、ファウラーの記事の意味で「貧血」である可能性があります。たとえば、TransferObjectスタイルの定義のセットです。そして実際の行動のない関係。
この種の妥協は、理想的に純粋なOOシステムが既存の非OO環境と相互作用しなければならない境界で非常によくあることです。
チームがリッチドメインモデル(RDM)を構築し、それを長期間維持することができない、または望まない場合は、貧血ドメインモデル(ADM)が適しています。RDMで勝つには、システムで使用される主要な抽象化に注意を払う必要があります。どの開発グループでも、そのメンバーの半分以下、おそらく10分の1だけが抽象化に対応していることを理解してください。この幹部(おそらく1人の開発者のみ)がグループ全体の活動に対する影響力を維持できない限り、RDMはエントロピーに屈します。
そして、エントロピーRDMは、特定の方法で痛いです。その開発者は厳しい教訓を学びます。彼らには生きる歴史がないので、最初は彼らは彼らの利害関係者の期待に応えることができるでしょう。しかし、彼らのシステムがより複雑になると(複雑ではない)、それはもろくなるでしょう。開発者はコードを再利用しようとしますが、新しいバグを引き起こしたり、開発を後戻りしたりする傾向があります(したがって、見積もりを超過します)。
対照的に、ADM開発者は、新しい機能のために多くのコードを再利用することを期待しないため、自分自身に対する期待を低く設定します。時間が経つにつれて、彼らは多くの矛盾のあるシステムを持つでしょうが、それはおそらく予期せず壊れることはないでしょう。RDMが成功した場合よりも市場投入までの時間が長くなりますが、利害関係者がこの可能性を認識している可能性はほとんどありません。
「(言語に関係なく)非OO方式で設計されたレガシーシステムにサービスのような「顔」を置くために取り組んでいる開発者。」
多くのLOBアプリケーションについて考える場合、これらのレガシーシステムは多くの場合、同じドメインモデルを使用しません。アネミックドメインモデルは、サービスクラスでビジネスロジックを使用してこれを解決します。このすべてのインターフェイスコードをモデル内に配置することもできますが(従来のOOの意味で)、通常はモジュール性が失われます。
貧血ドメインモデルの記事に最初に出くわしたとき、私は「聖なるs ***、それが私がしていることです。ホラー!」と思いました。私は忍耐強く、Eric Evanの本への参照に従い、良い例であると考え、ソースをダウンロードしました。「貧血ドメインモデルを使用しない」とは、「サービスクラスを使用しない、メディエーターを使用しない、戦略を使用しない」、または「操作対象のクラスにロジックを配置する」という意味ではないことがわかります。
DDDの例には、サービスクラス、XyzUpdaters、シングルトン、およびIoCがあります。
私は貧血ドメインモデルが正確に何であるかについて混乱したままです。「見ればわかる」と期待しています。今のところ、私は良いデザインの前向きな例に満足しています。
ADMを備えた「成熟した」システムで作業したことで、少なくとも、この質問に対していくつかの逸話的なフィードバックを提供できると感じました。
1)カプセル化の欠如
ADMを備えたライブシステムでは、たとえば 'obj.x = 100;と書く可能性があります。obj.save '、これがビジネスロジックに違反している場合でも。これにより、不変条件がオブジェクトでモデル化された場合には発生しない多くのバグが発生します。これらのバグの重大性と蔓延性が、ADMにとって最も深刻なネガティブであると私は感じています。
ここで、これが機能ソリューションであり、ADMの手順ソリューションが大幅に異なり、他の人がADMと機能ソリューションの表面の類似性に引き付けた可能性のある類似点は偶発的であることをここで指摘することが重要だと思います。
2)コードの膨張
ADMで生成されるコードの量は、OOP / RDMソリューションが作成するコードの5〜10倍であると推定しています。これは、おそらく50%がコードの繰り返し、30%が定型コード、20%がRDMの欠如から生じる問題の解決または解決によって説明されます。
3)ドメインの問題に対する理解が不十分
ADMとドメインの問題に対する理解の欠如は、いくぶん密接に関連しています。ナイーブなソリューションが発生し、既存のDMで要件をサポートすることが難しいため、要件は十分に考慮されていません。また、開発期間が長く柔軟性がないため、ADMはビジネスイノベーションの大きな障壁になります。
4)メンテナンスの難しさ
ドメインの概念が単なるコピーアンドペーストの再実装ではない可能性があることを考えると、ドメインの概念が表現されるすべての場所で変更されるようにするには、ある程度の厳密さが必要です。これにより、同じバグが何度も調査および修正されることがよくあります。
5)オンボーディングの難易度の増加
RDMの利点の1つは、ドメインのより迅速な理解を可能にする概念のまとまりであると思います。ADMを使用すると、概念が断片化されて明確性が失われる可能性があるため、新しい開発者が習得するのが難しくなります。
また、ADMの運用サポートコストをRDMのコストよりも高くしたいと思いましたが、これはいくつかの要因によって異なります。
他の人が指摘しているように、RDMの利点については、DDD(Greg Evans、Vince Vaughn、およびScott Millett)を参照してください。
これは、ほとんどのアンチパターンと同じプロです。多くの人を長時間忙しくさせることができます。マネージャーはより多くの人を管理するほど報酬が高くなる傾向があるため、改善しないという強いインセンティブがあります。
Eric Pの回答と上記の他のいくつかの回答に沿って、ADMの主な欠点は、OODが失われること、特にドメインコンセプトのロジックとデータをまとめて、実装の詳細が隠されていることです。 APIは豊富な場合があります。
エリックはさらに、注文にアイテムを追加する前に在庫をチェックするなど、ドメインクラスを操作するロジックに必要な情報がドメインクラスの外部にあることが多いことを指摘します。ただし、その答えがこの包括的なロジックを保持するサービスレイヤーなのか、それともオブジェクト設計の一部として処理する方が適切なのかは疑問です。 誰かがInventoryオブジェクト、Productオブジェクト、およびOrderオブジェクトについて知っている必要があります。おそらく、それは単にOrderSystemオブジェクトであり、Inventoryメンバー、Ordersのリストなどがあります。これはサービスとそれほど変わらないように見えますが、概念的にはより一貫性があると思います。
または、次のように見てください。内部クレジット残高を持つユーザーがいる可能性があり、User.addItemToOrder(item)が呼び出されるたびに、アイテムの価格を取得し、追加する前にクレジットをチェックします。これは妥当なOOのようです。設計。これをService.addItemToUserOrder(user、item)に置き換えることで何が失われるのか正確にはわかりませんが、何が得られるのかもわかりません。損失は、コードの余分な層に加えて、より不格好な書き方と、基礎となるドメインモデルの強制的な無知であると思います。
システムの複雑さとバリエーションの粒度が大きくなるにつれて、適切に設計されたメッセージパッシングオブジェクトモデルによって提供されるインターフェイスポイントのカプセル化と統合により、広範囲にわたるリファクタリングなしで重要なコードを変更および維持することがはるかに安全になることに注意してください。
ADMによって作成されたサービスレイヤーは、確かに実装が簡単ですが(比較的考えが少なく、分散型のインターフェイスポイントが多いため)、稼働中の成長中のシステムを変更するときに問題が発生する可能性があります。
また、すべてのケースでドメインモデルが必要なわけではないことも付け加えておきます(ADMモデルは言うまでもありません)。データ駆動型であり、アプリケーション全体のロジック/ビジネスルールに依存しない、より手続き型/機能的なスタイルのタスクを使用する方がよい場合があります。
アプリ全体の長所と短所を決定しようとしている場合は、コードを1行書き始める前に、特定のアプリケーションでそれぞれがどのように見えるかを最初に設計することが重要だと思います。両方のスタイルでアプリケーションをCRCまたはワイヤーフレーム化したら、一歩下がって、どちらがより理にかなっており、アプリケーションにより適しているかを判断します。
また、どちらを維持するのが簡単になるかを前もって考えてください...
それはより良い予測可能性を与えます。特にプロジェクトに時間と材料が支払われる場合、そのようなマネージャー。すべての変更は多くの作業を意味するため、困難な作業は多くの反復作業の背後に隠れることがあります。適切に設計されたDRYシステムでは、常に新しいことをしているため、予測可能性は非常に悪くなります。
ドメイン駆動設計に関するEricEvansの本を最初に読んだ後、それが優れたドメインモデルクラスを設計するための単なる戦術パターンの集まりではないことを本当に理解していませんでした。
トピックについてさらに学び、戦略的パターンも使用した後、私はようやく、最初は解決しようとしているビジネス上の問題を深く理解することがすべてであると理解し始めました。
その後、集約、エンティティなどの戦術パターンを適用するためにシステムのどの部分が適合するかを決定できます。(貧血モデルではなく)いわゆるリッチドメインモデルとともに、、リポジトリます。しかし、これらのパターンから利益を得るには、システムのその部分のビジネスロジックに関して十分な複雑さが必要です。
したがって、目前の問題の解決策を実装する場合は、最初に、この特定の問題にCRUDベースのアプローチを使用するか、前述の戦術パターンとともにリッチドメインモデルに投資する方がよいかどうかを判断する必要があります。
CRUDの方が理にかなっている場合、たとえば、複雑なビジネスロジックがなく、ロジックのほとんどがドメインモデルを実装するデータの変換、転送、永続化に関係している場合、不必要なやり過ぎになる可能性があります。これは、実行する作業がそれほど多くないことを意味するのではなく、単に、最も多くの実装作業を生み出すのはビジネスルールではありません。しかし、この場合、そのようなものはありませんドメインモデルがまったくないという理由だけで貧血ドメインモデルはありません。むしろ表示されるのは、DTO(データ転送オブジェクト)やDAOなどです。(データアクセスオブジェクト)およびデータを操作するサービスクラス。また、対応する操作は、データをある表現から別の表現に変換し、ビジネスロジックをほとんどまたはほとんど使用せずにデータを移動することに大きく関係しています。
あると判断した場合 ドメインモデルに投資するよりも時間の経過とともに変化する複雑なビジネスロジックがたくさん、私の経験からすると良い考えです。その理由は、コードを介してビジネスパースペクティブを表現しやすくなり、ビジネスドメインとそのルールを反映する対応する操作を理解しやすくなるためです。これは、すべてのユースケースにドメインモデルクラスが存在する必要があるという意味ではありません。たとえば、変更および永続化する状態がない場合、純粋関数のように実装されるドメインロジックを含むドメインサービスのみが存在する可能性もあります。
ただし、ビジネスドメインで目的と意味を持つ、変更および永続化する状態もある場合は、状態とこの状態を変更する動作をカプセル化する必要があります。それでは、誰も簡単にビジネスルールを回避することができず、それによって重大な失敗とともに無効な状態につながります。いわゆる貧血ドメインモデルは、しばしばそのような問題の原因です。これは、さまざまなコンポーネントが同じ「貧血」ドメインモデルクラスで動作し、そのビジネスエンティティの全体的な不変条件を気にせず、知らずに、状態の一部をチェックして状態の一部を変更するコードを見る場合によくあります。これをアンチパターンと呼ぶ必要はありませんが、それを理解することが重要です。上記の問題に加えて、DDDベースのアプローチではリッチドメインモデルの多くの利点が失われます。動作とそのデータが同じクラスに配置されるドメインモデルを使用する場合、そのクラスの操作を呼び出すさまざまな「クライアント」が多数存在する可能性もありますが、ビジネスエンティティのビジネス不変条件が次のように順守されることを気にする必要はありません。ドメインモデルクラスは常にそれを処理し、無効な操作について「クライアント」に通知したり、セーフティネットとして例外をスローしたりすることもできます。
だから結論として、私はそれが重要だと思います貧血のドメインモデルクラスと(などのDTOまたはDAOをなど)のクラスのようなデータ構造混同しません。注意深く意図的に選択されたCRUDベースのアプローチでは、複雑なビジネスロジックが少なすぎるため、ドメインモデルを使用しようとする利点はありません。
よる貧血ドメインモデル私は、私はむしろ近いこれらのロジックが変更されたデータにする必要があります別のコンポーネントに分散され、複雑なビジネスロジックとビジネスルールがたくさんあることがわかります、そこからコードを参照することになります。
途中で私が学んだ別の教訓もあります。ステーキ所有者が日常業務で使用しているのと同じビジネス言語(ユビキタス言語とも呼ばれます)をコードで使用しようとすると、理解に関する多くの利点がすでに得られます。ビジネスドメインとコードの可読性の向上により、CRUDベースのアプローチとドメインモデルベースのアプローチのどちらを使用する場合でも、非常に役立ちます。
Michaelの答えを拡張するために、私はそのコードがどこに行くべきかが(かなり)明確であると思っていました:注文と在庫の間の相互作用を処理する専用のメディエーターに。
私のPOVから、ドメインに関する重要なことは、isInThisState()
メソッドなどの単純なテスト動作を保持する必要があるということです 。私の経験では、これらはほとんどの企業のサービスの涙(原文のまま:))にも散在しており、コピーまたはコピーされたものは際限なく書き直されています。これらはすべて、標準の結束規則に違反します。
私の見解では、アプローチは、実用的である限り多くのビジネス行動を保持するDMを目指し、残りを明確に指定された領域に配置することです(つまり、サービスではありません)。
私のチームは個人的にADMを好みます。ドメインの特定の部分を表す一連のビジネスオブジェクトがあります。これらのオブジェクトをデータベースに保存するためにサービスを使用します。私たちのビジネスオブジェクトにはメソッドがありますが、これらのメソッドはその内部状態を操作するだけです。
RDMよりもADMを使用することの利点は、オブジェクトをdbに永続化する方法に見ることができます。レガシーコードシステムで作業している開発者は、(新しいシステムからの)ビジネスオブジェクトを使用し、現在のデータアクセスレイヤーを引き続き使用して、これらのオブジェクトをデータベースに永続化できます。RDMを使用すると、レガシーシステムの開発者は、リポジトリオブジェクトをビジネスモデルに挿入する必要があります...これは、現在のデータアクセス層と一致しません。
貧血のドメインモデルは、アンチパターンです。アンチパターンには長所がありません。