web-dev-qa-db-ja.com

ドメインモデルの検証を行う場所

ドメインモデル検証のベストプラクティスを探しています。ドメインモデルのコンストラクターに検証を配置するのは良いことですか?次のドメインモデル検証の例:

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;
    }
}

あなたの提案のすべてに感謝します。

40
adisembiring

Martin Fowlerによる興味深い記事 がその主題についてあり、ほとんどの人(私を含む)が見落としがちな側面を強調しています。

しかし、私が常に人をつまずくと思うのは、isValidメソッドなどのコンテキストに依存しない方法でオブジェクトの有効性が意味することを彼らが考えるときです。

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

このことから、おそらくすべてのコンテキストで共有される非常に基本的な健全性チェックを除いて、コンストラクタは検証をしないでください

再び記事から:

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

49

この質問は少し古いですが、価値のあるものを追加したいと思います。

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

妥当性については、制御システムについて考えたいと思います。制御システムは、出力の状態を常に分析し、出力が設定値から外れると修正アクションを適用することによって機能します。これは閉ループ制御と呼ばれます。閉ループ制御は本質的に偏差を予期し、それを修正するように機能します。これが実際の動作です。これが、すべての実際の制御システムが通常閉ループコントローラーを使用する理由です。

コンテキスト依存の検証を使用し、オブジェクトを簡単に構築できるようにすると、システムでの作業が簡単になると思います。

6
Paul

もうご存知だと思いますが...

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

C'torパラメータとして渡されたデータの有効性のチェックは、コンストラクタでdefinitely有効です。それ以外の場合は、無効なオブジェクトの作成を許可している可能性があります。

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


また、コードサンプルに含めたのと同じように...

AddOrderLineでさらに検証(または他の機能)を行わない限り、List<LineItem>Orderとしてではなくプロパティとして facade として機能させます。

4
Demian Brecht

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

ドメインモデルやソフトウェアを作成する他の方法など、あらゆる状況での検証は、検証する対象と現在のレベルの目的に役立つはずです。

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

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

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

  3. 検証は、集約(または集約ルート)エンティティでも実行する必要があります。 Orderオブジェクトには有効なデータが含まれる場合があり、そのためOrderLinesにも含まれます。しかし、ビジネスルールでは、注文は1,000ドルを超えることはできないと規定されています。 IS許可されている場合、このルールをどのように適用しますか?これは悪用につながるため、 "do not validate amount"プロパティを追加することはできません(遅かれ早かれ、おそらくあなたは、この「厄介な要求」を邪魔にならないようにするだけです)。

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

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

「それは依存する」という有名な言葉でそれを伝えるために、少なくとも今では、なぜそれが依存するのかを知っています。

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

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

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

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

これがプレゼンテーションの検証についても触れた理由であり、可能な限りこれらをショートさせるためです。

なぜ私が集約レベル(または、必要に応じてサービスレベル)でも検証を行うのか不思議に思っているのは、将来誰が私のサービスを使用するのかを説明するクリスタルボールがないためです。自分の間違いを見つけるのに十分な問題があり、無効なデータを入力して他の人があなたの間違いを犯すのを防ぐことができます。アプリケーションAを管理していますが、アプリケーションBはサービスを使用して一部のデータをフィードします。バグがあるときに最初に誰に尋ねるかを推測しますか?アプリケーションBの管理者は、「自分の側でエラーはなく、データを入力するだけです」と喜んでユーザーに通知します。

4
Wesley Kenis