09

なんでそんなにSQL嫌いなの

ORMはあまり使ったことないけど

私がWebプログラミングを学んだのは仕事上必要だったからなんだけど、多分2001年とか2002年くらいにWindowsのASP(いわゆるLegacy ASP。VBScriptで書くPHPみたいなやつ)とかJava Servlet+JSPとかがはじめだったと思う。Javaで言えば当時ちょうどStrutsとかの出始めくらいだったんじゃないかと思うんだけど、当時書いたアプリケーションでStrutsを採用したものがいくつかあった気がする。

で、当時からORMってのはあって、EJBの世界だとCMPとかだったんだろう(そのあたりは個人的にはまったく触ったこともないのでどんなものかすら知らない)

Servletの世界でもいくつかORMは出てきていて、Hibernateがなんとか使い物になりそうって感じの時期だったかな?個人的にはHibernateも使ったことはなくて、自分がStrutsと組み合わせて使っていたのはTorqueっていう、Strutsとは別のMVCフレームワークであるApache Turbine(当時は Apache Jakarta Turbineだったかな)のサブプロジェクトとして開発されていたORMだった。

Apache Jakarta Turbine

いきなり脱線するけど、このTurbineっていうフレームワーク、あまり採用したような話は聞いたことがないし、Java系の雑誌なんかでもあまり詳しく紹介されている記事は見なかった気がする。

結構巨大なフレームワークだったこともあってサブプロジェクトが多く、いまではJavaのビルドツールのデファクトとなっているMavenも、もともとはこの巨大なフレームワークを使って開発する際のひな形を生成したり、膨れ上がったコードや設定ファイルを自動的にまとめ上げてアプリケーションとしてビルドするために始まったのだったと記憶している。

他にも、いまだにたまに使われているということを耳にするVelocityというテンプレートエンジンももともとはここのサブプロジェクトだったはず。

Apache Jakarta Tuebine Torque

話を戻して、TorqueというORMの話。 当時自分のプロジェクトで何故Torqueを採用したのか、もはや10年以上も前の話なのでおぼろげにしか覚えていないんだけど考えてみた。

まずTorqueのユースケースというのは基本的にはテーブル構造(というか、たぶんオブジェクト構造だろう)をXMLで定義して、そこからRDBMSのテーブルやクラスを自動生成する、というものだったと思っている。ただそれだけではなくて、すでに構築済みのDBのスキーマ情報からメタデータを引っ張ってきて、このXMLの定義を起こし、そこからクラスを生成するという方法もサポートしていた。

.NetのEntity Frameworkのモデル・ファーストとかデータベース・ファーストとかいう手法とおなじ考え方だと思う(Entity Frameworkよく知らないけど)

ここで思うのはテーブル定義をコーディング前にやってしまうウォーターフォール脳にはちょうど良かったっというのが一つ、それともう一つがテーブルとクラスが一対一で、Joinして別のテーブルをくっつけたい場合でも結構わかりやすい関数を自動生成してくれていたことが大きな理由のような気がする。

当時IDEといえばとりあえずEclipseが使い物になるようになってきたみたいな時代だったんだけど、クラスやJoinするためのメソッドを先に生成しておいてもらえると、自動補完でどんどんコードが書ける。基本的にはテーブルから自動生成されたコードになるのでSQLを書き損じてシンタックスエラーになるなんてことが起こりにくくて品質上がるじゃん、という期待があった気がする。

その程度で上がる品質なんて今の時代で言えばJUnitで一通り流せばわかるじゃんという話なんだけど、まあ当時はそこまでJUnitでやる発想がなかったというかなんというか。あとやっぱりそういうのプロジェクトをブートストラップするための各種コストおよびメンテするコストになるのでね。極力コンパイラがバグを摘出できる構成を作り上げたいというのがあった。

なんでこれが重要かということを考えると、まあSQLの構築をJavaの世界にもってきてコンパイラで検出可能にするのが大きい。

SQLのシンタックスエラーって、シンタックスエラーなのにもかかわらず発生するとどこでどう間違ったのかがどうにも分かりずらい。日ごろからRDBMS使っている人ならわかるだろうけど、「誰(何)が」「何に対して」「何をしようとして」「何が」「どう」おかしいのか、というのが全く分からないため、デバッガで逐次実行するか、同じ状況を作って専用のSQLクライアントから流すとかしないと現象の詳細が確認できない。いや、シンタックスエラーに限らないか。前に書いたけどこれなんて何言ってるのかさっぱりわからない。

