web-dev-qa-db-ja.com

データベースからの誤ったnullエントリを防ぐための設計と実践

私のプログラムの一部は、データベース内の多くのテーブルと列からデータをフェッチして処理します。一部の列はnullである可能性がありますが、現在の処理コンテキストではエラーになります。

これは「理論的には」発生してはならないので、発生した場合は不良データまたはコード内のバグを示しています。エラーの重大度は、どのフィールドがnullかによって異なります。つまり、一部のフィールドでは処理を停止して誰かに通知する必要があり、他のフィールドでは処理を続行して誰かに通知するだけにする必要があります。

まれですが可能なnullエントリを処理するための優れたアーキテクチャまたは設計原則はありますか?

ソリューションはJavaで実装できるはずですが、問題は言語にとらわれないため、タグを使用しませんでした。


私自身が持っていたいくつかの考え:

NOT NULLの使用

最も簡単なのは、データベースでNOT NULL制約を使用することです。

しかし、データの元の挿入がこの後の処理ステップよりも重要である場合はどうなりますか?そのため、挿入によってnullがテーブルに挿入される場合(バグまたはおそらく何らかの理由により)、挿入が失敗しないようにしたいと思います。プログラムのさらに多くの部分が、挿入されたデータに依存しているが、この特定の列には依存していないとしましょう。そのため、挿入ステップではなく、現在の処理ステップでエラーが発生する危険を冒したいのです。それが、NOT NULL制約を使用したくない理由です。

単純にNullPointerExceptionに依存

常にそこにあると期待しているかのようにデータを使用し(実際にそうであるはずです)、結果のNPEを適切なレベルでキャッチします(たとえば、現在のエントリの処理は停止しますが、処理全体は進行しません) )。これは「フェイルファスト」の原則であり、私はしばしばそれを好みます。少なくともバグの場合、ログに記録されたNPEを取得します。

しかしその後、さまざまな種類の欠落データを区別する能力が失われます。例えば。一部の欠落しているデータについては、それを省略できますが、他のデータについては処理を停止して管理者に通知する必要があります。

各アクセスの前にnullをチェックし、カスタム例外をスローする

カスタム例外を使用すると、例外に基づいて正しいアクションを決定できるので、これは進むべき道のようです。

しかし、どこかで確認するのを忘れた場合はどうなりますか?また、私はコードをNullチェックで乱雑にしますが、これは決してまたはまれにしか期待されません(そのため、ビジネスロジックフローの一部ではありません)。

この方法を選択した場合、どのパターンがアプローチに最適ですか?


私のアプローチについての考えやコメントは大歓迎です。また、あらゆる種類の優れたソリューション(パターン、原則、私のコードやモデルの優れたアーキテクチャなど)。

編集:

別の制約があります。ORMを使用してDBから永続オブジェクトへのマッピングを行うため、そのレベルでnullチェックを実行しても機能しません(nullが害を及ぼさない部分で同じオブジェクトが使用されるため)。 。これまでに提供された回答の両方がこのオプションについて言及したため、これを追加しました。

9
jhyot

結果セットからオブジェクトを作成するマッピングコードにnullチェックを配置します。これにより、チェックが1か所に配置され、エラーが発生する前にレコードの処理の途中でコードを実行できなくなります。アプリケーションフローの仕組みによっては、各レコードを一度に1つずつマッピングして処理するのではなく、すべての結果のマッピングを前処理ステップとして実行することもできます。

ORMを使用している場合は、各レコードを処理する前にすべてのnullチェックを実行する必要があります。 recordIsValid(recordData)タイプのメソッドをお勧めします。これにより、すべてのnullチェックと他の検証ロジックを(もう一度)1つの場所に保持できます。ヌルチェックを残りの処理ロジックと混在させないでください。

9
TMN

Nullの挿入はエラーのようですが、データを失いたくないので、挿入時にこのエラーを強制することを恐れます。ただし、フィールドがnullであってはならないが、nullである場合、データを失っていますです。したがって、最善の解決策は、最初にnullフィールドが誤って保存されないようにすることです。

