「マルチDBMSアプリケーション」が目指すデータベースとのかかわり方

https://qiita.com/e99h2121/items/efd3e12a526816775aab

の続編、「マルチDBMSアプリケーション」にまつわる話を、筆者のデータベースマイグレーション時のノウハウ資料やメモから書き起こしたもの。内容はごった煮。


## この記事における「マルチDBMSアプリケーション」の定義

以下のようなことを目指すアプリケーション

- 特定のプラットフォーム(ハードウェア、OS)に依存しない
- 特定のDBMSに依存しない


## なぜマルチDBMSアプリケーション

理想は以下

- 様々な顧客案件・用件に対応できるようにしたい
- 将来的に、価格面・機能面がより優位なDBMSを選択したい
- ベンダーロックインを回避したい。具体的には
    - デザイン/プログラムのロックイン
    - 開発者のスキルの固定化
    - 開発者の確保が困難な場面
    - テクノロジー・トレンドに乗り遅れる可能性
    - ライセンスや価格面での環境変化
    - サポート切れ


## 目指す「マルチDBMSアプリケーション」

- マルチベンダーへの対応がしやすくなるアプリケーション

### アプリケーション

- 業界標準/手順のサポート
- 他のシステムと容易に連携できる

### 開発環境・言語

- ベンダー独自の言語は使わない
- ベンダー独自の開発環境は使わない

### アーキテクチャ・API

- ベンダー独自のAPIを使わない
- スタンダードなアーキテクチャを採用する

### ミドルウェア

- ベンダー独自の製品・機能は使わない

### OS

- 1種類のプログラムで多様なOSに対応できる

### ハードウェア

- 1種類のプログラムで多様なハードウェアに対応できる

## 考えるべきポイント

- 包括的な標準仕様など存在しない
- ロックインを忌避することで設計や実装で制約を受ける場面が考えられる
    - 「便利な機能」が使えない
    - パフォーマンス・チューニングが必要
- 「マルチDBMSアプリケーション」は開発コストは高くつく
    - 設計・開発・テスト 全てマルチDB毎分けるなど

## 検討の２つの道筋

- 既存のアプリケーションをマルチDB対応にする
- 新規のアプリケーションをマルチDBとして開発する

## とはいえ「No Silver Bullet」

- マルチDBMSアプリケーション実装の銀の弾丸は存在しない
- ただし、現実に各社はマルチDBMS対応版のパッケージを出荷しているし、様々な工夫や試みが行われているのも事実

## マルチDBMSを目指すなら

### 計画する

- 「作って、後でポーティング」は苦しい

### DBMSの提供する便利な独自機能を避け、なるべく標準に準拠する

- 何が独自で、何が標準なのか
- なるべく厳格なDBMSを使用して開発を行う

### チームの開発者全員が各DBMSの相違点を正しく理解して開発するのは、難しい

- ルールで縛るだけでは不足。しかけが必要。
- データアクセス層を分離する。一部の開発者にしかDBへアクセスするコードは書かせない等

### テストに自動化や突合などの工夫をする

-   検収基準にするのも一案

### 「オープン・スタンダード準拠」はオプション扱いとする

- 昨今はどのようなRDBでも「SQL標準をサポート」と言える
    - にもかかわらず何故互換性が無いのか
- 「プロプラエタリ部分との境目が明確かどうか」に注目すべき
    - 「標準構文でも書ける」ことと「標準を守らせる」ことは別の話
    - 「知らない間に」「意識せずに」プロプラエタリなコードになる危険性
- 例) JOIN
    - 独自構文でも書ける
    - SQL92準拠構文でも書ける
    - しかしチェック機構が無い

### プロジェクトの観点は
-   テーブルデザイン
-   アプリケーションデザイン
-   開発・テスト

### 命名規則や制限値は

- まずはターゲットDBMSを決めること
-   ターゲットとするDBMSの仕様を調査の上、**最も厳しいもの** を採用する
-   命名規則の例
    -   オブジェクトごとの名前の最大長
    -   利用可能な文字
    -   大文字・小文字の区別有無
-   制限値の例
    -   1テーブルに保有できるカラム数
    -   1テーブルに定義できるINDEXの最大数
    -   1INDEXで含められるキーの数
    -   1ページに格納可能な行の数

### カラムの属性と最大長は

- ターゲットとするDBMSの仕様を調査の上、最も厳しいものを採用する
- 利用を許すデータタイプの標準化が必要
    - 日付・ラージオブジェクト・独自データタイプをどうするか

### NULLの扱いは

- NULLと'' (空文字列)を区別するかどうか
- NULLをUNIQUE INDEXに含められるかどうか
- NULLをプライマリキー付のカラムに含められるかどうか
- NULLをストレージ上どのように保有するか
- 空文字をNULLの意味で使用しない
- テーブル設計でNULLを排除
    - 平均をとる場合に結果が違ってくるケース

- NULLと長さゼロ文字列('')
    - 例えばOracleではNULLと'' 長さゼロの文字列 の区別なし(NULLと扱われる)
    - 例えばDB2では両者は別のものとして扱われる

**Oracle**

