ビジネスロジックでのデータベース値の参照


43

これはハードコーディングとベストプラクティスに関する別の質問だと思います。データベースに保存されている値のリストがあるとしましょう(テーブルはSSRSレポートなどの他の目的に使用されるため、データベースに存在する必要があります)。

1 Apple 
2 Banana 
3 Grapes

それらをユーザーに提示し、ユーザーが1つ選択すると、FavouriteFruitとしてプロファイルに保存され、IDがデータベースのレコードに保存されます。

ビジネスルール/ドメインロジックに関しては、特定の値にロジックを割り当てるための推奨事項は何ですか。ユーザーがGrapesを選択した場合、追加のタスクを実行したい場合、Grapes値を参照する最良の方法は次のとおりです。

// Hard coded name
if (user.FavouriteFruit.Name == "Grapes")

// Hard coded ID
if (user.FavoriteFruit.ID == 3) // Grapes

// Duplicate the list of fruits in an enum
if (user.FavouriteFruit.ID == (int)Fruits.Grapes)

または、他の何か?

もちろん、FavouriteFruitはアプリケーション全体で使用されるため、リストは追加または編集できます。

誰かが「ブドウ」の名前を「ブドウ」に変更したいと思うかもしれませんが、これはもちろんハードコードされた文字列オプションを壊します。

ハードコードされたIDは完全には明確ではありませんが、示されているように、コメントを追加するだけで、どのアイテムであるかをすばやく識別できます。

enumオプションには、データベースからのデータの複製が含まれますが、同期が取れなくなる可能性があるため、間違っているようです。

とにかく、コメントや提案を事前に感謝します。


1
みんなありがとう:提案と一般的なアドバイスは本当に役に立ちます。@RemcoGerlichは、表示目的で使用される文字列の懸念と、より読みやすいコードのルックアップコードとしての別の懸念を分離するというアイデアは非常に優れています。
ケイト

1
@Mike Nakisにプリロードされたオブジェクトのアイデアを与えますが、これは両方の世界で最高のようです。
ケイト

1
最初のソリューションのバリエーションをお勧めします。テーブルに処理方法の3列目を含め、そのフィールドを使用して実行するコードを決定します。表示フィールドではなく、複数の果物間で共有できます。
キックスタート

1
enumオプションには、データベースからのデータの複製が含まれますが、同期が取れなくなる可能性があるため、間違っているようです。私は実際にこれが好きです。複式簿記のようなものです。元帳の両側のバランスが取れていない場合、何かが間違っていることがわかります。物事を変えることがより慎重になります。
レーダーボブ

1
うーん... IDと文字列の1:1の関係がある場合、これは冗長であり、両方を持つことは無意味です。文字列は、整数だけでなくDBキーとしても機能します。 MyApplication.Grape.IDいわば、st音です。「Apple」は「Red_Apple」ではなく、IDが3でも4です。したがって、「Apple」を「Red_Apple」に名前変更する可能性は、3が4(さらには3)であることを宣言する以上に意味がありません。enumのポイントは、その数値DNAを抽象化することです。ですから、ビジネスモデルで文字通り意味を持たない任意のリレーショナルDBキーを実際に分離する時が来たのかもしれません。
レーダーボブ

回答:


31

文字列と魔法の定数は一切犠牲にします。 それらは完全に問題外であり、オプションと見なすべきではありません。これにより、実行可能なオプションは1つだけになります。識別子、つまり列挙型です。ただし、もう1つのオプションもあります。これは私の意見では最高です。このオプションを「プリロードされたオブジェクト」と呼びましょう。プリロードされたオブジェクトを使用すると、次のことができます。

if( user.FavouriteFruit.ID == MyApplication.Grape.ID )

ここで起こったことは、明らかに行全体Grapeをメモリにロードしたことです。そのため、比較で使用する準備ができたIDがあります。オブジェクトリレーショナルマッピング(ORM)を使用している場合は、さらに良く見えます。

if( user.FavouriteFruit == MyApplication.Grape )

