web-dev-qa-db-ja.com

ビジネスオブジェクト、検証、および例外

例外とその使用に関するいくつかの質問と回答を読んでいます。例外は、例外の未処理の場合にのみ発生する必要があるという強い意見のようです。そのため、検証がビジネスオブジェクトでどのように機能するのか疑問に思いました。

オブジェクトのプロパティのゲッター/セッターを持つビジネスオブジェクトがあるとしましょう。値が10〜20であることを検証する必要があるとします。これはビジネスルールであるため、ビジネスオブジェクトに属します。つまり、検証コードが私のセッターに含まれていることを意味しているようです。これで、UIデータがデータオブジェクトのプロパティにバインドされました。ユーザーは5を入力するため、ルールは失敗する必要があり、ユーザーはテキストボックスから移動できません。 。 UIはプロパティにデータバインドされているため、セッターが呼び出され、ルールがチェックされ、失敗します。ルールが失敗したという例外をビジネスオブジェクトから発生させた場合、UIはそれを取得します。しかし、それは例外の好ましい使用法に反しているようです。セッターであることを考えると、実際にはセッターの「結果」は得られません。オブジェクトに別のフラグを設定した場合、それはUIが各UI操作の後にそのフラグをチェックする必要があることを意味します。

では、検証はどのように機能する必要がありますか?

編集:私はおそらくここで過度に単純化された例を使用しました。上記の範囲チェックのようなものはUIで簡単に処理できますが、評価がより複雑な場合はどうなりますか。ビジネスオブジェクトは入力に基づいて数値を計算し、その計算された数値が範囲外の場合は拒否する必要があります。これはより複雑なロジックであり、UIに含めるべきではありません。

すでに入力されたフィールドに基づいて入力された追加データの考慮もあります。たとえば、手持ち在庫や現在のコストなどの特定の情報を取得するには、注文にアイテムを入力する必要があります。ユーザーは、さらに入力するかどうかを決定するためにこの情報を必要とする場合があります(注文するユニットの数など)。さらに検証を行うため。アイテムが有効でない場合、ユーザーは他のフィールドに入力できる必要がありますか?ポイントは何でしょうか?

37
user11355

データ検証に関する Paul Stovell の注目すべき作業について少し掘り下げたいと思います。彼は一度に彼の考えを この記事 に要約しました。私が実装した問題についての彼の見解をたまたま共有します 私自身のライブラリ

Paulの言葉を借りれば、セッターで例外をスローすることの短所は次のとおりです(Nameプロパティが空であってはならないサンプルに基づく):

  • 実際に空の名前が必要になる場合があります。たとえば、 "アカウントの作成"フォームのデフォルト値として。
  • 保存する前にデータを検証するためにこれに依存している場合、データがすでに無効である場合を見逃すことになります。つまり、空の名前でデータベースからアカウントをロードし、それを変更しない場合、それが無効であることに気付かない可能性があります。
  • データバインディングを使用していない場合は、これらのエラーをユーザーに表示するために、try/catchブロックを使用して多くのコードを作成する必要があります。ユーザーがフォームに入力しているときにフォームにエラーを表示しようとすると、非常に困難になります。
  • 例外ではないものに対して例外をスローするのは好きではありません。アカウントの名前を "Supercalafragilisticexpialadocious"に設定しているユーザーも例外ではなく、エラーです。もちろん、これは個人的なことです。
  • 違反したすべてのルールのリストを取得することは非常に困難です。たとえば、一部のWebサイトでは、「名前を入力する必要があります。アドレスを入力する必要があります。電子メールを入力する必要があります」などの検証メッセージが表示されます。それを表示するには、たくさんのtry/catchブロックが必要になります。

そして、ここに代替ソリューションの基本的なルールがあります:

  1. 無効なビジネスオブジェクトを保持しようとしない限り、それが問題になることはありません。
  2. 壊れたルールはすべてビジネスオブジェクトから取得できる必要があります。これにより、データバインディングと独自のコードでエラーがあるかどうかを確認し、適切に処理できるようになります。
18
Mac

個別の検証コードと永続化(つまりデータベースに保存)コードがあると仮定すると、次のようにします。

  1. UIは検証を実行する必要があります。ここで例外をスローしないでください。エラーをユーザーに警告し、レコードが保存されないようにすることができます。

  2. データベース保存コードは、不良データに対して無効な引数例外をスローする必要があります。この時点ではデータベースの書き込みを続行できないため、ここで行うのは理にかなっています。 UIはユーザーの保存を妨げるため、理想的にはこれが発生しないはずですが、データベースの一貫性を確保するために必要です。また、UIデータの検証が行われていないUI以外の場所(バッチ更新など)からこのコードを呼び出している可能性もあります。

