データベースの設計が不十分なリレーショナルデータベース駆動型アプリケーションでより良いオブジェクト指向コードを作成する方法


19

私は主に、すべてのページに複数のテーブルとそれらのテーブルに適用されるフィルターがある類似したページの束で構成されるJava Webアプリケーションを作成しています。これらのテーブルのデータは、SQLデータベースから取得されます。

私はmyBatisをORMとして使用していますが、データベースの設計が貧弱で、mybatisはデータベース指向のツールであるため、私の場合はこれが最良の選択ではないかもしれません。

データベースの設計が貧弱であるため、クエリが非常に異なる可能性があるため、類似したものに対して異なるクエリを作成する必要があるため、多くの重複コードを記述していることがわかりました。つまり、クエリを簡単にパラメータ化することはできません。これは私のコードに伝播し、単純なループでテーブルの列に行を入力する代わりに、次のようなコードがあります:

取得Aデータ(P1、...、PI);

get B Data(p1、...、pi);

Cデータの取得(p1、...、pi);

Dデータの取得(p1、...、pi); ...

そして、異なる列を持つ異なるテーブルがある場合、これはすぐに爆発します。

また、ページ内のhtml要素へのオブジェクトのマッピングである「ウィケット」を使用しているという事実も複雑さを増しています。そのため、私のJavaコードはデータベースとフロントエンドの間のアダプターになります。これにより、ロジックが混在した大量の配線、定型コードが作成されます。

正しい解決策は、ORMマッパーを、dbへのより均質なインターフェースを提供する追加レイヤーでラップすることでしょうか、または私が書いているこのスパゲッティコードを処理するより良い方法はありますか?

編集:データベースに関する詳細情報

データベースは、主に電話情報を保持しています。貧弱なデザインは以下で構成されています:

ドメインの知識とは関係のない人工キーを主キーとして持つテーブル。

一意のトリガー、チェック、または外部キーは一切ありません。

さまざまなレコードのさまざまな概念に一致する一般的な名前のフィールド。

条件が異なる他のテーブルと交差することによってのみ分類できるレコード。

文字列として保存される数値または日付である列。

まとめると、散らかった/怠zyなデザインです。


7
データベース設計の修正はオプションですか?
–RMalke

1
データベースの設計が不十分である方法を説明してください。
Tulainsコルドバ

@Renan Malke Stigliani残念ながら、それに依存するレガシーソフトウェアがあるため、残念ながらそうではありませんが、若干異なるデザインのテーブルの一部をミラーリングし、コードを簡素化しています。しかし、私は誇りに思って、こののではないと私は無差別にかなり重複するテーブルをたくありません
DPM

1
この本は、datbaseの問題を修正し、レガシーコードを機能させ続ける方法のアイデアを提供するかもしれません:amazon.com/…–
HLGEM

4
あなたがリストした問題のほとんど。。。ありません。最近では、自然キーではなく代理キーを使用することは、実際にはかなり標準的な推奨事項です。「貧弱なデザイン」ではありません。制約の欠如と不適切な列タイプの使用は、「貧弱な設計」に関する限り、より良い例ですが、実際にアプリケーションコードに影響を与えるべきではありません(これらの問題を悪用する予定がない限り)。
-ruakh

回答:


53

これらのタイプのシナリオが発生するため、オブジェクト指向は特に価値があり、複雑さをカプセル化できる抽象化を合理的に設計するツールを提供します。

ここでの本当の質問は、その複雑さをどこでカプセル化しますか?

それでは、少し前に戻り、ここで言及している「複雑さ」についてお話しましょう。あなたの問題(私が理解しているように;私が間違っている場合は修正してください)は、データで完了する必要があるタスクに効果的に使用できるモデルではない永続モデルです。これは、他のタスクのための効果的かつ使用できますが、ないためにも、あなたの仕事。

では、平均値の良いモデルを提示しないデータがある場合はどうしますか?

翻訳します。できるのはそれだけです。その翻訳は私が上で言及した「複雑さ」です。モデルを翻訳することを受け入れたので、いくつかの要因を決定する必要があります。

両方向に翻訳する必要がありますか?次のように、両方向は同じように翻訳されますか?

(Tbl A、Tbl B)-> Obj X(読み取り)

Obj X->(Tbl A、Tbl B)(書き込み)

または、挿入/更新/削除アクティビティは、Obj Xとしてデータを読み取りますが、データはObj Yから挿入/更新されるような異なるタイプのオブジェクトを表しますか?これらの2つの方法のどちらを使用するか、または更新/挿入/削除ができない場合は、翻訳を配置する場所の重要な要素です。


どこで翻訳しますか?

この回答で私が最初に述べた声明に戻ります。OOを使用すると、複雑さをカプセル化できます。ここで言及するのは、必要なだけでなく、すべてのコードに漏れ出ないようにするには、その複雑さをカプセル化する必要があるという事実です。同時に、完璧な抽象化ができないことを認識することが重要です。そのため、非常に効果的で使いやすい抽象化を持つことよりも、それについて心配する必要はありません。

