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

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

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

参照用 記事

Webサービスの設計: ハイパーオブジェクトとトリガー

「Webサービスを設計するための単純明快な方法」の続き、あるいは補足です。

内容:

Web APIもWebサイトも同じ

僕の方針は、プログラムが利用するWeb APIであっても、次の原則で設計することです。

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

人間用のサイトを一切作らないときでも同じ原則を適用します。転送オブジェクト(レスポンスのエンティティボディに入るデータ)の形式が(X)HTMLでないときでも同じ原則に従います。

「人間+ブラウザ」用の転送オブジェクトの形式(フォーマット)といえば、もちろんHTMLです。HTMLの最も重要な特徴はハイパーリンクです。ハイパーリンクがWebを形作っているのです。ですから、HTML以外のフォーマットを使うときでもハイパーリンク機能は絶対に使うべきです! 個々のフォーマットの詳細は捨象して、ともかくもハイパーリンク機能を持つようなデータ(Webの転送オブジェクト)をハイパーオブジェクトと呼ぶことにします。ハイパーオブジェクトに含まれるハイパーリンク(のアンカー)をトリガーとも呼びます(こう呼ぶ理由は後述)。

トリガーとは

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

さて、HTMLフォーマットとHTML文書は典型的かつ最重要なハイパーオブジェクトです。a要素、form要素、link要素がそのトリガーです。場合により、img要素、script要素もトリガーと考えることがあります。トリガーはURL(href属性、src属性)を持っていて、そのURLが指し示すリソースや処理をサーバ側に要求するリモコン・ボタンとなります。

単純なアンカー <a id="ChimairaSite" href="http://www.chimaira.org/">キマイラ サイト</a> はトリガーですが、{"id" : "ChimairaSite", "href" : "http://www.chimaira.org/", "text" : "キマイラ サイト"} というJSONオブジェクトに同じハイパーリンク機能を持たせれば、これはJSONにおけるトリガーになります。そして、このようなトリガーを含むJSONデータはハイパーオブジェクトとなるのです。(「JSONだってハイパーメディア -- JSONハイパースキーマ仕様をなんとかしたい」も参照。)

あえてトリガーと呼ぶ理由を述べておきます; 本来のハイパーリンクは、リソース間の関係を表すものです。3つ以上のリソースが関与する関係でもかまわないし、方向性を持たなくてもかまいません。しかし現実には、「2つのリソースの間の方向を持つ関係」をインライン方式(どちらかのリソースにリンク記述を埋め込む方式)で表すリンクしか使われていません。それと、form要素も仲間に入れると「二者間の関係」という解釈は苦しくなります。

そこで、手続き呼び出し的な解釈を採用して、サーバー側のなんらかの処理(アクションと呼びます)を呼び出すデータ構造とメカニズムは総じてトリガーと呼ぶことにします。「手続き呼び出し」と聞くだけで顔をしかめる人もいるでしょうが、便利で分かりやすいなら、特定の宗派や党派に与する必要はありません。それに、リソース(の表現)を転送するだけのアクションを呼び出すなら、それはリソース指向と整合します。

トリガーの構造

HTMLのa要素、form要素、link要素において、トリガーとして必要な情報は属性に指定されます。トリガーに関係する属性を列挙してみます。記述形式としてはCatyスキーマ言語を使います。(Catyスキーマ言語の構文は見れば分かるものです。)

type uri = string(format="uri");
type mediaType = string(format="media-type");
type httpMethod = ("GET" | "PUT" | "POST" | "DELETE" | "HEAD");

type Trigger<InputType> = {
 // 個々のトリガーを識別する属性
 "id" : string?,
 "name" : string?,
 "class" : string?,

 // ハイパーリンクの記述
 "href" : uri,
 "rel" : string?,
 "rev" : string?,
 "type" : mediaType?,
 "method" : httpMethod?,

 // 手続き呼び出し的なデータ項目
 "verb" : string?,
 "input" : InputType,

 // その他いろいろ
  * : any?
};

ここで、InputTypeは型変数で、実際に使うときは何らかの型に具体化されます。変数としての型InputTypeは、フォームの入力に要求される型だと思ってください。ですから、入力を伴わないトリガーのときは無視できます(InputTypeの値をnullとかundefined*2にする)。