(だから私はそれを「プリロードされたオブジェクト」と呼んでいます。)

そのため、私は、起動時にすべての「列挙」テーブル(曜日、年の月、性別などの小さなテーブル)をメインアプリケーションドメインクラスにロードします。明らかに、MyApplication.Grape「グレープ」と呼ばれる行を受信する必要があるため、名前でそれらをロードし、それらのすべてが見つかったと断言します。そうでない場合は、起動時に保証されたランタイムエラーが発生します。これは、すべてのランタイムエラーの中で最も悪性度が低いものです。


17
私は答えに同意しませんが、「文字列と魔法の定数をすべてのコストで避ける」ことの必須事項は残りの答えに同意しないと思います。実際に、魔法の定数または文字列が使用される場所少なくとも1つ必要です「プリロードされたオブジェクト」にデータを入力します。そこので、それは、私が思うに、注目すべきだですが、通常より多くのそれの価値よりも、難読化だが、完全に「文字列と魔法の定数」を回避する方法は...
svidgen

2
@svidgenは、同じ名前のレコードの内容をロードするために、場所ごとに名前によるバインディングと名前によるバインディングを一度だけ分散させることと、それだけを行うこととの間に根本的な違いがあることに同意しませんか?起動時、実行時エラーはコンパイルエラーとほとんど同じくらい良性ですか?とにかく、あなたが言及した難読化にもかかわらず、名前によるわずかなバインディングさえも回避する方法は常に興味深いので、あなたが心に持っているものを聞きたいです。
マイクナキス

ああ、私は完全に同意します。そして、OPの性質を考えると、この答えは「すべてのコストで」を「可能な場合はいつでも」または同様の何かに変更することから恩恵を受けることをお勧めします。...もっと時間があるなら、完全を期すために、ある種のメタプログラミングのナンセンスを扱った答えを書きます...しかし、それはおそらくOP(またはほとんどの場合誰でも)が必要とするものではありません。しかし、メタプログラミングソリューションは、最初の声明にそのまま一致します。
svidgen

1
@ user469104の違いは、IDが変更される可能性があり、アプリケーションがすべての行を正しくロードし、すべての比較を正しく実行することです。また、好きな方法でコードをリファクタリングし、行の名前を自由に変更できます。修正するものを探す必要があるのはアプリケーションの起動時のみであり、非常に明白になる傾向がありGrape = fetchRow( Fruit.class, NameColumn, "Grape" ); ます。何か間違ったことをするAssertionErrorと、あなたに知らせます。
マイクナキス

1
@grahamparksはenum魔法の文字列であったはずです。ポイントは、すべての名前によるバインディングを1か所集中させること、起動中にすべてを検証すること、およびタイプセーフティです。
マイクNakis

7

文字列に対するチェックは最も読みやすいですが、二重の役割を果たします。識別子と説明の両方として使用されます(無関係な理由で変更される場合があります)。

私は通常、両方の職務を別々のフィールドに分割します。

id  code    description
 1  grape   Grapes
 2  apple   Apple

説明は変更される場合がありますが(「ブドウ」から「バナナ」は変更されません)、コードは変更できません。

これは主に、IDがほとんど常に自動生成されるためであり、適切ではありません。IDを自由に選択できる場合は、IDが常に正しいことを保証して使用することができます。

また、「ブドウ」を「ブドウ」に実際に編集する頻度はどのくらいですか?たぶんこれは必要ありません。


8
私は...まだ多くの冗長性が答えだとは思わない
ロビーディー

4
私もこのオプションを検討し、試してみましたが、これが最終的に起こったことです。ある時点で、「apple」を「green_apple」と「red_apple」に区別する必要がありました。しかし、「apple」はコード内の無数の場所ですでに使用されているため、名前を変更できなかったため、「apple」と「green_apple」が必要でした。そしてその結果、私のシェルドンは私がそこに行き、すべてを「プリロードされたオブジェクト」にリファクタリングするまで、数晩寝ることを妨げました。(私の答えを参照してください。)
マイクナキス

