Domain Logic and SQL

Martin Fowler's Bliki in Japaneseの「ドメインロジックとSQL」(http://capsctrl.que.jp/kdmsnr/wiki/bliki/?DomainLogicAndSQL)より。
さすがはFowlerさん、しっかりした記事だ。OO支持者にありがちな行き過ぎたSQLバッシングにならず、むしろSQLの使いどころについてきちんと説明されていた。
本人は、「超OO開発者」だとおっしゃっているが、この文書だけを読んでしまうと、むしろDOAで開発されている人のOOPへの考え方を解説しているような感じがする。たとえば、序文にこんな文章があったりする。

SQL クエリを使えば様々なタスクを処理することができるのだ。アプリケーション開発者がSQLを使わないのは、強力なツールを排除しているのに等しい

私はこの議題にオブジェクト指向のバイアスをかける。とはいえ、私は反対側の人間だったこともあるわけだが(以前のクライアントに OO エキスパートグループがあり、彼らに会社を追い出されたことがある。私が「データモデラー」だったからだ)。

まずは、本文について、すこしツッコミをいれていこう。ちなみに、私はデータベースの研究室で学んでいるので、DOAのバイアスをかける。とはいえ、私はDOAよりもオブジェクト指向のほうが好きだが。

複雑なクエリ(Complex Queries)

SQLは複雑な問い合わせを書くことができる。ここでは「フィルタ」や「サマリ」のリッチさをあげているが、さらにいえば複数の表を結合(ジョイン)して検索ができることもかなり強力だ。XMLネイティブのデータベースやその他のデータベースではなかなか大変である。そして、その結合した表についてフィルタやサマリをかけられるわけだ。
しかし、おそらくジョインだけであれば、とくにドメインロジックに反しないだろう(複雑なジョインは別)。Fowlerさんの例を使えば「値引き対象かどうか」というフィルタをかけてしまうことで、ビジネスロジックの内容をSQLに記述できてしまう。
DOAではSQLがデータ抽出のすべてであって、それ以外のプログラム部分でデータをフィルタしたりサマリすることは基本的に行わない。それだけSQLの記述能力はリッチなのである。

Transaction Script

手続き的にアクセスを行う方式。Fowlerさんが示している例を読むとデータベースへのアクセスはすべてfind_xxxという名前になっている。これは、たとえば「顧客IDを見つける」という処理を実行すると,引数の名前に関係付けられた顧客IDが返される.つまり一般的な関数のような処理になっている.ここで重要なことは,オブジェクトのように内部状態を持たないこと(せいぜいあって,セッションくらい).ただSQLで検索をして配列に結果を詰めているだけであり,SQLも低機能のものしか使用していないので,使い回しがきく.
ただし,この例ではテーブルごとにアクセスを行っているので非常に効率が悪い。

Domain Model

古典的なオブジェクトモデルと呼んでいるが、これは最も(オブジェクト指向的には)素直なモデルだと思う。Fowlerさんの例ではいきなりfinderというデータベースアクセスレイヤの処理から入っているが、むしろ一番最後にあるCustomerクラスにあるcuillenMonthsのようなメソッドを構築したいがために、finderがその準備をしていると考えたほうが(私には)わかりやすい。ここではfinderは単にデータベースとのアクセスのみを行っており、レイヤの分割はきれいに行われている。
ただし、やはりテーブルごとにアクセスを行っているので効率が悪い。

Logic in SQL

Havingやdistinctまで使用したSQLを使った例が示される。なんと、たったこれだけで、答えが出てくる!なんとすばらしい・・・・?(もちろん、これはFowlerさんがSQLを使うと便利な例を出しているからだろう)

パフォーマンスを考慮する(Looking at Performance)