個々の応用で使うトリガー型は、上に定義した最も一般的と思える型(総称型)のサブタイプになるように定義します。例えば、単純なアンカーは次のように定義できます。

type SimpleAnchor = {
 "id" : string?,
 "href" : uri,
 "text" : string
};

このSimpleAnchor型のインスタンスのひとつが {"id" : "ChimairaSite", "href" : "http://www.chimaira.org/", "text" : "キマイラ サイト"} です。

トリガーについてもっと

次の型もトリガー型になります。

type PostForm<InputType> = {
 "href" : uri,
 "method" : "POST", // POSTに固定
 "verb" : string?,
 "input" : InputType, // 色々な型を許す

  * : any?
};

PostForm型には型変数InputTypeが含まれるので、これを具体化してみます。

type UserInfo = {
 "userId" : string(minLength=3, maxLength=12),
 "password" : string(minLength=6, maxLength=16)
};

type UserLoginForm = PostForm<UesrInfo>;

念のため、型変数が具体化された後の形を書いてみると*3

type UserLoginForm = {
 "href" : uri,
 "method" : "POST",
 "verb" : string?,
 "input" : {
   "userId" : string(minLength=3, maxLength=12),
   "password" : string(minLength=6, maxLength=16)
  },

  * : any?
};

このデータ型のインスタンスとしては次があります。

{
  "href" : "http://example.jp/user/login.cgi",
  "method" : "POST",
  "input" : {
    "userId" : "m-hiyama",
    "password" : "xxxxyyyy"
  }
};

このなかで、inputプロパティの値である {"userId" : "m-hiyama", "password" : "xxxxyyyy"} はサーバー側に送られて、アクションの入力になります。対応するHTMLフォームは次のようになるでしょう(実用的にはひど過ぎる表示ですが)*4

<form
  action ="http://example.jp/user/login.cgi"
  method = "POST"
>
  <input type="text" name="userId" /> <br />
  <input type="password" name="password" /> <br />

  <input type="submit" />
</form>

トリガーのデータ型定義やインスタンスから、HTMLアンカーやHTMLフォームを自動生成するには情報が不足ですが、人間の知的解釈が介在すれば、トリガーとHTML要素(a要素、form要素、link要素)の対応を付けることは容易です。

ハイパーオブジェクトの基礎フォーマットとしてJSONやXMLを採用すれば(そして多少頑張れば)、人間の知的解釈を不要にできます。(X)HTMLを使う場合でも、アノテーションやコンベンションでルール化をすれば、「人間の知的解釈」なしでも最低限のHTML文書の生成は可能でしょう。最低限のHTML文書とは、クリーンHTM文書(「Webサービスを設計するための単純明快な方法」の冒頭を参照)のことです。

ハイパーオブジェクトを返すRPC

トリガーは結局、サーバー側処理の呼び出しを表現します。RPC(遠隔手続き呼び出し)の呼び出し側スタブだという解釈です。ただし、従来のRPCと違う点は、手続きがハイパーオブジェクトを返し、戻り値であるハイパーオブジェクトがクライアント側状態遷移を引き起こす点です。「アクションの戻り値=ハイパーオブジェクト=クライアント側状態」と考えます。これは、ブラウザによる素朴な閲覧行為とまったく同じモデルなので、「API体系は、人がブラウザで閲覧するためのサイトとまったく同じ構造にする」ことになります。

注意しないとRPCの弊害を再現させてしまいますが、僕は「抽象化された動作/行為」というダイナミックな概念(それがアクション)なしにサービスを設計するのはどうも困難だと感じるので、トリガー、ハイパーオブジェクト、アクションといった概念と用語は必要だと思うのですよ。

*1:[追記]「Webサービスの設計: ハイパーオブジェクトはワークフローやインターフェースも運ぶ」にメリットを書きました。 [/追記]

*2:現在のCatyスキーマでは、undefinedの代わりにnever?と書きます。予約語を増やしたくないという事情ですが、never?は分かりにくいかも。

*3:verbは特定の文字列に固定するか、使わないのがいいと思います。その指定には見慣れないスキーマ構文が出てくるので、とりあえずそのままにしておきます。

*4:HTTPSを使うかどうか、とかは今の議論とは別の問題です。