ドメイン駆動設計、ドメイン オブジェクト、セッターに関する態度
-
10-10-2019 - |
質問
最近、Greg Young のビデオをいくつか見ていて、ドメイン オブジェクトのセッターに対して否定的な態度がある理由を理解しようとしています。ドメイン オブジェクトは DDD のロジックで「重い」ものであるはずだと思っていました。悪い例の良い例とそれを修正する方法はオンラインにありますか?例や説明は何でも良いです。これは CQRS 方式で保存されたイベントにのみ適用されますか、それとも DDD 全体に適用されますか?
解決
私は、他の理由で不変剤についてのロジャー・アルシングの答えを補完するためにこの答えを貢献しています。
意味情報
ロジャーは、プロパティセッターがセマンティック情報を持っていないことを明確に説明しました。 post.publishdateのようなプロパティのセッターを許可することは、投稿が公開されているかどうか、または公開日が設定されているかどうかを確実に知ることができないため、混乱を加えることができます。これが記事を公開するのに必要なものであることを確実に知ることはできません。オブジェクト「インターフェイス」は、その「意図」を明確に表示しません。
ただし、「有効」のようなプロパティは、「取得」と「設定」の両方に十分な意味を持つと信じています。それはすぐに有効にする必要があり、プロパティセッターにセマンティクスが欠落している理由だけで、setactive()またはactivate()/deactivate()メソッドの必要性を見ることができません。
不変
ロジャーはまた、プロパティセッターを介して不変性を破る可能性についても話しました。これは絶対に正しいことであり、「統合された不変値」(ロジャーの例を使用するための三角形の角度として)を読み取り専用プロパティとして提供し、それらをすべて一緒に設定する方法を作成するためにタンデムで機能するプロパティを作成する必要があります。すべての組み合わせを1つのステップで検証します。
プロパティオーダーの初期化/設定依存関係
これは、一緒に検証/変更する必要があるプロパティの問題を引き起こすため、不変剤の問題に似ています。次のコードを想像してください。
public class Period
{
DateTime start;
public DateTime Start
{
get { return start; }
set
{
if (value > end && end != default(DateTime))
throw new Exception("Start can't be later than end!");
start = value;
}
}
DateTime end;
public DateTime End
{
get { return end; }
set
{
if (value < start && start != default(DateTime))
throw new Exception("End can't be earlier than start!");
end = value;
}
}
}
これは、アクセスオーダーの依存関係を引き起こす「セッター」検証の素朴な例です。次のコードはこの問題を示しています。
public void CanChangeStartAndEndInAnyOrder()
{
Period period = new Period(DateTime.Now, DateTime.Now);
period.Start = DateTime.Now.AddDays(1); //--> will throw exception here
period.End = DateTime.Now.AddDays(2);
// the following may throw an exception depending on the order the C# compiler
// assigns the properties.
period = new Period()
{
Start = DateTime.Now.AddDays(1),
End = DateTime.Now.AddDays(2),
};
// The order is not guaranteed by C#, so either way may throw an exception
period = new Period()
{
End = DateTime.Now.AddDays(2),
Start = DateTime.Now.AddDays(1),
};
}
期間オブジェクトの終了日の開始日を変更できないため(「空の」期間であり、両方の日付がデフォルト(datetime)に設定されていない限り - はい、それは素晴らしいデザインではありませんが、あなたは私が取得します平均...)最初に開始日を設定しようとすると、例外がスローされます。
オブジェクト初期剤を使用すると、より深刻になります。 C#は割り当ての注文を保証しないため、安全な仮定を行うことはできません。コードは、コンパイラの選択に応じて例外をスローする場合とそうでない場合があります。悪い!
これは最終的にクラスの設計に問題です。プロパティは両方の値を更新していることを「知る」ことができないため、両方の値が実際に変更されるまで検証を「オフ」することはできません。両方のプロパティを読み取り専用にし、両方を同時に設定する方法(オブジェクト初期化剤の機能を失う)を提供するか、プロパティから検証コードを完全に削除する必要があります(おそらくiSvalidなどの別の読み取り専用プロパティを導入するか、検証する必要なときはいつでも)。
orm "Hydration"*
単純化されたビューでは、水分補給は、持続したデータをオブジェクトに戻すことを意味します。私にとって、これはプロパティセッターの背後にロジックを追加することの最大の問題です。
多くの/ほとんどのORMSは、持続的な値をプロパティにマッピングします。プロパティセッター内のオブジェクト状態(他のメンバー)を変更する検証ロジックまたはロジックがある場合、「不完全な」オブジェクト(1つはまだロードされている)に対して検証しようとします。これは、フィールドが「水分補給」される順序を制御できないため、オブジェクトの初期化の問題に非常に似ています。
ほとんどのORMを使用すると、プロパティの代わりに永続性をプライベートフィールドにマッピングできます。これにより、オブジェクトが水分補給されますが、検証ロジックがほとんどプロパティセッター内にある場合は、ロードされたオブジェクトが有効であることを確認するために他の場所に複製する必要がある場合があります。か否か。
多くのORMツールは、仮想プロパティ(またはメソッド)マッピングをフィールドに使用することにより、怠zyなロード(ORMの基本的な側面!)をサポートするため、ORMがフィールドにマッピングされたレイジーロードオブジェクトが不可能になります。
結論
したがって、最終的には、コードの複製を避けるために、ORMが可能な限り最高のパフォーマンスを可能にし、フィールドが設定されている順序に応じて驚くべき例外を防ぐために、プロパティセッターからロジックを遠ざけることが賢明です。
私はまだこの「検証」ロジックがどこにあるべきかを考えています。オブジェクトの不変な側面はどこで検証しますか?高レベルの検証はどこに置きますか? ORMSのフックを使用して検証を実行しますか(Onsave、Ondelete、...)?などなど。しかし、これはこの答えの範囲ではありません。
他のヒント
セッターはセマンティック情報を運びません。
例えば
blogpost.PublishDate = DateTime.Now;
それは、投稿が公開されたことを意味しますか?または、公開日が設定されていることだけですか?
検討:
blogpost.Publish();
これは、BlogPostを公開する必要があるという意図を明確に示しています。
また、セッターはオブジェクトの不変剤を破る場合があります。たとえば、「三角形」エンティティがある場合、不変は、すべての角度の合計が180度であることです。
Assert.AreEqual (t.A + t.B + t.C ,180);
セッターがある場合、不変剤を簡単に破ることができます。
t.A = 0;
t.B = 0;
t.C = 0;
それで、すべての角度の合計が0である三角形があります...それは本当に三角形ですか?ノーと言います。
セッターをメソッドに置き換えると、不変剤を維持するように強制する可能性があります。
t.SetAngles(0,0,0); //should fail
このような呼び出しは、これがエンティティに無効な状態を引き起こすことを示す例外をスローする必要があります。
したがって、セマンティクスとセマンティクスと、セッターの代わりにメソッドを使用します。
この背後にある理由は、エンティティ自体がその状態を変更する責任があるべきであるためです。エンティティ自体の外側にプロパティを設定する必要がある理由は本当にありません。
簡単な例は、名前を持つエンティティです。パブリックセッターがある場合は、アプリケーションのどこからでもエンティティの名前を変更できます。代わりにそのセッターを削除して、 ChangeName(string name)
名前を変更する唯一の方法となるエンティティに。そうすれば、名前を変更するときに常に実行されるあらゆる種類のロジックを追加できます。これは、名前を何かに設定するだけで、はるかに明確です。
基本的に、これは、州を個人的に維持している間、エンティティの行動を公に公開することを意味します。
元の質問には.NETがタグ付けされているため、エンティティを直接ビューにバインドしたいコンテキストに実用的なアプローチを提出します。
それは悪い習慣であり、おそらくビューモデル(MVVMなど)などが必要であることを知っていますが、一部の小さなアプリでは、iMhoを過剰に散乱させないことは理にかなっています。
プロパティを使用することは、.NETでボックスデータバインディングが機能する方法です。コンテキストは、データバインディングが両方の方法で機能することを規定している可能性があるため、セッターロジックの一部としてinotifypropertychangedとpropertychangedを実装することは理にかなっています。
エンティティは、クライアントが無効な値を設定したときに、壊れたルールコレクションなどにアイテムを追加することができます(CSLAが数年前にその概念を持っていて、まだそうしている可能性があります)。作業単位は、それがそこまで来るべきであれば、無効なオブジェクトを持続することを後に拒否するでしょう。
私は、デカップリング、不変性などを大幅に正当化しようとしています。いくつかの文脈では、よりシンプルなアーキテクチャが求められていると言っているだけです。
セッターは単に値を設定します。そうあるはずではありません "heavy" with logic
.
適切な説明的な名前を持つオブジェクトの方法である必要があります "heavy" with logic
ドメイン自体にアナログがあります。
読むことを強くお勧めします エリック・エヴァンスによるDDDブック と Bertrand Meyerによるオブジェクト指向ソフトウェア構造. 。彼らはあなたがこれまでに必要なすべてのサンプルを持っています。
ここからは逸れるかもしれませんが、設定にはセッター メソッドではなくセッターを使用する必要があると考えています。これにはいくつか理由があります。
a) .Net では意味があります。すべての開発者はプロパティについて知っています。このようにしてオブジェクトに設定を行います。なぜドメインオブジェクトに関してそれから逸脱するのでしょうか?
b) セッターはコードを持つことができます。3.5 より前では、オブジェクトの設定は内部変数とプロパティ シグネチャで構成されていたと思います。
private object _someProperty = null;
public object SomeProperty{
get { return _someProperty; }
set { _someProperty = value; }
}
セッターに検証を入れるのは非常に簡単でエレガントです。IL では、ゲッターとセッターはとにかくメソッドに変換されます。なぜコードが重複するのでしょうか?
上に投稿した Publish() メソッドの例では、私も完全に同意します。他の開発者にプロパティを設定させたくない場合があります。それはメソッドで処理する必要があります。しかし、.Net がプロパティ宣言で必要な機能をすべて提供しているのに、すべてのプロパティに setter メソッドを用意するのは意味があるのでしょうか?
Person オブジェクトがある場合、理由がないのに、そのオブジェクトのすべてのプロパティに対してメソッドを作成する必要はありません。