この目的のために、そのデータの1つの信頼できる永続的なリポジトリであるデータベースでデータが正しいことを強制します。 nullではない制約を追加することにより、これを行います。その後、コードは失敗する可能性がありますが、これらの失敗はすぐにバグを通知し、すでにデータを失う原因となっている問題を修正できるようにします。バグを簡単に特定できるようになったので、コードをテストして2回テストします。 nullを気にする必要がないため、データの損失につながるバグを修正し、その過程で、データのダウンストリーム処理を大幅に簡略化できます。

6

質問のこの文に関して:

これは「理論的には」発生してはならないので、発生した場合は不良データまたはコード内のバグを示しています。

私は常にこの引用に感謝しています( この記事 の厚意により):

初心者プログラマーが彼らの主な仕事がプログラムのクラッシュを防ぐことであると信じているとき、それは面白いと思います。この壮大な失敗の議論は、そのようなプログラマにとってはそれほど魅力的ではないでしょう。より経験豊富なプログラマーは、正しいコードが優れていること、クラッシュするコードが改善をもたらす可能性があることを認識していますが、クラッシュしない誤ったコードは恐ろしい悪夢です。

基本的に、それはあなたが支持しているように聞こえます Postelの法則 、「送信するものは保守的に、受け入れるものは寛大に」。理論的には優れていますが、実際には、この「ロバスト性の原則」は、ないロバストなというソフトウェアにつながります。少なくとも長期的には-そして時には短期的にも。 (Eric Allmanの論文と比較してください The Robustness Principle Reconsidered 。これは、主にネットワークプロトコルのユースケースに焦点を当てていますが、この主題の非常に徹底的な処理です。)

データベースにデータを誤って挿入するプログラムがある場合、それらのプログラムは壊れているので、fixed。問題を取り上げると、悪化し続けるだけです。これは enabling と同等のソフトウェアエンジニアリングであり、依存症を続ける常習者です。

ただし、実際には、少なくとも一時的に、特に緩んだ壊れた状態から厳密な正しい状態へのシームレスな移行の一部として、「壊れた」動作を続行できるようにする必要がある場合があります。その場合、誤った挿入を成功させることができる方法を見つけたいが、それでも「正規の」データストアを常に正しい状態にすることができます。これにはさまざまな方法があります。

  • データベーストリガーを使用して、不正な形式の挿入を正しい挿入に変換します。欠落/ null値をデフォルトに置き換える
  • 正しくないプログラムが、「正しくない」ことが許可されている別個のデータベーステーブルに挿入し、修正されたデータをそのテーブルから正規のデータストアに移動する別のスケジュールされたプロセスまたはその他のメカニズムを用意する
  • クエリ側のフィルタリング(ビューなど)を使用して、データベースから取得したデータが常に正しい状態であることを確認します。

これらすべての問題を回避する1つの方法は、書き込みを発行するプログラムと実際のデータベースの間に、制御するAPIレイヤーを挿入することです。

問題の一部のように思えますが、正しくない書き込みを生成しているすべての場所がわからない、または更新するにはそれらの場所が多すぎることが原因です。それは恐ろしい状態ですが、そもそもそれが発生することは許されるべきではありませんでした。

正規の本番データストアでデータを変更することが許可されている少数のシステムを取得するとすぐに問題が発生します。を一元的に維持する方法はありませんそのデータベースについて。できる限り少ないプロセスに書き込みを許可し、必要に応じて挿入する前にデータを前処理できる「ゲートキーパー」としてそれらを使用することをお勧めします。このための正確なメカニズムは、実際には特定のアーキテクチャによって異なります。

5
Daniel Pryden

まれであるが可能性のあるnullエントリを処理するための優れたアーキテクチャまたは設計原則はありますか?

簡単な答え-はい。

