2010-10-28

リクエストの表現 (あるいは URI) の設計 - Blogger Glass

Blogger Glass では、リクエスト(特定の機能を実行するための URL)を表現するために「クエリ変数」を用いている。具体的には、個別の記事を見るためのリクエストは以下のようになる。

http://bloggerglass.appspot.com/post/?id=1234567890

Blogger Glass にとって(Blogger にとっても)、記事は最小のリソースだが、記事 ID はそれを特定するために必要十分な情報だ。言い換えると、/post/ リクエストに対する付加情報(引数と言っても良い)には記事の ID 以外にはない。このことを踏まえると、/post/?id=1234567890 という表現の中の ?id= の部分は余計なものだとわかる。これは以下のように書けばすっきりした表現になる。

http://bloggerglass.appspot.com/post/1234567890

同じことは他のリクエストに対してもあてはまるだろうか?

2 つの形式

(「RESTful Webサービス」p.126)
パス変数は階層をトラバースしているように見えるし、クエリ変数はアルゴリズムに引数を渡しているように見える。「検索」にはアルゴリズムのような響きがある。

以下では、「クエリ変数」を使ったリクエストの表現を「アルゴリズム形式」、パスにマップした表現を「パス形式」と呼ぶことにする。

プログラミング的に見れば、必要な情報をウェブアプリに渡すという意味で 2 つの形式は等価だと言える。ただし、アルゴリズム形式は、伝統的に CGI で使われてきたこともあり、パス形式より簡単に扱える(後述)。

ヒトが読むことを想定するなら、アルゴリズム形式よりもパス形式の方が見やすい。先の個別記事を見るためのリクエストの例のように、パスの階層の意味が明確ならなおさらだ。

とはいえ、ウェブサービスを作っているならともかく、ウェブアプリではトップの URL を別にして、リクエストを表現する URL はユーザの目に触れることを意図しない。であれば、どちらを使ったところで「ユーザ体験」に差が出るとは思えない。

試してみる

なにはともあれ、試してみよう。

Blogger Glass 全リクエスト形式 (省略形をのぞく)
リクエスト 機能
/?page=<number> number で指定したページの一覧を表示する。
/post/?id=<id> id で指定した記事の内容を表示する。
/search/?label=<string> ラベル string で記事を絞り込んむ。結果が複数ページにわたる場合は、最初のページ(ページ番号 0)を開く。
/search/?label=<string>&page=<number> ラベル string で記事を絞り込んだ結果のうち、number で指定したページを開く。
/settings/ 設定変更のための画面を開く。

これらアルゴリズム形式のリクエストをパス形式で表現すると以下のようになる(付加情報のない /settings/ を除く)。

アルゴリズム形式とパス形式の対応
アルゴリズム形式 パス形式
/?page=<number> /<number>
/post/?id=<id> /post/<id>
/search/?label=<string> /search/<label name>
/search/?label=<string>&page=<number> /search/<label name>/<number>

以下では、個別記事を見るためのリクエストをパス形式にするための実装上の変更点を見ていく。

追記@2010-10-31

以下の記述はほぼ無用のものだった。GAE の webapp フレームワークでは、リクエスト URL とリクエストハンドラを結びつける URL マッピングという機能を提供している。これにより、上述のパス形式で表現されたリクエスト URL の中から、その一部をパラメータとして切り出すことが簡単にできる。正規表現のグルーピングを使って切り出す部分を指定すると、get メソッド等の引数となってわたってくる。

具体的にはこうなる。

(src/postview.py より)
class PostViewHandler(webapp.RequestHandler):
    [...snip...]
    def get(self, post_id):
        [...snip...]

def main():
    application = webapp.WSGIApplication([('/post/(\d+)', PostViewHandler),
                                          ],
                                         debug=True)

Bloggger Glass のコードには、GAE の便利な機能を知らないために書いてしまっている無駄なコードが他にもまだあるんだろうな。

app.yaml への記述
(src/app.yaml より)
- url: /post/|/post/.*/?
  script: postview.py

省略形の /post//post/1234567890/ のように末尾の余分なスラッシュに対応するために、URL パターンは少し複雑になっている。

リクエストハンドラの変更

変更点は主に 2 ヶ所。1 つは URL から付加情報を取り出す部分、もう 1 つは URL に対するハンドラの対応づけだ。

付加情報の取り出しは util モジュールに関数として置く。

(src/util.py より)
def get_params(uri, request_type):
    """This function assumes that a request uri must be written in
    one of the following forms:
    - 'http://<host>/<param_1>/<param_2>/.../<param_n>/'
    - 'http://<host>/<request_type>/<param_1>/<param_2>/.../<param_n>/'
    """
    uri_parts = uri.split('/')[3:]
    if len(uri_parts) < 1 or uri_parts[0] == '':
        params = None
    elif uri_parts[0] == request_type:
        params = uri_parts[1:]
    else:
        params = uri_parts
    return params

呼び出す側を以下に示す。

(src/postview.py より)
        post_id = util.get_params(self.request.uri, 'post')[0]

ハンドラの対応づけ部分はこうなる。

(src/postview.py より)
def main():
    application = webapp.WSGIApplication([('/post/', PostViewHandler),
                                          ('/post/.*/?', PostViewHandler),
                                          ],
                                         debug=True)

app.yaml 同様の指定になっている。2 つに分けているのは見易さのためだ。

参考文献

RESTful Webサービス
Leonard Richardson, Sam Ruby
オライリー・ジャパン ( 2007-12-21 )
ISBN: 9784873113531
おすすめ度:アマゾンおすすめ度

関連リンク

関連記事

0 件のコメント:

コメントを投稿