再び今。あなたの問題は次のとおりです。この複雑さをどこに置きますか?さてあなたには選択肢があります。

ストアドプロシージャを使用てデータベースで実行できます。これには、ORMでうまくプレイできないことが多いという欠点がありますが、常にそうとは限りません。ストアドプロシージャには、パフォーマンスなどの利点があります。ただし、ストアドプロシージャには多くのメンテナンスが必要になる場合がありますが、特定のシナリオを分析し、メンテナンスが他の選択肢よりも多いか少ないかを判断するのはユーザー次第です。私は個人的にストアドプロシージャに非常に熟練しており、そのため、利用可能な人材のこの事実はオーバーヘッドを削減します。知っていることに基づいて意思決定を行うことの価値を過小評価しないでください。ユーザーまたはチームが最適なソリューションよりも優れたソリューションを作成および保守する方法を知っているため、次善のソリューションが正しいソリューションよりも最適になる場合があります。

別のデータベース内オプションはビューです。データベースサーバーに応じて、これらは非常に最適または次善であるか、まったく効果的ではない場合があります。欠点の1つは、データベースで使用可能なインデックスオプションに応じたクエリ時間です。データの変更(挿入/更新/削除)を行う必要がない場合は、ビューがさらに良い選択になります。

リポジトリパターンを使用する古いスタンバイがあるデータベースを通過します。これは実績のあるアプローチであり、非常に効果的です。欠点にはボイラープレートが含まれる傾向がありますが、十分にファクタリングされたリポジトリはこれをある程度回避でき、これらが不幸な量のボイラープレートをもたらす場合でも、リポジトリは単純なコードであり、理解しやすく、維持しやすく、優れたAPIを提示する傾向があります/ abstraction。また、リポジトリは、データベース内オプションを使用すると失われる単体テスト可能性に適しています。

自動マッパーのようなツールがあり、ORMを使用してデータベースモデルからormを使用可能なモデルに変換できる可能性がありますが、これらのツールの一部は、魔法のように振る舞うことを維持/理解するのが難しい場合があります。ただし、最小限のオーバーヘッドコードを作成するだけで、十分に理解されていればメンテナンスのオーバーヘッドが少なくなります。

次に、データベースからさらに一歩踏み出します。これは、非翻訳永続モデルを処理するコードの量が増えることを意味し、これは本当に不快になります。これらのシナリオでは、UIに翻訳レイヤーを配置することについて話します。これは一般的に非常に悪い考えであり、時間の経過とともにひどく減衰します。


今の話を始めましょう狂いました

存在するのObjectは、すべてがすべてを網羅した抽象化だけではありません。コンピュータ科学が研究されてきた長年にわたって、そしてそれ以前にも数学の研究から、多くの抽象化が開発されてきました。私たちが創造的になり始めるなら、研究されている利用可能な既知の抽象化について話を始めましょう。

アクターモデルがあります。これは、他のコードにメッセージを送信するだけで、他のコードにすべての作業を効果的に委任するだけで、複雑さをすべてのコードからカプセル化するのに非常に効果的であるため、これは興味深いアプローチです。これは、「Obj XをYに送信する必要がある」というメッセージを俳優に送信し、場所Yで応答を待機し、Obj Xを処理するレセプタクルがある限り機能します。 「Obj Xと計算Y、Zが必要です」そして、待つ必要さえありません。翻訳はそのメッセージパスの反対側で行われ、その結果の読み取りが必要ない場合は先に進むことができます。これは、目的のためにアクターモデルをわずかに乱用する可能性がありますが、すべて依存しています。

別のカプセル化境界はプロセス境界です。これらは、複雑さを非常に効果的に分離するために使用できます。通信が単純なHTTPであるSOAP、RESTを使用するWebサービスとして、または独自のプロトコルが必要な場合(推奨されない)、翻訳コードを作成できます。STOMPは完全に悪い新しいプロトコルではありません。または、システムローカルで公開されているメモリパイプを備えた通常のデーモンサービスを使用して、選択したプロトコルを使用して再び非常に迅速に通信します。これには実際にかなり良い利点があります:

  • 古いバージョンと新しいバージョンのサポートの翻訳を同時に行う複数のプロセスを実行して、翻訳サービスを更新してオブジェクトモデルV2を公開し、後で個別に新しいコードを使用するように消費コードを更新できます。モデル。
  • プロセスをパフォーマンスのためにコアに固定するなどの興味深いことを行うことができます。また、このアプローチでは、そのデータに触れるセキュリティ特権で実行される唯一のプロセスを作成することにより、セキュリティの安全性を確保します。
  • 翻訳スペースでコードを書くことは翻訳スペースの外で呼び出すことができないため、固定されたままになるプロセス境界について話しているとき、非常に強力な境界が得られます。プロセス範囲を共有せず、契約ごとに使用シナリオの固定セットを確保します。
  • 非同期/非ブロッキング更新がより簡単になります。

