ビジネス ロジックの障害に関する有益なメッセージを返す CRUD クラス ライブラリの設計。これは例外ではありません。
-
06-07-2019 - |
質問
私はローカル (参照の追加) 環境と wcf (サービス参照の追加) 環境の両方で使用することを想定した基本的な CRUD ライブラリを構築しています。
CRUD 設定の作成、更新、および削除の部分 (より複雑なビジネス ルールがある) に最適な戻り値の型は何ですか?
回線上のやり取りを最小限に抑えたいと考えていますが、操作がビジネス ロジックに失敗しても技術的には有効である (したがって例外ではない) 場合について、意味のある情報をクライアントに提供したいとも考えています。
たとえば、CRUD は次のフィールドを持つ Person クラスのものであるとします。名、ミドルネーム、姓、誕生日。First、Last、DOB は必須ですが、Middle は必須ではありません。
ビジネス ロジックの失敗をクライアントにどのように伝えればよいでしょうか?I.E.「FirstName の値を指定する必要があります。」
- これは私が例外を投げるべきである場所ですか?(それは例外的なケースのようには感じないので、私はそうではありませんが、私は間違っている可能性があります)。
- void と "out" パラメータを使用する必要がありますか?もしそうなら、それはどのタイプにすべきですか?
- オブジェクトの戻り値の型を使用して、何が起こったかについてのデータをそこに入れる必要がありますか?
- 私が完全に見逃していた他のオプションはありますか?
解決
1.これは例外をスローすべき場所ですか?(例外的なケースとは思えないので、そうではないと思いますが、間違っている可能性があります)。
個人的には、情報の欠落 (形式の検証) やビジネス ロジックの検証のいずれが原因であっても、データ検証の例外をスローするのではなく、結果と検証エラーを含むオブジェクトを返す必要があると感じています。ただし、データ自体に関係のないエラーに対しては例外をスローすることをお勧めします。つまり、次のとおりです。データベースのコミットが有効なデータで失敗した場合など。
ここでの私の考えは、検証の失敗は「例外的な出来事」ではないということです。私は個人的に、ユーザーが十分な/正しい/その他のデータを入力しないことによって混乱を招く可能性のあるものは例外的なものではないと感じています。これは標準的な慣行であり、API によって直接処理されるべきです。
ユーザーの行動に関係のないもの (例:ネットワークの問題、サーバーの問題など)は例外的に発生するものであり、例外が保証されます。
2. void と "out" パラメータを使用する必要がありますか?もしそうなら、それはどのタイプにすべきですか?
3.オブジェクトの戻り値の型を使用し、何が起こったかについてのデータをそこに入れる必要がありますか?
私は個人的には 3 番目のオプションを好みます。「out」パラメータはあまり意味がありません。また、この呼び出しから複数のステータス情報を返す必要があります。適切なプロパティに無効のフラグを立てるのに十分な情報と、操作全体の完全な情報を返す必要があります。
これにはおそらく、少なくともコミット ステータス (成功/失敗したフォーマット/失敗したビジネス ロジックなど)、プロパティ -> エラーのマッピングのリスト (つまり: IDataErrorInfo スタイル情報)、および特定のプロパティに関連付けられていないエラーのリストである可能性がありますが、むしろ操作全体のビジネス ロジック、または提案されたプロパティ値の組み合わせに対処します。
4.完全に見逃していた他のオプションはありますか?
私がとても気に入っているもう 1 つのオプションは、ビジネス処理層とは別のアセンブリで検証を行うことです。これにより、クライアント側で検証ロジックを再利用できるようになります。
これの良い点は、ネットワーク トラフィックを大幅に簡素化して削減できることです。クライアントは情報を事前に検証し、有効な場合にのみデータをネットワーク経由で送信できます。
サーバーは正常なデータを受信して再検証し、単一のコミット結果のみを返すことができます。これには、成功、ビジネス ロジックによる失敗、またはフォーマットによる失敗の少なくとも 3 つの応答が必要であると考えています。これによりセキュリティが確保され (クライアントを信頼する必要はありません)、何が適切に処理されていないのかについての情報がクライアントに提供されますが、クライアントからサーバーへの不正な情報と、サーバーからクライアントへの検証情報の両方が渡されることは回避されます。トラフィックを大幅に削減できます。
その後、検証層は情報を (安全に) CRUD 層に送信して送信できます。
他のヒント
パフォーマンス上の理由から、最初の入力検証はクライアント層で実行する必要があります。 (つまり、不正なデータをネットワーク経由で送信しないでください。)クライアント層では、入力検証の失敗は、実際には例外をスローすべきではない非常に予期される問題です。ただし、これを「初期」に置くと、クライアント層で検証が行われている場合、より深い層で発生したデータ検証の失敗は、予期しない問題と合理的に見なされる可能性があるため、それらの層でデータ検証エラーの例外をスローすることは不適切ではありません。
Rob Bagbyのこのブログ投稿は興味深いかもしれません。 CRUD操作を処理するためのリポジトリの実装方法について説明します。具体的には、検証の実装方法について説明し、「RuleViolation」のコレクションを返します。問題がある場合にクライアントに送信します。
[edit]私にとっては、例外をスローする場合です。ユーザーの作成にファーストネームが必要で、ファーストネームが提供されない場合、呼び出し側は適切な引数を使用せず、メソッドを使用していません意図した方法で。 InvalidArgumentExceptionは適切に聞こえます。
本当に CSLAフレームワークで検証ルールのRocky Lhotkaの実装を確認する必要があります。
注:最新の.NET開発トレンドにおけるSRPの取り組みを壊すカップリングの問題があるため、私は彼のフレームワークをまとめて使用するとは言いませんでした。
しかし、彼のフレームワークは「自動」を使用しています。 UIレイヤーまでの通知と、Web / Winformsコントロールのサポートによる検証エラーメッセージとの統合。
編集:通常、検証をビジネスレイヤーに抽象化し、検証を処理し、検証の成功後にCRUDメソッドを呼び出します。
これを実行する1つの方法は、「応答」を返すことです。ライブラリの利用者が何が起こったのか、次に何をすべきかを評価するために必要なすべての情報を保持するクラス。使用できるクラスの非常に基本的な例は次のようになります。
public class Response<T> where T:BusinessObject
{
public Response(T oldOriginalValue, T newValue)
{
}
/// <summary>
/// List of Validation Messages
/// </summary>
public List<ValidationMessage> ValidationMessages { get; set; }
/// <summary>
/// Object passed into the CRUD method
/// </summary>
public T OldValue { get; private set; }
/// <summary>
/// Object passed back from the CRUD method, with values of identity fields, etc populated
/// </summary>
public T NewValue { get; private set; }
/// <summary>
/// Was the operation successful
/// </summary>
public bool Success { get; set; }
}
public class ValidationMessage
{
/// <summary>
/// Property causing validation message (i.e. FirstName)
/// </summary>
string Property { get; set; }
/// <summary>
/// Validation Message text
/// </summary>
string Message { get; set; }
}
失敗の詳細を含む結果構造を返すことと例外をスローすることの間の議論は、「要求されたアクションを正常に実行できますか?」に一般化できます。ライブラリを設計しており、ライブラリが障害モードをどのように通信するかはその一部です。
ライブラリがUserオブジェクトを作成するように求められたが、ユーザー名が検証に失敗したためにできない場合、検証失敗メッセージとともに空のUserを返し、クライアントコードが戻り値をテストすることを期待できます。エラーコードの戻り値をテストしなければならないという私の経験(DCOMのATLフラッシュバック)では、満足(または怠け者)になってスキップするのは簡単です。
説明したことから、例外の使用は問題外ではありません。ライブラリがスローできるすべての例外タイプの親例外を定義します。そうすれば、クライアントは本当に必要な場合を除き、サブ例外の大きなリストを含める必要がなくなります。この例はSqlExceptionです。