Webサービスを既存の挙動を破壊しないようにシームレスに変更することは、往々にして難しい。
これまでテストケースを拡充することで従来の挙動を保護してきた。このアプローチは有効なのだが、最終的にDBアクセスを伴う処理が特異的に変更しづらいことが分かった。
いま、PostgreSQLにユニットテストの関数を追加するエクステンション
pgTAPを導入し、これが決定的な解になると期待している。
SQLだけを用いて、PostgreSQL単体のテストを書ける。
SQLは集約した方が良い
Webサービス開発の主流は、従来からDBアクセス処理にORマッパーを採用している。
ORマッパーとは、データベースのネイティブ言語であるSQLではなく、PythonやRubyで書けるように変換するライブラリを指す。
pgTAPを用いたユニットテストは当然ながらSQLを直接書く前提で役立ち、ORマッパーとは対極的なアプローチと言える。
ORマッパーを使わない開発アプローチでは、PythonやRubyのコードにSQLを埋め込む構成になる。
いま振り返ると、サーバーアプリケーションにSQLを散発的に埋めるというアプローチが中途半端だった。
機能を追加していくと、ある処理の中に複数のクエリを書きたくなるが、n+1問題で知られるとおりアプリケーションから複数のクエリを発行することは避けるべき基本ルールだ。
アプリケーション内でクエリを集約すると、すぐにWITH
を使うような長大なSQLに成長する。
クエリ設計の面では妥当でも、正しさの確認が難しくなる。
また、本来SQLには文法があるものの、Pythonなどのホスト言語に埋め込んだSQLは単なる文字列として扱われる。
よって、埋め込みSQLの誤りはコンパイル段階で検出できず、実行時エラーを追跡する展開に陥りやすい。
コード記述時に適切さを検証できない以上、実行時エラーをチェックするのはテストコードの役割だ。
埋め込みSQLの難点は、ホスト言語の関数にラップされる構造上、チェック対象の入力・出力がSQLと乖離してしまうことだろう。適切なSQLであることは直接確認できず、あくまでも関数の挙動が妥当であることから推定するよりほかない。
設計・実装・テストの各段階に課題がある。
ストアド関数に集約すべき
pgTAPで一般的なプログラミング言語と同様のユニットテストを書けることが分かったいま、サーバーアプリケーションからSQLのDBアプリケーションを分離するアーキテクチャにより埋め込みSQLの問題が解決するはずだ。
PostgreSQL上にストアド関数を実装することで複雑なSQL処理を実装できる。
ストアド関数にはSQLに加えてPL/pgSQLを利用でき、BASIC言語のような変数と制御構造で書ける。
そしてストアド関数は埋め込みSQLとは異なり、WITH
などで1クエリに複雑に処理を詰め込む必要がない。
コネクション制御や暗黙のトランザクションといったペナルティがないため、埋め込みSQLではn+1問題になるような書き方も避けなくてよい。
ストアド関数が複雑化するケースでは、他の実装方法でもSQL以上に簡素になる可能性はなく、サーバーとDB間のクエリが増える分だけ性能ペナルティを払うよりほかない。
ORマッパーが生成するSQLセットとストアド関数の処理は、理想的には似たような処理になるのだろう。それをデータベース内にパックするのとサーバー側に制御を移すのとでは挙動が異なる。
ある側面ではストアド関数は最適であり、他の手段は妥協と言い切れる。
タプル型のストレージフォーマットとB木ファミリーのデータストアの利用範囲が広いため、本来はそのネイティブ言語であるSQLを積極的に扱う必要がある。
pgTAPを利用すると、PostgreSQLのみの簡素なシステム構成で、コアのデータモデル機能の品質を確保できる。
従来のアプリケーション側のコードとテストケースを簡素化できることもあり、開発スピードアップに効果的だろうと見る。
SQL特有の難点
実際にテストコードを記述してみると、一般的なプログラミング言語と異なるポイントでミスが起きやすいことが分かった。
たとえば、
3値論理の影響でboolean
だと思い込んでいる処理のケースを見落とす場合がある。
true = null
やfalse OR null
といった論理演算は多くの言語ではfalse
を返すが、SQLの場合にはnull
を返す。
この例の比較対照ではnull
と書いているため注意する余地があるが、実際のコードではこの部分には変数が入っている。
当然ながらテストコードは記述したケースを保護するが、存在に気づけなかったケースは保護しない。
SQL特有のケースについては、おそらくテスト資産を拡充していく中で明らかになる分野がある。