欠点は明らかに一般的に必要なものよりも多くのメンテナンスであり、通信オーバーヘッドがパフォーマンスとメンテナンスに影響します。


複雑さをカプセル化する方法は非常に多様であり、その複雑さをシステムのさらに奇妙で奇妙な場所に配置することができます。高次関数の形式(多くの場合、戦略パターンまたはオブジェクトパターンのその他のさまざまな奇妙な形式を使用して偽造されたもの)を使用すると、非常に興味深いことができます。

そうです、モナドについて話を始めましょう。必要な独立した翻訳を行う小さな特定の関数の非常に独立した方法でこの翻訳レイヤーを作成できますが、これらの翻訳関数をすべて非表示にして、外部コードからほとんどアクセスできないようにします。これには、外部コードにあまり影響を与えずに簡単に変更できるように、それらへの依存を減らすという利点があります。次に、優れたOOモデルタイプオブジェクトのいずれかで機能する高階関数(匿名関数、ラムダ関数、ストラテジオブジェクト、ただしそれらを構造化する必要があります)を受け入れるクラスを作成します。次に、これらの関数を受け入れる基礎コードに、適切な変換メソッドを使用したリテラル実行を実行させます。

これにより、すべてのコードから離れた境界の反対側にすべての翻訳が存在するだけでなく、境界が作成さます。それはその側でのみ使用され、コードの残りの部分は、その境界のエントリポイントがどこにあるか以外、それについて何も知ることさえできません。

ええ、それは本当におかしな話ですが、誰が知っていますか。あなたはただ狂ったかもしれません(真剣に、88%未満の狂気の評価でモナドを引き受けないでください、人体傷害の本当の危険があります)。


4
うわー、驚くほど包括的な答え。SEだけが許可してくれたら、私はこれを複数回支持します。
マルジャンヴェネマ

11
映画版はいつ発売されますか?
ヤニス

3
わーい!この答えをブックマークして、娘が年をとったときに見せます。
トンバトロン

4

私のおすすめ:

次のデータベースビューを作成します。

  1. 列に意味のある名前を付ける
  2. 「条件の異なる他のテーブルとの交差」を実行して、その複雑さを隠します。
  3. 文字列として保存されている数値または日付をそれぞれ数値と日付に変換します。
  4. いくつかの基準に従って、存在しない個性を作成します。

アイデアは、悪いものの上に優れたデザインをエミュレートするファサードを作成することです。

次に、ORMを実際のテーブルではなく、そのファサードに関連付けます。

ただし、これは挿入を単純化しません。


データベースビューの使用は素晴らしいアイデアのように見えますが、最もエレガントな一連のアクションによって、最低レベルのさを抽象化し、なんらかの理由で考慮していませんでした。ありがとうございました。
DPM

3

既存のデータベーススキーマによって、より適切に設計されたスキーマで抽象化される可能性のあるタスクに対して、より具体的なコードとクエリを作成する方法がわかりますが、優れたオブジェクト指向コードを書く能力を妨げるべきではありません。

  • 固い原則を忘れないでください。
  • 簡単に単体テストできるコードを記述します(多くの場合、SOLIDの原則に従っています)。
  • ビジネスロジックを表示ロジックとは別にしてください。
  • Apache Wicketのドキュメントと例を読んでください-このフレームワークはおそらくあなたが思っているよりも多くの定型コードを節約できるので、それを効果的に使用する方法を学んでください。
  • データベースを処理する必要のあるロジックは、ビジネスロジックで使用できるクリーンなインターフェイスを提供する別のレイヤーに保持します。このように、あなた(または将来のメンテナー)がスキーマを改善する機会を得た場合、ビジネスロジックにあまり多くの変更を加えることなく、スキーマを改善できます。

完璧ではないデータベーススキーマで作業していることに気付いたとき、それが仕事を難しくするすべての方法に不満を抱くのは簡単ですが、ある時点でそれらの苦情を捨てて、それを最大限に活用する必要があります。

不完全なスキーマにも関わらず、クリーンで再利用可能で保守が容易なコードを作成するためにあなたの創造性を活用する機会と考えてください。


1

オブジェクト指向コードの改善に関する最初の質問に答えて、SQLを話すオブジェクトを使用することをお勧めします。ORMはオブジェクトを操作するため、オブジェクト指向の原則に本質的に反します。OOPのオブジェクトは自給自足のエンティティであり、目標を達成するためのすべてのリソースを持っています。このアプローチにより、コードがより簡単になると確信しています。

問題空間、つまりあなたのドメインについて話して、集約ルートを特定しようとします。これらはドメインの一貫性の境界です。常に一貫性を保持する必要がある境界。集計は、ドメインイベントを介して通信します。システムが十分に大きい場合は、おそらくサブシステム(SOA、マイクロサービス、自己完結型システムなど)で分割を開始する必要があります。

また、CQRSの使用を検討します-書き込み側と読み取り側の両方を大幅に簡素化できます。このトピックに関するUdi Dahanの記事を必ずお読みください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.