ドメインモデルの検証を配置する場所


38

ドメインモデル検証のベストプラクティスをまだ探しています。ドメインモデルのコンストラクターに検証を入れるのは良いですか?私のドメインモデル検証の例は次のとおりです。

public class Order
 {
    private readonly List<OrderLine> _lineItems;

    public virtual Customer Customer { get; private set; }
    public virtual DateTime OrderDate { get; private set; }
    public virtual decimal OrderTotal { get; private set; }

    public Order (Customer customer)
    {
        if (customer == null)
            throw new  ArgumentException("Customer name must be defined");

        Customer = customer;
        OrderDate = DateTime.Now;
        _lineItems = new List<LineItem>();
    }

    public void AddOderLine //....
    public IEnumerable<OrderLine> AddOderLine { get {return _lineItems;} }
}


public class OrderLine
{
    public virtual Order Order { get; set; }
    public virtual Product Product { get; set; }
    public virtual int Quantity { get; set; }
    public virtual decimal UnitPrice { get; set; }

    public OrderLine(Order order, int quantity, Product product)
    {
        if (order == null)
            throw new  ArgumentException("Order name must be defined");
        if (quantity <= 0)
            throw new  ArgumentException("Quantity must be greater than zero");
        if (product == null)
            throw new  ArgumentException("Product name must be defined");

        Order = order;
        Quantity = quantity;
        Product = product;
    }
}

ご提案ありがとうございます。

回答:


47

ありますMartin Fowler氏によって興味深い記事(私を含む)ハイライト側面ほとんどの人が見落としがちであることをその対象には:

しかし、私は常に人々をつまずかせると思うのは、isValidメソッドが暗示するようなコンテキストに依存しない方法でオブジェクトの妥当性を考えるときです。

検証は、コンテキストにバインドされた何か、通常は実行したいアクションとして考えるのがはるかに便利だと思います。この注文は有効であり、この顧客はホテルにチェックインするのに有効ですか?そのため、isValidのようなメソッドを使用するのではなく、isValidForCheckInのようなメソッドを使用します。

このことから、おそらくすべてのコンテキストで共有されるいくつかの非常に基本的な健全性チェックを除き、コンストラクターは検証を行うべきではありません。

再び記事から:

「アバウト・フェイス」で、アラン・クーパーは、ユーザーが不完全な情報を入力(および保存)するのを防ぐために、有効な状態のアイデアを許可すべきではないと主張しました。数日前、ジミー・ニルソンが取り組んでいる本の草稿を読んでいたときに、このことを思い出しました。彼は、たとえオブジェクトにエラーがあっても、常にオブジェクトを保存できるべきだという原則を述べました。私はこれが絶対的なルールであるべきだとは確信していませんが、人々は本来あるべき以上の貯蓄を妨げる傾向があると思います。検証のコンテキストを考えると、それを防ぐのに役立つ場合があります。


誰かがこれを言ってくれてありがとう。データの90%を含むが何も保存しないフォームは、データを失わないために他の10%を構成することが多いユーザーにとって不公平です。構成されました。同様の問題がバックエンドで発生する可能性があります-データのインポートなど。通常、無効なデータを適切に処理することは、それが発生しないようにすることよりも優れていることがわかりました。
psr

2
@psrデータが保持されない場合、バックエンドロジックも必要ですか?データがビジネスモデルに意味を持たない場合、すべての操作をクライアント側に任せることができます。データが無意味である場合、メッセージ(クライアント-サーバー)を前後に送信するためのリソースも無駄になります。したがって、「ドメインオブジェクトが無効な状態になることを許可しない」という考え方に戻ります。。
ジオC. 14年

2
なぜそんなにあいまいな答えに多くの票があるのだろうか。DDDを使用する場合、一部のデータがINTであるか範囲内にあるかを単純にチェックするルールが存在する場合があります。たとえば、アプリのユーザーに製品の制約を選択させる場合(誰かが私の製品をプレビューできる回数、月の何日間隔)。ここで、両方の制約はintであり、そのうちの1つは0〜31の範囲にある必要があります。これは、非DDD環境ではサービスまたはコントローラーに適合するデータ形式検証のようです。しかし、DDDでは、ドメイン内で有効性を維持する側にいます(その90%)。
ジオC. 14年

