CRUD Webアプリケーション(ASP.NET MVC with Entity Framework)を構築してソフトウェア設計を学んでいます。私はそれを2つのプロジェクトに分割しました。1つはビジネスロジックを含むコアライブラリ、2つ目はWeb GUIです。分離されたコアライブラリを他の場所(デスクトップアプリケーションなど)で使用したり、必要に応じてGUIを後で完全に変更したりできると思いました。
ビジネスロジックは、データベースエンティティで動作し、データ転送オブジェクトを使用して結果を提供する「マネージャー」に格納されています。これは、dbエンティティをコアの外部に公開したくないためです。 Web GUIはユーザー入力を受け入れ、マネージャーを使用してジョブを実行します。また、ビューモデルオブジェクトも定義します。これは、基本的にはデータ転送オブジェクトからの模倣ですが、データアノテーション属性で拡張されています。
問題
ユーザーからの入力を確認する必要があるため、Webプロジェクトはビューモデルオブジェクトに対してサーバー側の検証を実行します。コアプロジェクトは、その上のGUIレイヤーについて何も知らないため、データ転送オブジェクトに対して同じ検証を再度実行します。問題は、データベースへのクエリが必要になることがあり、一般にオーバーヘッドと重複コードが追加されるため、重複データ検証をどのように回避できるかです。 私の場合のベストプラクティスは何ですか?または、ソリューションを2つのプロジェクトに分離するという全体の考えが間違っていますか?
これまでに読んだ投稿では、通常の3層ソリューションでの検証について説明しています。検証は各層(クライアント(JavaScript)、ビジネスレイヤー、データレイヤー)でわずかに異なり、そのような重複した検証は問題ではありません(両方がビジネスです)。
私が思いついた解決策
編集
明確にする必要があると思います:コアはクラスライブラリ、Webはビュー、コントローラーなどを備えた通常のASP.NET MVCアプリケーションです。
編集2
私の質問は データ入力検証-どこ?どれくらい?[終了] の複製のように見え、私は同意しません。この投稿では、アプリケーション内のさまざまなレベルのさまざまな検証スコープについて説明します(クライアント側のJavaScript、ビジネスレイヤー、データレイヤーの検証)。各レベルは異なる検証スコープをカバーしますが、交差する傾向があります。 2つのプロジェクトに分かれている場合、単一レベル(ビジネス)内で重複した検証を削除する方法を知りたいのですが。また、リンクされた投稿は回答を受け入れていません。投票された回答のほとんどは、基本的にコア検証を単一の場所に移動するように指示しています。これは最終的には正しいですが、私の問題に対する答えではありません。
これまでのすべての回答は、コアクラスライブラリに検証を組み込むことを推奨しています。質問は、無効な入力エラーに関する情報をどのように返すかです。
それはデータがコアに提供される前に毎回呼び出されるべきコア「API」の特別なメソッドでしょうか?このAPIは、ユーザーに提供できる詳細なエラー情報を返します。これには、コアライブラリ(プログラマー)のユーザーが、このような特別な動作(または、ユーザーが検証を以前に呼び出したかどうかを判断するための多くのコーディング)について知る必要があります。
入力が無効な場合にコアによってスローされる特別に定義された例外になりますか?この例外には、詳細なエラー情報が含まれます。
ソリューション
回答とコメントをありがとうございました!最終的に、Webプロジェクトからすべてのサーバー側の検証を削除し、Coreプロジェクト内でのみ検証しました。ユーザーMachadoが提案したようにInvalidModelExceptionも作成し、Exception.Dataプロパティを使用してすべてのモデルエラーをキー/値ペアの形式で格納しました。キーは無効なプロパティの名前で、値は以前にCoreで列挙型として定義されたエラーコードです。 Webプロジェクトの内部でこのInvalidModelException例外をキャッチし、一度キャッチすると、ModelStateにモデルエラーを入力して、ユーザーにより良いフィードバックを提供します。すべてのユーザーフレンドリーなエラーメッセージをWebプロジェクトのリソース.resxファイルに保存しています。プロパティ名とInvalidModelExceptionからのエラーコードに基づいて、リソースファイルからわかりやすいエラーメッセージを引き出すためにErrorMessageName文字列を生成しています。
コアはデータがどこから来たのか知りませんよね?つまり、すべての データ入力をサニタイズする必要がある なので、COREが入力を検証するのは必須だと思います。
とはいえ、不正な入力を受け取り、呼び出し元にとって意味のある何かを返すことが予想されるAPIを作成する必要があります。 「ガベージイン、ガベージアウト」は、単に悪いAPIデザインです。
いくつかのMVC5アプリケーションで過去に使用した1つのアプローチは、 "BusinessException"を作成することでした。これには、呼び出し元がAPIを間違って使用していることを理解するための重要な情報が含まれていました。 BusinessExceptionのコンポーネントの1つは、「ユーザーフレンドリーなエラーメッセージ」でした。これは、フォームアプリまたはWebアプリ(コアが実行されている場所をコアが知らなかった)でユーザーに直接表示でき、その他のいくつかの重要なフィールドがありました。 「FaultyProperty」、「TechnicalException」など。
そうすることで、高負荷をCoreにディスパッチする前に、MVCアプリを基本的な検証(非常に基本的な)を実行するシンレイヤーにして、必要に応じて「BusinessException」をキャッチし、それに応じて反応する単一の慣用的な方法でエラーを処理できます。
このようにして、検証を保持し、コードを読みやすくして、 単一責任の原則 と [〜#〜] dry [〜#〜] を適切な場所に維持できます。
また、BusinessExceptionの特定のフィールド/プロパティを入力してエラーの詳細を呼び出し元に返すことにより、GUIレイヤーは、Niceユーザーエクスペリエンスを提供するために必要なものをすべて強調表示できます。
要約/確認するには:アプリケーションの2つのレイヤーが(同じプロセス空間に)あり、アプリケーションの外部レイヤーが内部レイヤーも実行している検証を実行している。これは、ユーザーエクスペリエンスに関するクライアント側の検証と混同しないでください。これには基本的に2つの主要なルートがあります。簡単な方法と複雑な方法です。
簡単な方法は、冗長な検証を削除することです。内層または外層から削除できます。それを外層だけに持つことは、私にはあまり意味がありません。 2つの層があるということは、他のソリューションで内部層を使用できることを意味します。理論的には外側のレイヤーを再利用することもできますが、これはおそらくありそうにありません。内側のレイヤーを他の外側のレイヤーと再利用する場合は、そこで検証を複製するか、コアの検証を無効にできるようにする必要があります。最も単純なアプローチは、すべての「コア」検証を内層に配置し、その層に固有の外層にのみ検証を実装することです。
例外の問題があります。例外のコストは、しばしば売られ過ぎです。私はJavaおよび1で例外のコストについていくつかの分析を行いました。それはスタックの深さに完全に依存します2.本当に深いスタック(数千の呼び出し)でも、多くの反復を実行しなければなりませんでした意味のある測定値を取得するためだけです。C#でも同じだと思います。これが本当に問題であるかどうかを確認するために、いくつかの実験を行うことをお勧めします。Pythonisticの方法で制御フローに使用することは推奨されませんが、不良データからシステムを保護することは例外のスローが私にとって完全に有効であると思われる1つのケース。これを本当に避けたい場合は、リクエストに関連付けられたある種のオブジェクトを使用して、状態と問題を追跡できます。警告を処理する必要がある場合や、致命的でないエラー。
複雑な方法は、検証ロジックである種の台帳を使用して、必要な各検証が特定のトランザクションで実行されたかどうかを追跡することです。その後、コアはそのロジックを再実行する必要はありません。これは空想的であり、したがって、新しい問題を作成する多くの機会があります。
入力データセットに複数の独立した検証エラーがあり、すべてのエラーをUIに表示する必要があるため、コアからの例外はおそらく適切ではありません。したがって、最善のアプローチは、入力データの検証エラーのコレクションを返すvalidate(InputDataSet)
メソッドをコアに含めることです。
検証が成功した場合、validateメソッドが別の型ValidatedDataset
を返すようにすることができます。次に、ビジネスメソッドにValidatedDataset
のみを受け入れさせることができます。
これが小規模なチームによる社内プロジェクトである場合、検証の層の1つを安全に削除できると思います。プロジェクトが大規模または複雑な場合、または複数のチームに対処する必要がある場合、またはサードパーティがAPIにアクセスする場合は、おそらく冗長な検証が必要です。
データベースのルックアップが原因で何かを検証する作業に大きなオーバーヘッドがある場合は、共通のキャッシュを実装することを検討してください。キャッシュプロバイダーをコンポジションルートに挿入して、アプリケーションのすべてのレイヤーが同じ共通キャッシュを使用するようにします。冗長な検証が行われ、一部のルックアップ値をデータベースに照会する必要がある場合、キャッシュヒットが発生し、パフォーマンスへの影響は最小限になります。