[〜#〜] etl [〜#〜]

データがデータベースに入るのに十分な品質であることを確認するために、いくつかの事前処理を実行します。ドロップファイルの内容はすべて報告され、クリーンなデータをデータベースにロードできます。

私は密猟者(開発者)とゲームキーパー(DBA)の両方である人物として、苦い経験から、第三者が強制されない限りデータの問題を解決しないことを知っています。絶えず後方に曲がり、データをマッサージすることは危険な先例を設定します。

マート/リポジトリ

このシナリオでは、生データがリポジトリDBにプッシュされ、アプリケーションがアクセスできるマートDBにサニタイズされたバージョンがプッシュされます。

デフォルト値

実用的なデフォルト値を列に適用できる場合は、これが必要ですが、これが既存のデータベースの場合は、多少の作業が必要になる場合があります。

早期に失敗する

アプリケーション、レポートスイート、インターフェースなどへのゲートウェイでデータの問題に単に対処するのは魅力的です。これだけに依存しないように強くお勧めします。他のウィジェットをDBに接続すると、同じ問題に直面する可能性があります。データ品質の問題に対処します。

2
Robbie Dee

ユースケースでNULLを適切なデフォルト値で安全に置き換えることができる場合はいつでも、SELECTまたはISNULLを使用してCOALESCE Sqlステートメントで変換を行うことができます。だから代わりに

 SELECT MyColumn FROM MyTable

書ける

 SELECT ISNULL(MyColumn,DefaultValueForMyColumn) FROM MyTable

もちろん、これはORMが選択ステートメントを直接操作できる場合、または生成用に変更可能なテンプレートを提供できる場合にのみ機能します。この方法で「実際の」エラーがマスクされていないことを確認する必要があります。そのため、デフォルト値による置換がNULLの場合に必要なものである場合にのみ適用してください。

データベースとスキーマを変更でき、dbシステムがこれをサポートしている場合は、@ RobbieDeeで提案されているように、特定の列にデフォルト値句を追加することを検討できます。ただし、これにより、データベース内の既存のデータを変更して以前に挿入されたNULL値を削除する必要があり、正しいインポートデータと不完全なインポートデータを後で区別する機能が削除されます。

私自身の経験から、ISNULLを使用すると驚くほどうまく機能することがわかっています-以前は、元の開発者がNOT NULL制約を多数の列に追加するのを忘れていたレガシーアプリケーションを維持する必要があり、後でこれらの制約を簡単に追加することはできませんでしたいくつかの理由で。しかし、すべてのケースの99%で、数値列のデフォルトとして0を、テキスト列のデフォルトとして空の文字列を完全に受け入れました。

1
Doc Brown

OPは、ビジネスルールとデータベースの技術的な詳細を結び付ける回答を想定しています。

これは「理論的には」発生してはならないので、発生した場合は不良データまたはコード内のバグを示しています。どのフィールドがnullであるかによって、エラーの重大度は異なります。つまり、一部のフィールドでは処理を停止して誰かに通知する必要があり、他のフィールドでは処理を続行して誰かに通知するだけにする必要があります。

これはすべてのビジネスルールです。ビジネスルールは、それ自体はnullを気にしません。データベースがnull、9999、 "BOO!"を持っている可能性があることはすべて知っています。 ...それは単なる別の値です。つまり、RDBMSでは、nullには興味深いプロパティがあり、ユニークな使用法には意味がありません。

重要なのは、指定されたビジネスオブジェクトの「null性」の意味だけです...

まれであるが可能性のあるnullエントリを処理するための優れたアーキテクチャまたは設計原則はありますか?

はい。

  • ビジネスルールをクラスに入れます。
  • 文字変換は、ビジネスクラスとデータストアを分離する適切なコードレイヤーで行う必要があります。 ORMコードに配置できない場合は、少なくともデータベースに配置しないでください。
  • ここでは、ビジネスルールはなく、データベースを可能な限り簡潔にしてください。 デフォルト値のような無害なものでさえあなたを噛みます。行ったことがある。
  • データベースとの間でやり取りされるデータを検証します。そしてもちろん、これはビジネスオブジェクトのコンテキスト内で行われます。

データ取得時に例外をスローするは意味がありません。

問題は「「不良」データを保存する必要があるか」状況によって異なります。

  • 不正なデータが使用されている可能性があります-無効なオブジェクトまたはオブジェクトコンポジットを保存しないでください。あらゆる場所での複雑なデータ/ビジネス関係。ユーザーはいつでも任意の機能を実行でき、おそらくそのビジネスエンティティをさまざまなコンテキストで使用できます。保存時の不良データの影響(存在する場合)は、将来の使用に大きく依存するため不明です。そのデータの統一された/単一のプロセスはありません。
  • 不良データがある場合は進行できません-不良データの保存を許可します。ただし、プロセスの次のステップは、すべてが有効になるまで続行できません。たとえば、自分の所得税を行う。データベースから取得されると、ソフトウェアはエラーを指摘し、有効性チェックなしにIRSに送信することはできません。
1
radarbob

Nullを処理するには多くの方法があるため、データベースレイヤーからアプリケーションレイヤーに移ります。


データベースレイヤー

ヌルを禁止することができます;ここではそれは非現実的ですが。

列ごとにデフォルトを設定できます

  • 列がinsertからabsentである必要があるため、明示的なnull挿入はカバーしていません
  • insertが誤ってこの列を逃した行からの検出を防ぎます

トリガーを設定して、挿入時に欠損値が自動的に計算されるようにすることができます。

  • この計算を実行するために必要な情報が存在する必要があります
  • insertが遅くなります

クエリレイヤー

不便なnullが存在する場合、行をスキップできます

  • メインロジックを簡素化します
  • 「不良行」の検出を防ぐため、それらを確認するには別のプロセスが必要になります
  • 各クエリをインストルメント化する必要があります

クエリでデフォルト値を指定できます。

  • メインロジックを簡素化します
  • 「不良行」の検出を防ぐため、それらを確認するには別のプロセスが必要になります
  • 各クエリをインストルメント化する必要があります

注:いくつかの自動化された方法でクエリを生成する場合、各クエリのインストルメントは必ずしも問題ではありません。


アプリケーション層

禁止されているnullのテーブルを事前チェックできます:

  • メインロジックを簡素化します
  • 故障までの時間を改善します
  • 事前チェックとアプリケーションロジックの整合性を保つ必要があります

禁止されたnullに遭遇すると、処理を中断できます

  • これは、どの列がnullになることができ、どの列ができないかという知識の重複を回避します
  • それはまだ比較的単純です(単にチェック+リターン/スロー)
  • プロセスを再開可能にする必要があります(すでに電子メールを送信している場合は、2度または100度送信したくない!)

禁止されたnullに遭遇したときに、行をスキップできます

  • これは、どの列がnullになることができ、どの列ができないかという知識の重複を回避します
  • それはまだ比較的単純です(単にチェック+リターン/スロー)
  • プロセスが再開可能である必要はありません

禁止されたnullに遭遇したときに、一度に1つずつ、またはバッチで通知を送信できます。これは、他の方法を補完します。上に提示。ただし、最も重要なのは「次に何をするか」です。特に、行にパッチが適用され、再処理が必要な場合は、すでに処理された行と必要な行を区別する方法があることを確認する必要があります。再処理されています。


あなたの状況を考慮して、私はアプリケーションで状況を処理し、次のいずれかを組み合わせます:

  • interruptおよびnotify
  • skipおよびnotify

特に処理に時間がかかる場合は、ある程度の進歩を保証するために、可能であればスキップする傾向があります。

スキップされた行を再処理する必要がない場合は、それらをログに記録するだけで十分であり、スキップされた行の数をプロセスの最後に送信する電子メールは適切な通知になります。

それ以外の場合は、行を修正(および再処理)するためにサイドテーブルを使用します。このサイドテーブルは、単純な参照(外部キーなし)または本格的なコピーのいずれかです。後者は、より高価な場合でも、nullに対処する時間がない場合に必要です。メインデータをクリーンアップします。

0
Matthieu M.