パフォーマンスを考えるとなんとSQL in Logicが20倍も早いらしい。そりゃそうだ。最後のアプローチは1回しかSQLを発行していないし、クライアント上のメモリでいろいろ処理することもない。この値はネットワークの回線の早さや、1顧客あたりのオーダーの数にもよる。ただ、これらを考慮しても、前二つのアプローチが最後のアプローチを上回ることは不可能だろう。
ここでは、さらに改善案を示している.しかし,SQLを結合によってひとつにしたところでフィルタリングをクライアントでやっている以上、データのやり取りの量は圧倒的に多く、追いつくはずもない。(フィルタリングをやってしまえばドメインモデルに侵食してしまう)
一方、ここではドメインモデルの優位性、つまりデータベースへのアクセス部分を書き換えるだけでよく、それ以外のビジネスルールに関する部分は変更しなくてもよいというところが示されている。
しかし,変更は簡単に行えたが,CustomerMapperはCustomerのMapperにも関わらず、Orderの表にアクセスするようになってしまった。改善前のアプローチではOrderの表についてのアクセスはOrderMapperの役割だった。それが破られている。これは、あまりきれいではない。しかし、レイヤが分けられているという部分については死守されており、ドメインモデルという観点からは正しいといえるかもしれない。
もちろん,ここではデータベースの強みを合えて強調するためのものであって、通常はこのような複雑なクエリは用いないことを考慮すべきである.また複数のユーザー環境では重たいロックがかかることがある。group byなどの集約関数を計算している間は内部的に短時間の暗黙ロックがかけられる*1。これも考慮したほうがいいということをいっている(と思う).

*1:これは,データベースによる.Oracleのような多版型の場合は必要ない.ただし,今度はUNDOログ,Oracleでいうロールバックセグメントが消費される

更新性(Modifiability)

この章はすばらしい。この章でLogic in SQLアプローチが不利な点をあげて、結論として速度よりも柔軟性を重視するドメインアプローチがすばらしい、などというのかなと思っていたのだが、ぜんぜんそんなことはない。

分かりやすさ(Understandability)

Fowlerさんはドメインモデルが一番わかりやすいという。確かにそうかもしれない。なぜならきちっとした思想の元で分割されているからである。しかし、SQLになれた私にはそうでもない。これは人による。だから、

もしチーム内にSQLに詳しいひとがいなかったら、ドメインロジックをSQLから切り離したほうがいい

という結論になるのは当然だろう。

重複排除(Avoiding Duplication)

つぎに、重複の排除という点から見よう。ドメインロジックはメソッドが適切に分割されていれば再利用は容易である。しかし、SQLは単なる文字列の羅列に過ぎず、再利用はできない・・・・・・というわけでもない。Fowlerさんはこういう。

重複排除問題でSQLに触れないのはフェアではないだろう。リッチなSQLアプローチでも、重複問題を避けることは出来るのだ。データベース熱狂者は必死になってこう言うに違いない。ビューを使え、と。

そのとおりだ!!!と声を大にしていいたい。ビュー愛好家の私はこの言葉を待っていた。なぜこのようなことを触れる本が少ないのだろうか?ビューを嫌い、SQLを簡単にするために安易な派生属性*1に頼ってしまう設計者のなんと多いことか・・・。(ってOOとはあんまり関係なかったか・・・)
たとえば、Fowlerさんの例を使うと、値引きの適用が可能かどうかということをデータとして格納してしまうような設計をよく見かける。更新は日時などのバッチで行ったり、リアルタイム性が必要な場合は注文の更新時の処理に入れてしまったりする。
皮肉なことに、ドメインモデルの場合のほうがこの考えがあっているかもしれない。この更新処理は当然ながらビジネスロジックの部分におかれるので、このような考え方も適用できる。検索処理の非効率性に悩むこともなくなる。
ただし、更新処理に負担をかけるとロックの問題や、データの不整合の問題に悩まされるかもしれない。よって、注意深く設計しなければならない。ちなみに、私はこのような設計は(よっぽど検索に速度を要求される場合を除き)シンプルでなくなるので大嫌いだ。夜間のバッチ処理で整合性チェックをしなければならない悪夢はもう勘弁願いたい。

カプセル化(Encapsulation)