2
ドメインを有効な状態に維持するために、上位層にドメインについて多くの情報を強制することは、悪い悪いデザインのようなにおいがします。ドメインは、その状態が有効であることを保証するものでなければなりません。上位レイヤーの肩を動かしすぎると、ドメインが貧弱になり、ビジネスに悪影響を与える可能性のある制約をすり抜けてしまう可能性があります。私が今理解していることは、適切な一般化は、検証を可能な限り永続性に近づけるか、データ操作コードに近づけることです(最終状態に到達するように操作された場合)。
ジオC. 14年

PS承認(何かを行うことを許可されている)、認証(メッセージが適切な場所から来た、または適切なクライアントによって送信された、両方ともapi key / token / usernameまたはその他で識別される)と形式検証を混ぜないまたはビジネスルール。私が90%と言うとき、それらのほとんどがフォーマット検証も含むビジネスルールを意味します。もちろん、フォーマットの検証は上位レイヤーで行うこともできますが、それらのほとんどはドメイン内にあります(EmailAddress値オブジェクトで検証される電子メールアドレス形式も含む)。
地理C. 14年

5

この質問は少し古いという事実にもかかわらず、価値のあるものを追加したいと思います。

@MichaelBorgwardtに同意し、テスト容易性を高めて拡張したいと思います。「レガシーコードを効果的に使用する」では、Michael Feathersがテストの障害について多くのことを語っており、それらの障害の1つがオブジェクトの「構築が困難」です。無効なオブジェクトの構築が可能であるべきであり、ファウラーが示唆するように、コンテキスト依存の妥当性チェックはそれらの条件を識別できるはずです。テストハーネスでオブジェクトを作成する方法がわからない場合は、クラスのテストで問題が発生します。

有効性については、制御システムについて考えたいです。制御システムは、出力の状態を絶えず分析し、出力が設定点から逸脱するときに修正アクションを適用することで機能します。これは閉ループ制御と呼ばれます。クローズドループ制御は本質的に偏差を予期し、それを修正するように動作します。それが実世界の仕組みです。そのため、すべての実際の制御システムは通常、クローズドループコントローラーを使用します。

コンテキスト依存の検証を使用し、オブジェクトを簡単に構築できると、システムを将来的に使いやすくすることができると思います。


1
多くの場合、オブジェクトの構築は難しいように見えます。たとえば、この場合、テスト対象のクラスから継承し、無効な状態でベースオブジェクトのインスタンスを作成できるWrapperクラスを作成することにより、パブリックコンストラクターをバイパスできます。これは、クラスおよびコンストラクターで正しいアクセス修飾子を使用することが重要であり、不適切に使用された場合、テストに実際に有害になる可能性があります。さらに、適切な場合を除いて、「シールされた」クラスとメソッドを避けることは、コードをテストしやすくするのに大いに役立ちます。
P.ロー

4

私はあなたがすでに知っていると確信しているように...

オブジェクト指向プログラミングでは、クラス内のコンストラクター(場合によってはctorと略される)は、オブジェクトの作成時に呼び出される特別なタイプのサブルーチンです。新しいオブジェクトを使用するために準備します。多くの場合、コンストラクターがオブジェクトの最初の作成時に必要なメンバー変数を設定するために使用するパラメーターを受け入れます。クラスのデータメンバーの値を構築するため、コンストラクターと呼ばれます。

c'torパラメーターとして渡されたデータの有効性をチェックすることは、コンストラクターで間違いなく有効です-そうでなければ、無効なオブジェクトの構築を許可する可能性があります。

ただし、これは単なる意見であり、現時点では適切なドキュメントは見つかりません)-データ検証に複雑な操作(非同期操作など-デスクトップアプリを開発する場合のサーバーベースの検証など)が必要な場合は、何らかの種類の初期化または明示的な検証関数を設定し、メンバーnullがc'torでデフォルト値(など)に設定します。


また、コードサンプルに含めた補足事項として...

あなたはさらなる検証(または他の機能を)やっている場合を除きAddOrderLine、私が最も可能性が高い公開したいList<LineItem>プロパティとしてではなく、持っているOrderとして作用するファサード


コンテナを公開する理由 コンテナが何であるかは、上位層にとって重要ですか?AddLineItemメソッドを持つことは完全に合理的です。実際、DDDの場合、これが優先されます。List<LineItem>がカスタムコレクションオブジェクトに変更された場合、公開されたプロパティと、List<LineItem>プロパティに依存するすべてのものは、変更、エラー、例外の対象となります。
IA16年