8
Mike Thompson

私は常に CSLAフレームワーク (Charlesが述べたように)におけるRockyLhotkaのアプローチのファンでした。一般に、セッターによって駆動されるか、明示的なValidateメソッドを呼び出すことによって駆動されるかにかかわらず、BrokenRuleオブジェクトのコレクションはビジネスオブジェクトによって内部的に維持されます。 UIは、オブジェクトのIsValidメソッドをチェックするだけで、BrokenRulesの数をチェックし、適切に処理します。または、ValidateメソッドでUIが処理できるイベントを簡単に発生させることもできます(おそらくよりクリーンなアプローチ)。 BrokenRulesのリストを使用して、使用するエラーメッセージを要約形式または適切なフィールドの横に表示することもできます。 CSLAフレームワークは.NETで記述されていますが、全体的なアプローチはどの言語でも使用できます。

この場合、例外をスローするのが最善の方法ではないと思います。私は間違いなく、例外は例外的な状況のためのものであるべきだという考え方に従いますが、単純な検証エラーはそうではありません。私の意見では、OnValidationFailedイベントを発生させる方がより適切な選択です。

ちなみに、無効な状態のときにユーザーがフィールドを離れないようにするというアイデアは好きではありませんでした。戻って無効なフィールドを修正する前に、一時的にフィールドを離れる必要がある(おそらく最初に他のフィールドを設定する)必要がある状況は非常に多くあります。不必要な不便だと思います。

8
jeremcc

検証をゲッターとセッターの外に移動することをお勧めします。すべての検証ルールを実行するIsValidという関数またはプロパティを作成できます。 tは、辞書またはハッシュテーブルにすべての「違反したルール」を入力します。この辞書は外の世界に公開され、エラーメッセージを入力するために使用できます。

これは、CSLA.Netで採用されているアプローチです。

5
Charles Graham

通常検証の一部として例外をスローしないでください。ビジネスオブジェクト内から呼び出される検証は最後の防衛線であり、UIが何かをチェックできない場合にのみ発生する必要があります。そのため、他のランタイム例外と同様に扱うことができます。

検証ルールの定義と適用の違いは次のとおりです。ビジネスロジックレイヤーでビジネスルールを定義(つまり、コード化または注釈付け)したいが、UIから呼び出して、特定のUIに適した方法で処理できるようにすることができます。処理方法は、UIによって異なります。たとえば、フォームベースのWebアプリとajaxWebアプリなどです。例外オンセット検証では、処理のオプションが非常に限られています。

多くのアプリケーションは、JavaScript、ドメインオブジェクトの制約、データベースの制約など、検証ルールを複製します。理想的には、この情報は1回だけ定義されますが、これを実装することは困難な場合があり、水平思考が必要です。

4
Brad at Kademi

私は、ビジネスルールに違反する値が渡された場合、ビジネスオブジェクトは例外をスローする必要があると考える傾向があります。ただし、winforms 2.0データバインディングアーキテクチャはその逆を想定しているため、ほとんどの人はこのアーキテクチャをサポートするために鉄道に乗っています。

私は、ビジネスオブジェクトがwinforms環境だけでなく、複数の環境で使用可能で正しく機能するように構築する必要があるというshabbyrobeの最後の回答に同意します。たとえば、ビジネスオブジェクトはSOA typeで使用できます。 Webサービス、コマンドラインインターフェイス、asp.netなど。オブジェクトは正しく動作し、これらすべての場合に無効なデータから自身を保護する必要があります。

見落とされがちな側面は、1-1、1-n、またはnnの関係にあるオブジェクト間のコラボレーションを管理する際に発生することでもあります。これらも無効なコラボレーターの追加を受け入れ、チェックする必要がある、またはチェックする必要がある無効な状態フラグを維持するだけです。無効なコラボレーションの追加を積極的に拒否します。私はジルニコラらの合理化されたオブジェクトモデリング(SOM)アプローチに大きく影響されていることを認めなければなりません。しかし、他に何が論理的です。

次は、Windowsフォームの操作方法です。これらのシナリオのビジネスオブジェクトのUIラッパーを作成することを検討しています。

3
Jide Ogundipe

Paul Stovellの article で述べたように、IDataErrorInfoインターフェイスを実装することで、ビジネスオブジェクトにエラーのない検証を実装できます。そうすることで、 WinFormのErrorProvider および WPFの検証ルールとのバインド によるユーザーエラー通知が可能になります。オブジェクトのプロパティを検証するロジックは、各プロパティゲッターではなく、1つのメソッドに格納されます。必ずしも、CSLAやValidation ApplicationBlockなどのフレームワークに頼る必要はありません。