1
私は間違いなくあなたのプリロードされたオブジェクトが好きですが、もしあなたの「リンゴ」が差別化されているなら、とにかくすべてを調べなければなりませんか?
RemcoGerlich

国際化をサポートするために、説明名に別のテーブルを用意することもできます。
エリックイド

1
@MikeNakisとリファクタリングは、基本的に、Fruit.AppleをFruit.GreenAppleに置き換えるコードベース全体の検索と置換です。ハードコードされた文字列値を使用する場合、コードベース全体で検索と置換を実行して、「apple」を「green_apple」とほぼ同じものに置き換えます。-リファクタリングは、IDEがReplacingを実行しているため、気分が良くなります。
ファルコ

4

ここで期待しているのは、変化するデータに自動的に適応できるプログラミングロジックです。列挙型のような単純な静的オプションは、ランタイムに追加の列挙型を適切に追加できないため、ここでは機能しません。

私が見たいくつかのパターン:

  • Enums + defaultは、プログラムの日を台無しにする新しいデータベースエントリから保護します。
  • データベース自体で実行されるアクション(ビジネスロジック)のエンコード。多くの場合、多くのロジックが再利用されるため、これは非常に可能です。ロジックの実装はプログラム内にある必要があります。
  • データベース内の追加の属性/列により、プログラムが正しく展開されるまで、プログラム内でブランドの新しい値を「無視される」ものとしてマークします。
  • データベースから値をロード/リロードするコードパスの周りの高速メカニズムを失敗させます。(対応するアクションがプログラム内になく、無視するようにマークされていない場合は、更新しないでください)。

一般に、アクション自体が他の場所で実装されている場合でも、データが意味するアクションを参照するという点でデータが完全であることが好きです。データに依存しないアクションを決定するコードは、データ表現を断片化しただけであり、これはおそらく分岐してバグにつながる可能性があります。


4

両方の場所(テーブルとENUM)に保存するのはそれほど悪くありません。その理由は次のとおりです。

データベーステーブルに保存することにより、外部キーを介してデータベースの参照整合性を強化できます。そのため、フルーツに個人またはエンティティを関連付けると、データベーステーブルに存在するのはフルーツだけになります。

それらをENUMとして保存するのも理にかなっています。マジックストリングなしでコードを記述でき、コードが読みやすくなるからです。はい、同期を維持する必要がありますが、ENUMに行を追加し、データベースに新しい挿入ステートメントを追加するのは本当に難しいでしょう。

1つ、ENUMが定義されたら、その値を変更しないでください。たとえば、次の場合:

  • 林檎
  • 葡萄

グレープの名前をブドウに変更しないでください。新しいENUMを追加するだけです。

  • 林檎
  • 葡萄
  • ぶどう

データを移行する必要がある場合は、更新プログラムを適用して、すべてのブドウをブドウに移動します。


さらなるステップとして、私は、メタデータ値が使用されてはならないことを示すためにテーブルに削除フラグがあるショップで働いてきました(廃止されたか、新しいバージョンが存在します)。
ロビーディー

1

あなたはこの質問をする権利があります。不正確な条件の評価に対して防御しようとしているので、それは実に素晴らしい質問です。

そうは言っても、評価(あなたのif条件)は必ずしもあなたがそれを回避する方法の焦点である必要はありません。代わりに、「非同期」問題の原因となる変更を伝達する方法に注意してください。

ストリングアプローチ

文字列を使用する必要がある場合は、UIを使用してリストを変更する機能を公開してみませんか?たとえば、に変更GrapeするとGrapes、現在参照しているすべてのレコードを更新するようにシステムを設計しますGrape

IDアプローチ

読みやすさの妥協にもかかわらず、私は常にIDを参照することを好みます。The list may be added toこのようなUI機能を公開した場合に通知されることもあります。IDを変更するアイテムの並べ替えに懸念がある場合は、そのような変更をすべての依存レコードに再度伝播します。上記と同様。別のオプション(適切な正規化規則に従って、enum / id FruitDetailカラムを使用し、ルックアップ可能な 'Order'カラムがあるより詳細なテーブルを参照します)。