`CREATE TABLE T1 ( KEY CHAR(04), C1 CHAR(04));`
`INSERT INTO T1 VALUES('NULL', NULL); `
`INSERT INTO T1 VALUES('QUOT',  '' );`
`SELECT * FROM T1 WHERE C1 IS NULL;`

→ 2行とも取れる

`SELECT * FROM T1 WHERE C1 =  '' ;`

→ 結果ゼロ

**DB2**

`CREATE TABLE T1 ( KEY CHAR(04), C1 CHAR(04));`
`INSERT INTO T1 VALUES('NULL', NULL); `
`INSERT INTO T1 VALUES('QUOT',  '' );`
`SELECT * FROM T1 WHERE C1 IS NULL;`

→ 'NULL' のレコードが返ってくる

`SELECT * FROM T1 WHERE C1 =  '' ;`

→ 'QUOT' のレコードが返ってくる


### 日付・時間は

-   非常によく使われる
-   日付の表記および日付の計算はDBMS独自の方式が多い
-   ロジックで実装しSQLに任せないのが賢明か

### 行連鎖は

-   一行のデータを複数ページに跨って格納できるかどうか
-   テーブル設計に与える影響が大きい
    -   横に非常に長いテーブル(かつNULL多し)
    -   定義できない場合有り

### DDL (Data Definition Language、データを定義する言語) は

-   DDLを生成/管理するしかけがあるとベター
    -   市販ツール
    -   表計算ソフトから生成するツールを手作り
-   物理属性の指定はDBMSごとに異なる
    -   テーブルスペース
    -   ディスク容量の指定
    -   圧縮その他のオプション
    -   DBMSの自動ストレージ機能の活用も選択肢に (指定が楽になる)

## ということでアプリケーションデザイン

-   まずストアドプロシージャありきで考えない
-   ビジネスロジック層とデータアクセス層をきっちり分離する
    -   ビジネス層にSQLおよび関連要素は一切書かない・書かせない
    -   既にある程度確立したデザインパターンがある
    -   両者のインターフェースの設計が必要になる

### 2つのアプローチ

-   RDBMSの相違を吸収できる既存のツールやフレームワークに依存する
    -   O-Rマッピングツール
    -   商用フレームワークやアプリ生成ツール
    -   万能とは思われない
    -   ツールやフレームワークにロックインされるので、勝ち馬に乗る必要がある
-   自作フレームワーク
    -   技術情報は蓄積されており、中身も見えるので融通が利く
    -   「車輪を発明する」(すでにあるものを頑張って作る)ことのなきよう

### オブジェクトがメインか、DBがメインか

-   DB中心からオブジェクト中心へ 発想の転換が必要
    -   DBをSQLを使って操作
    -   → Javaオブジェクトを操作

### データアクセス層のデザイン上の考慮点

-   SQLは外部に保持し、適宜変更可能にする
-   SQL例外をビジネスロジック層に波及させない
-   Factoryパターンなどで入換可能にする
-   DAO(Data Access Object)パターンでDBMSの相違を隠蔽する
    -   継承を利用して相違部分のみをプログラムする工夫
-   DTO(Data Transfer Object)パターンで処理効率を上げる
-   ロック・ストラテジーの違い
    -   共通エリアに注意
    -   例外処理
    -   タイムアウト
    -   デッドロックとリトライ
-   複雑な検索や更新は専用のDAOを

### ロッキング・ストラテジー上、問題となる設計の例

-   FOR UPDATE
    発行のみでロックをかけて(かけたつもりになって)、共通エリアにデータをセット戻してから、改めて更新するようなデザイン


## 開発プロセスに関しては

-   標準への準拠度合いが高く、チェックの厳しいDBMSを開発のメインに据えるべき
-   データ・アクセス層の開発は少数のチームで
    -   SQLを業務開発者に書かせない
-   単体テストの完了基準に複数DBでの確認プロセスを盛り込む
-   テストの自動化
    -   開発よりも保守の期間のほうが長い
    -   リグレッションテストの重要性
    -   JUnit/HTTPUnit/DBUnit
-   DBの結果突合せ
    -   同一の名前で結果の異なる関数など
    -   これをきちんとやれれば一番安心

## アンチパターン

- 保守性を考慮してシングルソースへこだわる
    - サンプル程度は動くと思うが、本格的なアプリケーションでは設計やチューニングの場面で制約が大きくなる可能性大
    - 「拡張可能なデザイン」「疎結合」
- まず特定のDBMSで作ってしまってからポーティングする
    -   特に特定の案件が見えている場合などに多い
    -   当初の設計や規約から外れてしまう可能性大
- 縛りが標準化や開発ルールのみ
    - プロジェクト全員が各DBMSの細かい相違点を理解し、同じスキルレベルを保って開発できると考えるのは楽観的過ぎる
    - 業務開発者にはSQLを書かせない、等デザインや構造面での縛りが望ましい


## 結論

色々あるが、マルチDBMS対応が自己目的化しないように気をつけよう。
（今読むとそれって結構元も子もな話ではと思うが、それはそう...）


## 参考文献

http://www.atmarkit.co.jp/fdb/rensai/javadb01/javadb01_1.html

他、勉強会に基づく。参考になればさいわいです。