4

検証はできるだけ早く実行する必要があります。

ドメインモデルまたはその他のソフトウェア記述方法のいずれかのコンテキストでの検証は、検証する内容と現在のレベルの目的にかなう必要があります。

あなたの質問に基づいて、答えは検証を分割することだと思います。

  1. プロパティの検証では、1〜10の範囲が予想される場合など、そのプロパティの値が正しいかどうかをチェックします。

  2. オブジェクトの検証により、オブジェクトのすべてのプロパティが相互に関連して有効であることが保証されます。たとえば、BeginDateはEndDateより前です。データストアから値を読み取り、BeginDateとEndDateの両方がデフォルトでDateTime.Minに初期化されるとします。BeginDateを設定する際、「EndDateより前になければならない」ルールを強制する理由はありません。これはまだYETを適用しないためです。このルールは、すべてのプロパティが設定された後にチェックする必要があります。これは、集約ルートレベルで呼び出すことができます

  3. 検証は、集約(または集約ルート)エンティティでも実行する必要があります。Orderオブジェクトには有効なデータが含まれている可能性があるため、OrderLineもそうです。しかし、ビジネスルールでは、1,000ドルを超える注文は禁止されています。場合によっては、このルールがどのように施行されますか?「量を検証しない」プロパティを追加することはできません。これは悪用につながるためです(遅かれ早かれ、多分あなたも、この「厄介なリクエスト」を邪魔にしないためです)。

  4. 次に、プレゼンテーション層で検証が行われます。失敗することを知って、ネットワーク経由でオブジェクトを送信するつもりですか?または、ユーザーにこの負担をspareしみ、無効な値を入力したらすぐに通知します。たとえば、ほとんどの場合、DEV環境は運用環境よりも遅くなります。特に上司が首を呼吸することで修正される生産上のバグがある場合、「もう1つのテスト実行中にこのフィールドをもう一度忘れました」と通知される前に30秒待機しますか?

  5. 永続性レベルでの検証は、可能な限りプロパティ値の検証に近いものと想定されています。これは、あらゆる種類のマッパーまたは単純な古いデータリーダーを使用するときに、「null」または「無効な値」エラーの読み取りによる例外を防ぐのに役立ちます。ストアドプロシージャを使用するとこの問題は解決しますが、同じ検証ロジックをもう一度記述して実行する必要があります。また、ストアドプロシージャはDB管理ドメインなので、HISの仕事もやろうとしないでください(または、さらに悪いことに、この「彼が支払われない面倒な選択」に悩まされます)。

「依存する」という有名な言葉でそれを伝えることですが、少なくとも今ではそれが依存する理由を知っています。

このすべてを1か所に配置できればよいのですが、残念ながらこれはできません。これを行うと、すべてのレイヤーのすべての検証を含む「神オブジェクト」に依存関係が置かれます。あなたはその暗い道を行きたくありません。

このため、検証例外をプロパティレベルでのみスローします。他のすべてのレベルでは、ValidationResultにIsValidメソッドを使用して、すべての「壊れたルール」を収集し、それらを単一のAggregateExceptionでユーザーに渡します。

コールスタックを伝播するとき、プレゼンテーション層に到達するまでAggregateExceptionsでこれらを再度収集します。FaultExceptionとしてWCFの場合、サービス層はこの例外をクライアントに直接スローできます。

これにより、例外を取得し、それを分割して各入力コントロールで個々のエラーを表示するか、フラット化して単一のリストに表示することができます。選択はあなた次第です。

これが、これらを可能な限り短絡させるために、プレゼンテーションの検証にも言及した理由です。

集約レベル(または必要に応じてサービスレベル)で検証を行う理由を疑問に思っている場合、それは将来誰が私のサービスを使用するかを知らせるクリスタルボールを持っていないためです。あなたは他の人があなたの間違いを犯すのを防ぐためにあなた自身の間違いを見つけるのに十分な苦労があります:)無効なデータを入力することによって。バグがあるときに最初に尋ねる人を推測しますか?アプリケーションBの管理者は、ユーザーに「エラーはありません。データを入力するだけです」と喜んで知らせます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.