([追記 date="翌日"]文言を少し改善し、注意を付け足したりしました。[/追記])
HTTPメソッド、URL、動詞(verb)に関して次の記事を書きました。
問題点がほぼ明らかになり、全体の状況も見えてきたので、総括したいと思います。これで決定版にしたいのですが、実のところ、まだ考えが変わる可能性は否定できません。現時点では、以下に記述する案が最善だと思っていますがね。
内容:
- 用語の注意
- 事の発端,事の成り行き
- URLの意味と用途を分類する
- リソース種別ごとに動詞を考える
- さらにリソース種別ごとに動詞を考える
- GETに乗せるか、POSTに乗せるか
- インターフェースとしてのリソース種別と動詞
- リソースとクラス
用語の注意
HTTPクライアントは、現存するブラウザだけを考えます。将来、HTTPメソッドPUTやDELETEを発行できるブラウザが登場すれば、話は変わります。
URLという言葉は、この記事内の多くの場所で、実際のURLからクエリ文字列を除いた部分を意味します。この意味でのURLが、リクエストの送り先を識別し、クエリ文字列は名前付きパラメータ群を指定すると考えます。
事の発端,事の成り行き
HTTPには5つのメソッドがあり、それらのセマンティックスは比較的良く定義されています。しかし、HTTPメソッドの本来の使用法は無視されがちで、さらに、ブラウザがGETとPOSTしか発行できない事情が重なって、困った事象や状況が出現しています。現実的な対処法として、ブラウザが、5つのメソッドを発行したように見せかける手法を「HTTPメソッドの正統的使い方と現実的対処法」で紹介しました。それは次のようなものです。
HTTPメソッド | 発行の方法 |
---|---|
GETメソッド(本物) | GET URL |
PUTメソッド (擬似的) | POST URL?_method=put |
DELETEメソッド (擬似的) | POST URL?_method=delete |
HEADメソッド(擬似的) | GET URL?_method=head |
POSTメソッド (本物) | POST URL |
この方法を使ってみても、POSTには色々な用途がごった煮のごとく詰め込まれているので、_submethodというパラメータで分類してはどうか、とも提案しました。_submethod=append, _submethod=generate のように。
最近になって、_methodと_submethodの2つのパラメータを使うのは煩<わずら>わしいので、_verbに統合するのが良かろうと思いました。既にCatyにはこの方式が実装されています。しかし、単にメカニズムがあるだけです。どんな動詞をどんな用途で使うべきかというコンベンションやガイドラインはソフトウェアとはまた別な話です。僕がここでフォーカスしたい話題は、動詞の使用法に関する標準(あるいは推奨の)規約です。その規約が安定すれば、ソフトウェア構造に反映させることもできます*1。
URLの意味と用途を分類する
今までの僕のアプローチは、典型的な動詞を洗い出して、その使用法を明確化しようというものです。ここで少し発想を変えて、動詞と一緒に指定されるURLは何を表すのか? を考えてみます。URLの意味と用途を分類してみるのです。
しばし黙考して、次のように分類してみました。
- Resource
- Factory
- Application
- Processor
- APIProvider
順に説明します。
URLはなんであれリソースを表すのですが、Resource(英字表記)は狭義のリソースです。サーバー側で保存され管理されているデータと考えてください。典型的にはファイルやデータベースレコードです。
Factoryは、Resourceを作り出す能力を持つ存在です。それと同時に、一群のResourceのコレクション(集まり)も表します。Factoryにより生成され、その管理下に入ったResourceは従属リソースと呼ばれます(『RESTful Webサービス』の用語法)。Factoryとその従属リソースは、ある種の親子関係になります。例えば、ファイルはディレクトリ(フォルダ)に従属し、レコードはテーブルに従属し、親子となります。
Applicationはブラウザ側で実行されることを意図したリソースです。Flash/AjaxページはApplicationの典型的な例です。HTMLフォームも、デスクトップにおけるダイアログボックスのような役割を果たすのでApplicationに分類してもいいでしょう。ただし、Resource(単なるデータ)とApplicationの境界線は曖昧です。
Processorはサーバー側で実行されるプログラムです。動的ページの背後にはサーバー側プログラムがありますが、それをProcessorとは呼びません、内容が変動するResourceに過ぎません。当該URLが、計算処理資源を表しているときに、そのURLをProcessorに分類します。検索や翻訳などを行うURLはProcessorです。カンマ区切り数値データを送ると何か意味のある分析結果を返してくれるようなURLがあれば、それは典型的なProcessorになるでしょう。
最後のAPIProviderは、RPC(遠隔手続き呼び出し)スタイルのWebサービスを提供するURLです。Processorとの区別は; Processorはブラウザから送られたデータをそのまま処理対象とするのに対して、APIProviderはそれを手続き呼び出しがエンコードされたものと解釈し、デコード後に手続きを実行することです。(ProcessorとAPIProviderの区別も曖昧性が残ります。)
リソース種別ごとに動詞を考える
動詞をフラットに並べるのではなくて、今提示したリソースの種別(kind)ごとに、どんな動詞が使用されるかを考えます。動詞を関数名と考えて、create(Resource), post(Factory, Data) のような記法を使います。あるいは、動詞をメソッド名と考えれば、Resource.create(), Factory.post(Data) とも書けます。あまりうるさい事を言ってもしょうがないので、リソースそのものとリソースへの参照(つまりURL)を厳密には区別しません。クエリ文字列は、動詞(関数またはメソッド)への追加引数(名前付きパラメータ)になりますが、特に必要な場合以外は省略します。
一番分かりやすいのは、Resource(狭義のリソース、サーバー側に保存されるデータ)です。Resourceを操作する動詞はファイルIOのAPIとほとんど同じものです。
動詞 | 意味 | 適用形式 |
---|---|---|
create | 新規にResourceを生成する | create(Resource) |
get | Resourceの内容を取得する | get(Resource) |
update | Resourceの内容を上書きで置き換える | update(Resource, Data) |
put | updateと同じだが、Resourceがなければcreateする | put(Resource, Data) |
append | Resourceの内容にデータを追加する | append(Resource, Data) |
delete | Resourceを削除する | delete(Resource) |
念のためファイルIOのAPIとの類似性を書いておきます。creat(eがない)、open, close, read, write, unlik はファイルIO(一部はファイルシステム操作)のAPIです。
動詞 | ファイルIOならば |
---|---|
create | 既存ファイルがあるなら失敗(何もしない)、なければ creat |
get | 読み込みモードでopenして全部readしてcloseする |
update | 上書きモードでopenしてwriteしてcloseする |
put | “なければ作成”(O_CREAT)モードでopenしてwriteしてcloseする |
append | 追加モードでopenしてwriteしてcloseする |
delete | ファイルをunlink(削除)する |
読み込みには動詞getを使いましたが、view、execも類義語でほぼ同じ意味で使われます。ここで使われた動詞appendはファイルの追加書き込みの意味で使っています。postとの違いはすぐ後で述べます。
Factoryリソースに対しては、postとgenerateという動詞が有効です。
動詞 | 意味 | 適用形式 |
---|---|---|
post | 新規に従属Resourceを生成し、データを書き込む | post(Factory, Data) |
generate | 新規に従属Resourceを生成する | generate(Factory) |
Factoryはコレクションリソースでもあり、post, generateで新しいアイテムが追加されます。このとき、アイテム(従属リソース)は独自のURLを与えられます。動詞postは、HTTPメソッドPOSTの本来の用法を表しています。一方、appendは、指定したリソースにデータが追加されますが、そのデータはリソースの一部になるだけで、新たなURIを獲得することはないとします*2。
その他の種別のリソースに対する動詞は、引き続き節をあらためて述べます。
さらにリソース種別ごとに動詞を考える
Applicationリソースに対する動詞はexecでしたが、サーバー側でプログラムが実行(exec)される状況と紛らわしいので、load and exec の意味で動詞invokeを使うことにします。
動詞 | 意味 | 適用形式 |
---|---|---|
invoke | Applicationリソースをブラウザにロードして実行する | invoke(Application, Params) |
既に指摘しているように、HTTPメソッドGETによるリソースの取得が、viewなのかinvokeなのかは明確ではありません。気分的に使い分けるか、特に動詞は指定しないという使い方になるでしょう。
Applicationをinvokeするときは、他のリソースをパラメータとして渡すことがあります。例えば、invoke(EditorApplication, TargetResource) といった形です。より具体的には、
- EditorApplication = http://example.jp/blog/editor.html
- TargetResource = http://example.jp/blog/2010-01-12.entry
ならば、invoke(EditorApplication, TargetResource) という抽象的リクエストは、http://example.jp/blog/editor.html?_verb=invoke&url=http://example.jp/blog/2010-01-12.entry という具体的URL*3で表現されます。
ターゲットリソースに対するApplicationのinvokeが、ターゲットURLへの動詞として表現されることもあります。次の1番目の代わりに、2番目のように書いてもよいということです。
- http://example.jp/blog/editor.html?_verb=invoke&url=http://example.jp/blog/2010-01-12.entry
- http://example.jp/blog/2010-01-12.entry?_verb=edit
ファイルIO相当のResource操作以外の動詞(editはその例)は、そのResourceをターゲットとしたApplication起動と解釈できる場合が多いでしょう。
Processorリソースに対する動詞はprocessとします。以前の記事のprocessは、HTTPメソッドPOSTに対応する曖昧な動詞でしたが、今回はより明確な意味を与えることにしました。ブラウザからサーバにデータを送って、その処理を依頼する動詞がprocessです。
動詞 | 意味 | 適用形式 |
---|---|---|
process | サーバー側でデータ処理を行う | process(Processor, Data) |
最後のAPIProviderは、あまり問題にする気がないのですが、サポートする動詞はcallです。
動詞 | 意味 | 適用形式 |
---|---|---|
call | リモート呼び出しを実行する | call(APIProvider, CallInfo) |
GETに乗せるか、POSTに乗せるか
以上に列挙したような動詞を含むリクエストを、GETに乗せて運ぶかPOSTに乗せて運ぶかは、「サーバー側のリソース状態を変更するなら必ずPOST、そうでないならGETでもよい」という原則を守るなら、ケースごとに都合が良いように決めればよいかと思います。例えば、動詞processやcallは、GETかPOSTかどちらか一方に決め付けるのは無理があります。個別に考えることになります。
インターフェースとしてのリソース種別と動詞
なんでもかんでもオブジェクト指向と絡めて語る態度を僕は嫌っているんですが、この話題とオブジェクト指向との“多少の類似”を指摘しておくのは理解を助けるかもしれないので触れておきます。
URLが表すリソースを分類して、リソース種別ごとに動詞のセットを考えることは、インターフェースの策定と似ています。例えば、狭義のリソース(サーバー側データ)に対するファイルIO/ファイルシステム操作に相当する動詞は、次のインターフェースで表現してもいいでしょう。(Status、Representation、Dataは、事前に定義されているとする)。
public interface Resource { public Status create(); public Representation get(); public Status update(Data data); public Status put(Data data); public Status append(Data data); public Status delete(); }
ただし、「Resourceはこのインターフェースを満たさなくてはならない」という規則があるわけではありませんし、継承やカプセル化などが有効なわけでもありません。ムキになってオブジェクト指向で考えないように。
リソースとクラス
今まで述べてきたリソースの“概念的な大分類”(kind)よりもっと具体的なリソースの種類(type, class)を考えてみます。例として、CSV(カンマ区切り)データを取り上げます。CSVデータに対する動詞セットを考えますが、広く合意された動詞セットがあるわけじゃなくて、個々のWebサイト/Webアプリケーションに都合のよい動詞を選び出せばよいのです。その一例をクラス定義の形を借りて書いてみます。
@Extension(".csv") public class CSVData { /* カンマ区切り形式データ */ @Verb("") public Html viewAsHtml() { /* ... */ } @Verb("view-as-text") public Text viewAsText() { /* ... */ } @Verb("download") public Binary download() { /* ... */ } }
これは雰囲気を表しているに過ぎませんが、クラス名がリソースの種類、メソッド名が動詞に対応します。Javaのアノテーションとして@Extensionと@Verbが使われています。@Verbはメソッドに対応する動詞の綴りです。クラスを修飾している@Extensionはファイル名拡張子ですが、これはCatyの方式を反映しています。Catyでは、次のような対応を前提としています。
リソース概念 | オブジェクト指向 | ファイルシステム | |
---|---|---|---|
リソースの種類 | クラス | ファイルタイプ=拡張子 | |
個々のリソース | インスタンス | ファイル | |
動詞 | メソッド名 | 動詞(Windowsの場合) | |
動詞の振る舞い | メソッドの実装 | 拡張子に関連付けられたコマンド |
[追記]上の表の「拡張子に関連付けられたコマンド」のところに、なんか早まって「Catyスクリプト」と書いてありました。すぐ下の例のように、Catyでは、拡張子に関連付けられたコマンド文字列もCatyスクリプトで書くのです。[/追記]
参考までに、上にJava構文で書いたクラス定義に対応する、Catyの設定を載せておきます。
".csv": {
"name" : "CSVData",
"description" : "カンマ区切り形式データ",
"contentType": "text/csv",
"isText": true,"assoc": {
"" : '''
{
"filePath" : param %0,
"csv" : csv:csv-to-json %0
}
| print /csvdata/tableview.html
''',
"view-as-text" : '''
file:read %0 | response --content-type=text/plain
''',
"download": '''
file:read %0 | response --content-type=application/octet-stream
'''
}
}
「ファイルタイプ=拡張子」ごとに、許容する動詞と、動詞に関連(assoc)付けられたコマンドを指定するという方法は、Windowsデスクトップの方法をそのまま借用しています。これは、素朴で単純なクラスシステムを実現します。一方、ディレクトリ(フォルダ)構造は、オブジェクト包含階層(Object Containment Hierarchy)を構成します。ほんとに簡単なものではありますが、「クラスとインスタンスの関係=IS-A関係」と「全体と部分の関係=HAS-A関係」を構成できるのです。
デスクトップにおいて長年使われている素朴で単純な構造的編成の方法を、Webサイト/Webアプリケーションでも使おうとすると、動詞の取り扱いが鍵になります。その動詞という概念は、HTTPの統一インターフェースの要<かなめ>であるHTTPメソッドと概念も目的も一致します。単純で遍在的*4なメカニズムを追求すると、違った方向から同じ概念に出会うことがありますが、これもその事例ですね。