このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

Webサービスの設計: ハイパーオブジェクトはワークフローやインターフェースも運ぶ

「Webサービスの設計: ハイパーオブジェクトとトリガー」において:

僕は「Webとはハイパーリンクなり」と考えているので、Web APIでもなんでもハイパーリンクを使ってないなら「Webっぽい」とは思いません。RPC(遠隔手続き呼び出し)的な要素を取り入れても、ハイパーリンクを活用しているならWebっぽいでしょう。「っぽい」とか「らしい」は単なる趣味嗜好の問題ではなくて、ハイパーリンクの活用は大きなメリットがあります(そのメリットの説明は今日[注:2010年8月11日]はしませんが)。

ハイパーリンク活用の大きなメリット」について、今日(2010年8月25日)は説明します。

内容:

  1. 遠隔呼び出しとHTTP
  2. インターフェースと手順の合意
  3. あなたに任せるわ、私は言われるまま
  4. 理想のケースは人間がクライアントのとき
  5. 状態遷移モジュール
  6. インターネット/Webの驚嘆すべき頑健性

遠隔呼び出しとHTTP

関数呼び出しの関数本体が遠隔地(リモートロケーション)にあるとき、通信により関数呼び出しを行うメカニズムがRPCです。関数の識別情報と引数データをネットワークに送り出し、戻り値をネットワーク経由で受け取ります。次の図の上側は、遠隔呼び出しのイメージを描いたものです。

HTTPのリクエスト/レスポンスも大雑把な物言いをするなら「同じメカニズム」です(図の下側)。ですから、HTTPによる一連のやり取りをRPCモデルで考える事がただちに「悪」というわけではありません。しかし、ローカルの関数呼び出しのセマンティックスをネットワーク経由に直しただけだとWebとの相性が悪く、弊害をもたらします。

ここでは、クライアント側に要求する知識が多いことが「悪」「弊害」のひとつであり、ハイパーリンクによりこの問題をかなりの程度解消できることを説明します。

インターフェースと手順の合意

例題に次のようなインターフェースを考えましょう。


interface Service {
A a();
B b();
C c();
}

A, B, C は型で、a(), b(), c() は関数(メソッド、手続き)だとします。関数の名前a, b, cをURLにして、どこからでも呼び出せるようにしたと仮定します。

このサービスを利用するクライアントは、上のインターフェース記述にあるような、関数の名前(URL)と戻り値型を知っている必要があります。それだけでなく、意味のある処理をするには、どの関数をどんな順序で呼び出すかなどの知識も必要です。

次の図を見てください。

これは、クライアントが a(); b(); c() の順序で関数を呼び出している図です。左側に書いてある番号(丸付き数字)が時間順を表します。ここで、「a(); b(); c() という順序は正しく、a(); c(); b() ではダメ」などの判断をしているのはクライアントです。つまり、クライアントはサーバーについて熟知している必要があります。

クライアントがサーバーについて知っていれば知っているほど、インターフェースや手順が変更されたときの影響に敏感になり、壊れやすいシステムになります。そもそも、サーバーとクライアントのあいだでの合意事項が多かったり共有すべき知識が膨大だと、なにかと手間がかかって大変ですよね。

あなたに任せるわ、私は言われるまま

遠隔呼び出しする関数の戻り値がハイパーオブジェクトだとしましょう。すると、戻り値データにトリガー(「Webサービスの設計: ハイパーオブジェクトとトリガー」を参照)を含めることができます。

関数a()の戻り値型Aがハイパーオブジェクト型で、関数b()を呼び出すトリガーを含んでいるとします。B型もハイパーオブジェクト型で、c()を呼び出すトリガーを含んでいるとします。すると、一連の呼び出しは次のようになります。

クライアントは、最初にa()を呼び出すことは知っている必要があります。しかしその後は、戻り値に埋め込まれたトリガーをたどって正しい呼び出し手順を実行できます。正しい手順を誘導しているはサーバーであり、クライアントはサーバーの提示した情報から次の行動をその場で決めていきます。時間順を表す番号(丸付き数字)が右側に書いてあるのは、手順を決める主導権が右(サーバー)にあることを示します。

