昨日「URLに関する議論 -- なぜ僕はクエリパラメータを擁護、ときに推奨するのか」という記事を書きました。そこで出した簡単な事例は、足し算を要求するのに、/calc/add/2/3 みたいなURLを使うのはやめて /calc/add?x=2&y=3 とか /calc/?op=add&arg1=2&arg2=3 にしようぜ、というものです。
実際には、URLのパス部分が既に存在する実体を指すことを強要すると、PUTメソッドでファイルを作りたいときはどうすんだ? といった問題もあります。が、個別の問題を議論する前に、クエリーパラメータや拡張子を擁護する背景をもうちょい説明します。
内容:
合意と伝達は大変
僕は「合意と伝達」の問題に強い関心を持っています。複数の人間が協力してソフトウェアシステムを作るとき、仕様に関して合意する必要があります。合意形成の過程で、また合意事項を全員に伝達し周知させるために、コミュニケーションが必要になります。このような「決めて、みんなで守る」過程はけっこうな負担となります。特に、複数のメンバーが地理的に離れていたり、ミーティングのスケジュールを合わせにくい場合はなおさらです。
よって、合意すべき事項は少ないほうがいいことになります。また、合意に基づく実作業も少ないほうがいいのも当然です。
XMLを使ってエンコードする方法
冒頭の算術計算のサービスを例題にするとして、例えば、算術計算の要求を次のようなXMLデータで送るとしましょう。
<call xmlns="http://xmlns.example.com/api/calc"> <operationName>add</operationName> <arguments> <number>2</number> <number>3</number> </arguments> </call>
エントリーポイントURLとHTTPメソッド(POSTに固定でしょう)はいいとしても、リクエストのためのXMLフォーマットを決めなくてはなりません。タグの名前?、要素の名前?、どんな構造にするか? …… と。正確さを求めるなら、スキーマ定義が必要となるでしょう。
算術演算とその引数をXMLシリアライズ(マーシャリング)するために、クライアント側プログラムが必要になります。そしてサーバー側でそれらを分解して取り出す(パージング、デシリアライズ/アンマーシャリング)プログラムも作成しなくてはなりません。
計算結果を返すレスポンスに関しても同様に「決めて、作る」作業があります。既存の仕様、例えばXML-RPCをベースにして、そのライブラリを使うとかすれば負担は軽減します。しかし、「XMLフォーマットを決める」こと、「クライアント側とサーバー側で、付加的プログラムを作る」ことを省略はできません。
「合意すべき事項は少ないほうがいい」、「合意に基づく実作業も少ないほうがいい」という観点からはマズイ方法となります。
URLにエンコードする方法
それに比べると、「2 + 3」の要求を /calc/add/2/3 にエンコードする方法にはメリットがあります。
- エンコードのルールが簡単。
- HTTP GETを使うなら、クライアントはブラウザのアドレスバーで済んでしまう。
- サーバー側もパス文字列をスラッシュ区切りで分割するだけでデコードできる。
しかし、エンコードのルールはローカルルールとなります。第三者に説明するときに常識に訴えたり、他の仕様を参照することはできません。ローカルルールを詳細まで曖昧性なく説明する作業は、意外と手間です。
例えば、スラッシュで区切ったオシリ2つを演算の引数と思えばいいのでしょうか? /calc/add/2/3/1 はエラーでしょうか? それとも「2 + 3 + 1」の要求でしょうか? /calc/minus/3 は「3の符号反転である -3」なのか、それとも引き算の引数が足りないのか?
算術演算とその引数達を、どのように単一パスにエンコード/デコードするかに、準拠すべきこれといった標準はありません。「ルールは簡単」とはいえ、「決めて、みんなで守る」ことはそれなりの負担です。
とにかく手間を減らすには
/calc/add/2/3 の代わりに /calc/add?x=2&y=3 を使うと、演算子部分と引数(パラメータ)部分の区切りは極めて明白です。「算術演算の“パラメータ”を、クエリー“パラメータ”にエンコードする」のは常識的な感覚としても自然です。パラメータ部分のデコード方法も、ありものライブラリの挙動で問題はないでしょう。
とはいえ、パラメータの名前や個数の合意はローカルに取り決める必要があります。この手間も軽減できないでしょうか?
ここで、先に「マズイ方法」といったXMLによるエンコードの方法を思い起こします。XML-RPCは、XMLベースRPCの一般的デメリット(メンドクサイとか)を持ちますが、メリットもあります; 関数やメソッドのインタフェース定義があれば、そこからエンコード方法が決まってしまうことは大きなメリットです。
例えば、JavaScriptの関数風な次の宣言があったとします。
function add(x, y) {/** 足し算 */}; function mult(x, y) {/** 掛け算 */}; function minus(x) {/** 符号反転 */};
この宣言と、「関数のパラメータはそのままクエリーパラメータに対応させる」ルールを組み合わせると、決めた内容を伝達し周知させる負担はだいぶ軽くなります。
名前やパスに情報を含ませること
ついでに、拡張子が「伝達の負担」を減らすことにも触れておきましょう。
次の文面を考えます。
- 最新版を http:/www.chimaira.org/hiyama/latest-version に置きました。
この文だけから、最新版ファイルの内容がなんであるかはまったく推測できません。次はどうでしょう。
- 最新版を http:/www.chimaira.org/hiyama/latest-version.txt に置きました。
- 最新版を http:/www.chimaira.org/hiyama/latest-version.pdf に置きました。
- 最新版を http:/www.chimaira.org/hiyama/latest-version.java に置きました。
もちろん、拡張子がなくても、日本語で丁寧な説明を付ければ情報は伝わります。
- まだドラフトですが記事原稿テキストの最新版を http:/www.chimaira.org/hiyama/latest-version に置きました。
- 完成したレポートのPDF文書の最新版を http:/www.chimaira.org/hiyama/latest-version に置きました。
- サンプルのJavaプログラムの最新版を http:/www.chimaira.org/hiyama/latest-version に置きました。
日本語の説明には劣るものの、拡張子を付けるだけで情報量が増えることは明らかでしょう。
まとめ
個別の説明を要するローカルルールをできるだけ排除して、広く受け入れられている常識や習慣、そしてメジャーな標準(スタンダード)を採用すれば、合意と伝達の手間を減らせます。
また、ひとつの仕様(たとえば、関数/メソッド群のインタフェース定義)から、他の仕様を自動的かつ一意的に導出できるようにしておけば、合意事項を少なくでき、不整合も起こらず、記憶や伝達の負担を減らせます。