ユーザーがテキストボックスからフォーカスを変更できないようにすることに関する限り、まず第一に、これは通常、ベストプラクティスではありません。ユーザーは、フォームに順不同で入力したい場合があります。または、検証ルールが複数のコントロールの結果に依存している場合、ユーザーは、あるコントロールから出て別のコントロールを設定するためだけにダミー値を入力する必要があります。とはいえ、これは、フォームのAllowValidateプロパティをデフォルトのEnableAllowFocusChangeに設定し、Control.Validatingイベントにサブスクライブすることで実装できます。

    private void textBox1_Validating(object sender, CancelEventArgs e)
    {
        if (textBox1.Text != String.Empty)
        {
            errorProvider1.SetError(sender as Control, "Can not be empty");
            e.Cancel = true;
        }
        else
        {
            errorProvider1.SetError(sender as Control, "");
        }
    }

フォーカスが変更され、データバインドされたビジネスオブジェクトが更新される前にValidatingイベントが呼び出されるため、この検証にビジネスオブジェクトに格納されているルールを使用するのは少し注意が必要です。

3
foson

おそらく、クライアント側とサーバー側の両方の検証を検討する必要があります。クライアント側の検証をすり抜けた場合、ビジネスオブジェクトが無効になる場合は、自由に例外をスローできます。

私が使用したアプローチの1つは、検証ルールを説明するカスタム属性をビジネスオブジェクトプロパティに適用することでした。例えば。:

[MinValue(10), MaxValue(20)]
public int Value { get; set; }

次に、属性を処理して使用し、クライアント側とサーバー側の両方の検証メソッドを自動的に作成して、ビジネスロジックの重複の問題を回避できます。

3
Matt Howells

私は間違いなく、クライアント側とサーバー側の両方の検証(またはさまざまなレイヤーでの検証)を推奨します。スロー例外のコストがますます高くなるため、これは物理的な層またはプロセス間で通信する場合に特に重要です。また、検証を待つチェーンの下流に行くほど、より多くの時間が無駄になります。

データ検証に例外を使用するかどうか。プロセスで例外を使用することは問題ないと思いますが(まだ好ましくありませんが)、プロセスの外で、メソッドを呼び出してビジネスオブジェクトを検証し(保存する前など)、メソッドに操作の成功と検証を返します- エラー。エラーは例外的ではありません。

検証が失敗すると、Microsoftはビジネスオブジェクトから例外をスローします。少なくとも、それがエンタープライズライブラリの検証アプリケーションブロックの仕組みです。

using Microsoft.Practices.EnterpriseLibrary.Validation;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;
public class Customer
{
  [StringLengthValidator(0, 20)]
  public string CustomerName;

  public Customer(string customerName)
  {
    this.CustomerName = customerName;
  }
}
3
Rob Gray

ビジネスオブジェクトは、不正な入力に対して例外をスローする必要がありますが、通常のプログラム実行中にこれらの例外がスローされることはありません。それは矛盾しているように聞こえるので、説明します。

各パブリックメソッドはその入力を検証し、正しくない場合は「ArgumentException」をスローする必要があります。 (そして、プライベートメソッドは開発を容易にするために "Debug.Assert()"で入力を検証する必要がありますが、それは別の話です。)パブリックメソッド(そしてもちろんプロパティ)への入力の検証に関するこのルールは、アプリケーションのすべてのレイヤーに当てはまります。 。

もちろん、ソフトウェアインターフェイスの要件は、インターフェイスのドキュメントで詳しく説明する必要があります。引数が正しく、例外がスローされないことを確認するのは呼び出し元のコードの仕事です。つまり、UIは検証する必要があります。それらをビジネスオブジェクトに渡す前に入力します。

上記のルールに違反することはほとんどありませんが、ビジネスオブジェクトの検証が非常に複雑になる場合があり、その複雑さをUIに押し付けてはなりません。その場合、BOのインターフェースは、受け入れるものにある程度の余裕を持たせてから、プロパティをチェックし、何を変更する必要があるかについてフィードバックを与えるための明示的なValidate(out string [])述語を提供するのが良いでしょう。ただし、この場合、まだ明確に定義されたインターフェイス要件があり、例外をスローする必要がないことに注意してください(呼び出し元のコードがルールに従っていると仮定)。