ドメインモデルは、このカプセル化についても有利だ。というよりも柔軟性を確保している大きな要因になっている。
SQL in Logicはどうか。ビューがあるじゃないか!Fowlerさんの文章にはビューは更新ができないとあるが、そんなことはない。instead ofトリガというデータベーストリガを使えば更新もできる。アプリ側では単に表を(実はビューを)updateしているだけなのだが、裏ではストアードプロシージャで処理できる。実際のテーブルの構造によってSQLを書き換える必要はまったくないのだ!!!すばらしい。
Fowlerさんはここで、データソースビューとビジネスロジックビューに分けることを提案している。例で言えば、単にタリスカーの売り上げを集計するビューとタリスカーの売り上げによって割引を適用するかどうかのビューを分けてしまおうという。この場合では前者がデータソース、後者がビジネスロジックになる。
これはいい考え方かもしれない。ただ私なら、タリスカーの売り上げを集計するビューをデータソースビューにせず、商品ごとの売り上げを集計するビューをつくり、それにつかって、ビジネスロジックのビューでタリスカーという一商品を考慮すべきだと思う。
あと、もうひとつは、データソースビューとビジネスロジックビューに分けるんだったら、ついでにプレゼンテーションビューも作ってしまったほうがいいのではないだろうか。つまり、GUIや帳票上などユーザーの目に触れる形のビューである。というのも、GUI上のアプリケーションはよく似た検索を使用することが多く、流用可能なことが多いからである。プレゼンテーションビューはデータソースビューとビジネスロジックビューに依存するという形をとる。Fowlerさんの例で言えば、割引を適用するかどうかという検索はビジネスロジックビュー、それを注文IDや日付とあわせて出力するのはプレゼンテーションビューという分類になるだろう。そして、このプレゼンテーションビューにinstead ofトリガをつけておく。
ただ、このように多階層にビューを作り上げていく上で、ひとつだけ注意してほしいことがある。それはビューによってオプティマイザが変な実行計画を作ってしまうことがあるという点である*2。ここはとくに気をつけなければならないところだ。

データベース移植性(Database Portability)

ここはおっしゃるとおり、SQL in Logicアプローチの最大の欠点だろう。たとえば以下のようなSQLがあるが、これはDBMSに依存している。

SELECT orderID, date, customerID, name, total_cost,
CASE WHEN taliskerCost > 5000 THEN 'Y' ELSE 'N' END AS isCuillen
FROM dbo.OrdersTal

この中で、CASE WHENという関数*3PostgreSQLでは使用できるだろうが、おそらくOracleは使用できないはずだ*4OracleではDecodeを使用することになる。MySQLではIf関数を使用することになるだろう*5
つまり、移植性を考えるならばリッチなSQLを書く選択肢はないということだ。

テストの容易性(Testability)

ここもSQL in Logicアプローチの大きな問題点である。SQLのテストというのはあまり聞いたことがない。この点に関しては、「データベースの進化的設計」というFowlerさんの文章を読んで、もう一度考えていこう。

*1:他のテーブルにある値やサマリ値など他の属性から求められるにもかかわらず、属性として存在しているもの

*2:実体験済みです

*3:関数ではないかもしれない

*4:たぶん。最近Oracleから離れているため違うかもしれない、もしかしたら使えるのかも

*5:ちょっとマニュアルを読んだだけで、書いています

まとめ(Summing Up)

まとめると以下のとおり

  • データが論理的に分散しているか、どうか?分散しているとあまりSQLによる高速化のメリットはない。(というか、下手にジョインを行うとオプティマイザがへんな実行計画を作って、かえって遅くなる場合もある)よって、データアクセス層をつくって、ビジネスロジック部分は分散環境を意識させないシステムを作るべきである。つまり、ドメインモデルを使うほうがよい。ただし、もし、SQL in Logicを使うのであれば、ビューやシノニムを使用して、間接的にSQLを実行することにより意識させなくすることもできる。ただし、この場合高速化のメリットは少ない。
  • ロジックをSQLに入れるならば、移植性は諦めた方がいい。
  • 致命的なパフォーマンス問題が発生したらSQLの改善を考える

あと重要なことをいくつか

  • SQLに詳しいメンバーがいなければ、ドメインモデルを使う。
  • 複雑なSQLを使用する場合は、実行計画のチェックをしっかり行う。自信がなければドメインモデルで
  • ネットワークの回線が細い場合はSQL in Logicがよい。または、ドメインモデルにおけるメモリの展開を太い回線でつながれているアプリケーションサーバー上で行い、最低限のデータをクライアントに流すようにする
  • 複雑なSQLの場合は、テストが課題である。単純なSQLで構成されているドメインモデルはテストが簡単。