いずれにせよ、リストの修正や更新を制御することを提案しています。ORMを使用してこれを行うか、他のデータアクセスを使用するかは、テクノロジの仕様によって決まります。基本的に、あなたがしていることは、そのような変更のためにDBから離れている人々に要求することです-私はそれが良いと思います。ほとんどの主要なCRMは同じ要件を満たします。


1
データベースでは、特にその問題を回避するために、子レコードの数値IDが保存されています。この質問は、プログラミング言語とのインターフェイス方法に関するものです。
時計仕掛けミューズ

1
@ Clockwork-Muse-どの問題を避けるために?それは意味がありません。
JᴀʏMᴇᴇ

私はIDアプローチをかなり使用していますが、IDはロックされており、変更できません。添付された文字列は、「ローリー」というものの名前を変更したいことが多いため、「ID」で表されるもの自体は変更されないため、「トラック」などになることが多いためです。
ブライアンノブラウチ

IDアプローチを採用する場合、開発データベースと本番データベースをどのように扱いますか?自動インクリメントIDを使用すると、両方のDBに異なる順序でアイテムを追加すると、異なるIDになります。
プロテクター

ただし、自動インクリメントする必要はありませんか?この場合、特に使用している列挙型の整数値である場合は、そうではありません。
JᴀʏMᴇᴇ

0

非常に一般的な問題。データクライアント側の複製はDRYの原則に違反しているように思えるかもしれませんが、実際にはレイヤー間のパラダイムの違いによるものです。

列挙型(または何でも)がデータベースと同期していないことも、実際にはそれほど珍しいことではありません。クライアント側のコードではまだ使用されていない新しいレポート機能をサポートするために、メタデータテーブルに別の値をプッシュした可能性があります。

それは逆の場合もあります。新しい列挙値がクライアント側に追加されますが、DBAが変更を適用できるようになるまでDBの更新を行うことはできません。


はい、問題を説明しました。あなたのソリューションは何ですか?
プロテクター

1
@Protectoroneあなたが想定している私の経験では、誤った仮定である銀の弾丸ソリューションを。期待できる最善の方法は、一部のビジネスエンティティが問題ドメインを所有しているため、少なくともクライアント側とデータベース側のどちらが遅れているかを確認できることです。銀行業と金融業は通常、この点で非常に効率的です。小売部門はそれほど顕著ではありません...
ロビーディー

0

本質的に静的ルックアップとは何かについて話していると仮定すると、3番目のオプション-列挙-は基本的に唯一の正気の選択です。データベースが関与していなかった場合に行うことは理にかなっています。

この質問は、データベース内の列挙型と静的/ルックアップテーブルの同期を維持する方法についての質問になります。残念ながら、それは私がまだ完全な答えを持っている問題ではありません。

選択により、すべてのスキーマメンテナンスをコードで実行するため、アプリケーションのビルドと予想されるスキーマバージョンとの関係を維持できるため、ルックアップと列挙型の同期を維持するのは簡単ですが、覚えておく必要があります行う。それがより自動化されていればより良いでしょう(そして列挙とルックアップが一致することを確認するための自動化された統合テストも役立ちます)が、それは私が今までに実装したものではありません。


1
これらは単なる静的なルックアップだとは思いません。そうでなければ、データベースから取得してそのまま使用することができます。私が理解している問題は、使用されるルックアップ値に応じてビジネスロジックを適用する場合です。しかし、それはさておき、はい-列挙型は一般的にこの目的で使用されます。
ロビーディー

わかりました、私はあなたが記述するコンテキストが私が意味したものである「静的ルックアップのための」よりよい用語を必要とします:)キーは「静的」です-これらは問題を変更しない値は新しい値を追加し、「ラベル」を変更します(既存の値の意図ではありません)。
マーフ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.