私は最近この質問をしました: コンストラクタ内の検証
DDDアプリで検証を配置する場所を決定しようとしています。私はそれがすべての層で行われるべきだと思います。
私は現在ドメインモデルに集中しています。次のようなSetterメソッドで検証が行われることを期待していました: https://lostechies.com/jimmybogard/2016/04/29/validation-inside-or-outside-entities/
検証を行う「最良の」方法は何ですか。私はDDD純粋主義者の観点から話しています。 「最善の」方法は、必ずしもすべての状況で最も実用的な方法であるとは限らないことを理解しています。
また、必ずしもDDDが問題を解決するための最良のアプローチであるとは限りません。私はこの特定の領域で私の考えを改善しようとしています。
オプションは次のとおりです。
1)ここで説明するセッター内の検証: https://lostechies.com/jimmybogard/2016/04/29/validation-inside-or-outside-entities/
2)ここで説明するIsValidメソッド: http://enterprisecraftsmanship.com/2016/09/13/validation-and-ddd/
3)ここで説明するように、アプリケーションサービスレイヤーで有効性を確認します。 http://enterprisecraftsmanship.com/2016/09/13/validation-and-ddd/
4)ここで説明されているTryExecuteパターン: http://enterprisecraftsmanship.com/2016/09/13/validation-and-ddd/
5)Execute/CanExecuteパターン(ここで説明): http://enterprisecraftsmanship.com/2016/09/13/validation-and-ddd/
私はNHibernateを使用しているため、ドメインオブジェクトのセッターには、保護されたセットの可視性が必要です。
いろいろな形で出てきそうな興味深い質問です。
最善のアプローチは、無効な状態であるオブジェクトの概念を許可することであると私は考えています。
その理由は、オブジェクトの検証ルールは通常、適切に設定されていないためです。これらは、時間の経過とともに変化する場合や、操作によって異なる場合があります。したがって、以前に作成、入力、および永続化したオブジェクトは、現在無効であると見なされる可能性があります。
セッターまたはコンストラクターの検証チェックがある場合、データベースからこれらのエンティティを取得しようとしたり、古い入力を再処理したりすると、アプリケーションでエラーが発生するという大きな問題があります。
さらに、ビジネスルールに単純なyes/no検証がほとんど組み込まれているとは思いません。あなたのドメインがケーキを販売していて、川の南を配達しないが、誰かがあなたにそれを行うために100万ポンドを提供する場合。次に、特別な例外を作成します。
何百万ものアプリケーションを処理していて、フィールドに含めることができる文字に関する厳密なルールがある場合、おそらく不良フィールドを修正するプロセスがあります。悪いフィールドを受け入れられないようにしたくないので、ドメインを通る別のパスをたどるだけです。
したがって、コード内で非常に厳格で、コンストラクターが例外をスローするために「無効な」データが存在できない場合、「何人がフォームに間違って記入したか」などの質問に答えるのは難しいでしょう。
データを許可し、操作を失敗させます。この方法で、操作のデータまたはルールを調整して、再実行できます。
例:
public class Order
{
public string Id {get;set;}
public string Address {get;set;}
public void Deliver()
{
//check address is valid for delivery
if(String.IsNullOrWhiteSpace(this.Address))
{
throw new Exception("Address not supplied");
}
//delivery code
}
}
したがって、ここでは空のアドレスに配信することはできません。ドメインオブジェクトOrderを使用すると、空白のアドレスを入力できますが、その注文を配信しようとすると例外がスローされます。
アプリケーション、たとえば、キューワーカーがキューに格納されたjsonデータからの注文を処理し、「無効な」注文に遭遇したとします。
{
"Address" :""
}
コンストラクター、またはAddressのセッターには検証チェックがないため、Orderオブジェクトを作成できます。ただし、Deliverメソッドが呼び出されると、例外がスローされ、アプリケーションはアクションを実行できます。例えば
public class QueueWorker
{
public void ProcessOrder(Order o)
{
try
{
o.Deliver();
}
catch(Exception ex)
{
Logger.Log("unable to deliver order:${o.Id} error:${ex.Message}");
MoveOrderToErrorQueue(o);
}
}
}
アプリケーションは引き続き無効な注文を処理し、エラーキューに移動し、IDにアクセスし、エラーを報告します。ただし、配信操作には、空白のアドレスへの配信を処理する方法のドメインロジックが含まれています。
ドメインロジックが後でさらに要件を変更した場合:
public class Order
{
public string Id {get;set;}
public string Address {get;set;}
public void Deliver()
{
//check address is valid for delivery
if(String.IsNullOrWhiteSpace(this.Address))
{
throw new Exception("Address not supplied");
}
if(Address.Contains("UK"))
{
throw new Exception("UK orders not allowed!");
}
//delivery code
}
}
その後、英国の住所が許可されたときに生成されたキューの注文を処理して、期待どおりの結果を得ることができます。
私の回答を@VoiceOfUnReasonの回答と比較するのは興味深いことです。
簡単にするために、またアドレスとは何であるかの絶対的な定義がないため、文字列Addressを使用しました。しかし、より絶対的な定義がある場合、彼の預金には常に通貨があると言います。通貨のない預金について話すことは無意味です。
その場合、はい。通貨を指定していない限り、存在できない新しい値タイプを定義できます。コード内の潜在的なエラーのロードは単に不可能になります。
しかし、あなたはそれが基本的なことを確認する必要があります。そうでなければ、後でトラブルを求めています!
レビューに適した検索用語は「原始的な強迫観念」でしょう
DDDアプリで検証を配置する場所を決定しようとしています。
通常の答えは、値オブジェクトのコンストラクタ/ファクトリに検証を置くことです。
これが私が考えた方法です。値オブジェクトの目的の1つは、ドメインの動作を基礎となるデータ表現から分離することです。
素朴な例。銀行口座を考えてみましょう
Account {
int currentBalance;
void deposit (int amount) {
int workingBalanace = currentBalance;
workingBalance += amount;
currentBalance = workingBalance;
}
}
このエンティティのデザインは、intとしてのバランスの基礎となる表現と密接に結び付いています。しかし、その結合は実装の偶然であり、根本的なドメインの問題ではありません。 Int
is not銀行ドメインのユビキタス言語から取られました。
つまり、ビジネスロジックの実装方法を変更する必要なく、メモリ内のデータをどのように表現するかについて決定を変更するができるはずです。
この追加の間接層を持つ同じ単純なモデルは、次のようになります。
Account {
Balance currentBalance;
void deposit (Deposit amount) {
Balance workingBalance = currentBalance;
workingBalance = workingBalance.add(amount);
currentBalance = workingBalance;
}
}
したがって、アカウントは基になる表現についてまったく気にしません。正しいポスト条件をサポートする型を渡すだけです。
預金が準拠していることを確認するために必要な検証は、境界に近づきます。
したがって、検証がすべてのレイヤーで発生するとは言いません。境界で発生します。 1つの境界は、外界からのメッセージをドメインの概念に変換する場所であり、もう1つの境界は、永続ストアからのメッセージをドメインの概念に変換する場所です。
ただし、境界内に入ると、検証が行われたという事実をキャプチャしないことを選択しない限り、検証を繰り返す必要はありません。
ドメインクラスにプリミティブ型を渡すべきではないと言っていますか?
結構です。私が言っているのは、ドメインモデルの値オブジェクトは、エンティティと基になるデータ表現の間の間接層を提供するということです。
上記の例では、Deposit
をAccount
に渡します。 Deposit
はどこから来たのですか?これは、ドメイン内の値型であり、おそらく渡されたメッセージ(バイト)を取得して、より具体的なドメインコンセプトに変換することによってインスタンス化されます。
Deposit from(int amount) {
// ...
}
逆変換もどこかに存在する可能性が高いため、(リレーショナルデータベースのような)汎用アプライアンスを使用してデータを保存できます。
int from(Deposit deposit) {
// ...
}
腐敗防止レイヤーを考えています。それはあなたが話していることですか?
非常に似たアイデア。この場合の「レガシーシステム」は、データベースではなく、messagingです。
しかし、私の考えでは、重要なアイデアは、基になる表現(データ)から概念(ドメインモデルによって操作される値オブジェクトのインターフェイス)を分離することです。
あなたの質問は検証のいくつかの異なる概念を混同しているようですので、それらをばらばらにしてみましょう:
ビジネスとドメインの機能として検証を行うことができます。ドメインレベルで検証するという概念は、ビジネスの顧客、クライアント、サプライヤーとのやり取りや、ビジネスが行うアクション(例:このレベルでは、自動化のためではなくても手動で行われる検証について話しています。
コードのモジュール内で検証を行うことができます。これにより、コードが処理するようにプログラムされていないことや予期しないことをコードが確認できるようになります。このレベルでは、自動化/ソフトウェアアーキテクチャの内部構造による検証について話します。
誰もが言っていることは、ドメインの検証は、コンピュータードメインレイヤーの交差やエンティティのコンテンツではなく、ビジネスドメインの状態遷移で実行する必要があるということです。
つまり、ビジネス目的で検証してみましょう。ビジネスドメインレベルで状態遷移を達成するために検証してみましょう。ビジネス機能を達成するために検証します。
オブジェクト構築、セッターアクセス、レイヤークロッシングなどのコンピュータードメインソフトウェア実装の概念を目的としたビジネス検証は行わないでください。DDDはコードの記述方法やコードを知らないため、DDD自体にはこれらがありません。絶対必要です。
特に、タイプ(2)の検証で、ビジネスドメインレベルには存在しない制限(ビジネス用ソフトウェアの操作)を導入できないようにします。