トリガーを含んだハイパーオブジェクトは、単にデータ項目達を運ぶだけでなく、知識も運びます。「アプリケーションレベルのプロトコル、ワークフロー、ユースケース、シナリオ」などと呼ばれる「次に何ができるか/何をすべきか、どのようにそれをするか」という知識をクライアントに提供します。

この知識(ワークフローやインターフェース)は、事前に合意している必要がなく、その場で与えられます。例えば、ハイパーオブジェクトAに刷り込まれたインターフェースは次のものです。


interface A {
B b();
}

その場で必要な、その場で許される呼び出しだけがコンパクトに記述されています。

理想のケースは人間がクライアントのとき

事前の合意や予備知識無しでもサーバー(が提供するサービス)を使える典型的な状況は、ハイパーオブジェクトがHTML形式であり人間がクライアントのときです。

アンカー(a要素)があれば、前後の文脈やリンクテキストを読んで、人間は「次に何をするか」をその場で決めます。フォーム(form要素)の場合はさらにすごくて、事前に入力データ(引数に相当)の型もセマンティックスも知らなくても、その場でフォームを埋めてサブミット(呼び出し)できます。

もちろんこれには、HTMLには自然言語の説明が書けて、人間は自然言語を解釈して知的な判断ができるという事情があります。プログラムがクライアントのときは、「リンクテキストを読む」とか、「その場でフォームの入力欄を埋めていく」ことはできません。しかし、人間がクライアントである状況に多少は近づけることはできるでしょう。人間は極めて頑健なWebクライアントです -- この頑健さを支えているメカニズムを真似れば、ソフトウェアWebクライアントを頑健にすることができると思うのです。

例えば先の例で、d()という関数がインターフェースに加わり、a(); b(); c() という呼び出しシーケンスが無効となり、a(); c(); d() に変わったとき、事前合意によるクライアントは破綻します。ハイパーオブジェクトとトリガーによりナビゲートされるクライアントは機能する可能性があります。

状態遷移モジュール

先の呼び出しシーケンス a(); b(); c() を「Webサービスの設計:Webの状態遷移図の描き方」で示したような図示法で描いてみます。ただし、アクションである関数を黒丸で描くのは省略しています。

四角の箱に入り込む矢印は1本しかありません。これが一連の手順を表す状態遷移モジュールへの入り口(エントリーポイント)です。入り口(への入り方)さえ知っていれば、それに引き続く状態遷移はサーバーがナビゲートしてくれます。それが箱の内部の遷移グラフです。

直線状の遷移グラフでは単純すぎるので、もうひとつ例を挙げましょう。

この状態遷移モジュールだと、「a(); b(); d()」、「a(); b(); c(); d()」、「a(); c(); d()」が“正しい”遷移経路となります。この遷移グラフを正規言語を認識するオートマトンとみなせば a, (b | b?, c), d という正規表現が遷移経路の表現になります。しかし、鳥瞰的に正しい経路を知っている必要はなく、各状態点ごとに局所的な情報 -- つまり次に「移るべき状態=次に呼び出すべき関数」さえ分かれば必然的に正しい経路をたどれます。

インターネット/Webの驚嘆すべき頑健性

「Webサービスの設計: ハイパーオブジェクトとトリガー」において次のような方針を述べました。

  • API体系は、人がブラウザで閲覧するためのサイトとまったく同じ構造にする。

Webはこの世で最も大規模で複雑な分散システムです。それがたいしたトラブルくもなく日々運用されています。サーバーは膨大な数だし、大きな多様性を持ち常に変化し続ける存在です。にも関わらずシステムは破綻せずに機能しています。この理由のひとつは、クライアントの大多数が人間+ブラウザだからでしょう。ハードコートされたソフトウェア・クライアントがWebの変化に追従するのは困難です。

この状況からすると、「ソフトウェア・クライアントも、人間+ブラウザに似せて作る」という方針は、既に成功している方法を真似るという意味で悪くないと思っています*1

*1:人とプログラムを区別しない理由は他にもありますが。