この後者のシステムに続いて、私はプロパティセッターの早期検証をほとんど行いません。なぜなら、そのソフトオフはプロパティの使用を複雑にするからです(しかし、質問で与えられた場合、私はそうかもしれません)。 (余談ですが、データが不良であるという理由だけでフィールドからタブアウトするのを妨げないでください。フォームをタブで移動できないと、嫌悪感を覚えます。戻ってすぐに修正します。 、約束します!OK、今は気分が良くなりました、ごめんなさい。)

3

Springフレームワークが採用したアプローチ を検討することをお勧めします。 Java(または.NET)を使用している場合は、Springをそのまま使用できますが、使用していない場合でも、そのパターンを使用できます。必要なのはそれの独自の実装を書いてください。

1
Andrew Swan

私の経験では、検証ルールがアプリケーションのすべての画面/フォーム/プロセスに共通することはめったにありません。このようなシナリオは一般的です。追加ページでは、Personオブジェクトに姓がなくても問題ない場合がありますが、編集ページでは姓が必要です。そういうわけで、検証はオブジェクトの外部で行われるべきである、またはルールがコンテキストに応じて変更できるようにルールがオブジェクトに注入されるべきであると私は信じるようになりました。有効/無効は、検証後のオブジェクトの明示的な状態、または失敗したルールのコレクションをチェックすることによって導出できる状態である必要があります。失敗したビジネスルールは例外ではありません。

1
Daniel Auger

データが無効な場合、セッターでイベントを発生させることを検討しましたか?これにより、例外がスローされる問題が回避され、オブジェクトの「無効な」フラグを明示的にチェックする必要がなくなります。再利用しやすくするために、検証に失敗したフィールドを示す引数を渡すこともできます。

イベントのハンドラーは、必要に応じて適切なコントロールにフォーカスを戻すことができる必要があり、ユーザーにエラーを通知するために必要なコードを含めることができます。また、イベントハンドラーの接続を拒否し、必要に応じて検証の失敗を無視することもできます。

1
Jeromy Irvine

あなたの場合に例外を投げることは問題ありません。たとえば、何かが整数を文字列に設定しようとしているため、このケースは真の例外と見なすことができます。ビジネスルールにあなたの見解の知識がないということは、彼らがこのケースを例外的に考慮し、それを見解に戻す必要があることを意味します。

入力値をビジネス層に送信する前に検証するかどうかはあなた次第ですが、アプリケーション全体で同じ標準に従う限り、クリーンで読みやすいコードになってしまうと思います。

上記で指定したようにSpringフレームワークを使用できますが、リンクされたドキュメントの多くが強く型付けされていないコードを記述していることを示しているので注意してください。実行時に、コンパイル時に検出できなかったエラーが発生する場合があります。これは私ができるだけ避けようとしていることです。

現在ここで行っている方法は、画面からすべての入力値を取得し、それらをデータモデルオブジェクトにバインドし、値にエラーがある場合は例外をスローすることです。

1
Odd

私の意見では、これは例外をスローしても問題ない例です。おそらく、プロパティには問題を修正するためのコンテキストがありません。そのような例外が適切であり、可能であれば、呼び出し元のコードが状況を処理する必要があるためです。

0
Kimoz

入力がビジネスオブジェクトによって実装されたビジネスルールを超える場合、それはビジネスオブジェクトによって処理されないケースであると言えます。したがって、例外をスローします。この例では、セッターが5を「処理」しますが、ビジネスオブジェクトは処理しません。

ただし、入力のより複雑な組み合わせの場合は、有効化方法が必要です。そうしないと、非常に複雑な検証がいたるところに散らばってしまいます。

私の意見では、許可/禁止された入力の複雑さに応じて、どちらに進むかを決定する必要があります。

0
Ludvig A. Norin

あなたのビジネスモデルがどれだけ重要かによると思います。 DDDの道を進みたいのであれば、モデルが最も重要です。したがって、常に有効な状態にする必要があります。

私の意見では、ほとんどの人はドメインオブジェクトでやりすぎ(ビューとの通信、データベースへの永続化など)を試みていますが、場合によっては、より多くのレイヤーと関心の分離、つまり1つ以上のビューモデルが必要になります。次に、ビューモデルに例外なしで検証を適用し(検証は、Webサービス/ Webサイトなどのコンテキストごとに異なる可能性があります)、ビジネスモデル内に例外検証を保持できます(モデルが破損しないようにするため)。ビューモデルをビジネスモデルにマッピングするには、1つ(または複数)のアプリケーションサービスレイヤーが必要です。ビジネスオブジェクトは、NHibernateValidatorなどの特定のフレームワークに関連することが多い検証属性で汚染されるべきではありません。

0
W3Max