09.hatenadiary.jp

で、いざ問題のあるSQLを直そうという段になると、これがJavaのコード上では文字列の長ったらしい連結を繰り返して書かれていたりする。特にJavaだとヒアドキュメントリテラルがないのでひどいことになりがちだ。一度クエリとして実行して何が悪いか確認しようと思っても一度文字列連結処理を削除して普通のSQLに戻す、という作業が発生する。いまどきはprepared queryは当たり前に成っているから、そこからさらにパラメータをちまちま書き換えてやる必要もある。 そうしてできたSQLを流してみてエラーがわかればよいが、そのままでは再現しなかったりするとまた話が面倒になる。

今考えれば単にSQLのログ出力すればこんな苦労しなくてよさそうだ。まあ当時はそんなこともわからなかった。

・・・というような、この辺の苦労を少しでも軽くすために手書きのSQLは避けたい、というのが当時の心理だったろうな。自動生成によってそもそもSQLのシンタックスエラー的なところは防げるのではないかと…思っていた。

後日談になるけど、結局実装できる限界がすぐあらわになってしまって(つまり実際の処理を実装するにはそんなに使い勝手がよくなくて)性能的にも全然間に合わなかったので、だんだんプロジェクトからなくなっていって最終的にマスタメンテくらいにしか使わなかったな。

SQLといえば

しかしJDBC経由の場合のみかもしれないが、DBのエラーメッセージってなんであんなにわかりづらいのだろう。たとえば日付型のキャストしようとしてるけど型が間違ってます、みたいな場合、キャストしてる箇所が複数あったりしたときにどっちが問題か、とか、どういう値が問題だったか、ぐらい報告できないのだろうか。この問題に関してはここ10年以上進捗していない気がしているのだが。

DBのエラーメッセージがわかりやすいだけで生産性がずいぶん違うんではないかと思っているのだけどなあ。

その後はあまりコードを書かない仕事をしていた期間もあったりしてiBatisやらHibernateやらS2JDBCやらは(検証等で触ったりサイドワーク用のツールを実装してみたりとかは別として)ほぼスルーしてきていて、最近使ったのはmirage-sqlっていう、ORM(Object-Relational Mapping)というかOSM(Object-SQL Mapping)とでもいうべきライブラリ。これは一応テーブルとクラスのマッピングもやってくれるけど、どちらかというとSQLは設計要素としてちゃんとあって(つまり自動生成ではない)、SQLのI/Oをオブジェクトとマッピングするという感じ。

結局我々は思う通りのSQLを実行したい、という欲求があるので、この程度のライブラリが一番取り回しやすくてよいのではないかという気はしている。

MVCにおけるMの定義が2000年代後半以降とても難しくなってきていると思っている。それ以前は多少ユーザにとって適切でないUIやUXのシステムを出していても対して問題にならなかったんだけど、最近はその辺が如実に競争力に影響するというのと、いい加減なUXを声高に批判する鉞を持ったUX屋が増えてきた(という私の被害妄想かもしれないが)というのがあって、その辺の要求にこたえるためにユーザ一人あたりについてシステム内部とUIのやり取りが増える傾向にあり、システム内部のやり取りが増えるということはそれに従って性能要求が厳しくなる傾向にあると思っている。 こういった状況下で、ストレージから必要最小限の情報を取得したい、とか、最小パスである項目だけを更新したいといった要求がある一方で、一括である程度の情報を登録したいといった要求も依然ある。これらを両立したいと考えた場合、クラスベースでテーブルに自動的にマッピングするようなORMだといずれの場合においても非常に無駄が多くなることは少し考えればわかるだろう。巨大で堅牢なモデル設計では性能的なオーバーヘッドが多きくなりすぎるために要求に応えることが難しくなり、モデルを細切れに刻む(あるいは細切れのメソッドが大量につく)しかないのではという気がしている。そうやってコードを増やすのと、あきらめてSQLを手書きし、処理のパスを最小限に抑えることを比較すると、もしかして後者のほうがやりたいことについて率直に書ける、つまり分わかりやすいのではないか。 もちろん操作系全体で矛盾の無いことを証明できなければならないが。

ここ10年位の経験で、結局多くのWebシステムはただDBに書いては読むだけなのだから、DBに近いレベルでの堅牢を維持するのはともかくとして、レイヤーの高い部分であれこれやるのは大がかりすぎ(本来別に無駄というほどではないのだが、より性能を活かす方法がある)で、もっとマシンリソース的に効果的な設計にするべきなのでは、と思い始めている。