前回の記事では、REST の原理原則についてお話させて頂きましたが、ざっと RESTの概要をご理解頂けましたでしょうか。続編となる本記事では、もう少し踏み込んだ形で、REST アーキテクチャの種類と、導入における勘所をお話させて頂きます。

1. RESTfulとREST-RPC、そしてハイブリッド

一般的にRESTスタイルのアーキテクチャには以下の3つがあります。

RESTful

外観的にも内観的にも、アーキテクチャがRESTの思想に準拠しているものを指します。
どこまで完全準拠するかといった点では、アーキテクトの主観が介入するところではありますが、私は「5.RESTのコンセプトと特徴」で説明した内容程度が現実的な路線だと考えています。

REST-RPC

呼び出すサービスがURIとHTTPメソッド情報ではなく、エンティティボディ内に含まれる情報によって解決されるものを指します。
例えば以下のようなものです。

POST /services/member HTTP/1.1
User-Agent: Fiddler
Host: www.mamezou.com

<?xml version="1.0" charset="utf-8" standalone="yes" ?>
<call>
    <package>search</package>
    <class>Member</class>
    <method>getMember</method>
    <parameter type="java.lang.String">kazuhito_gomi</parameter>
</call>

※ここでは、searchパッケージ内にMemberというクラスがあって、getMember(String)というメソッドが定義されているものと仮定します。

RPCスタイルの重大な欠点として、大きく以下のものが挙げられます。

  1. 一度稼働したらインタフェースを変更することが非常に困難
  2. プログラムの(静的な)構造を公開先に強く意識させなくてはならない
  3. 異なるアーキテクチャのシステムが乱立する(例えば、エンティティボディの形式や構造が異なるなど)

REST-RPCハイブリッド ※1

あるときはRESTfulであるが、あるときはそうでなくなるものを指します。
例えば、以下のURIによって公開された会員情報取得サービスがあるとします。

GET /services/member?class=search.Member&method=getMember&arg=kazuhito_gomi HTTP/1.1
User-Agent: Fiddler
Host: www.mamezou.com

操作の内容はHTTPメソッド(GET)で表現されていますし、(呼び出すコンポーネントの情報が含まれていますが)URIもユニークなものとなっているので、RPCスタイルではあるもののRESTfulと言ってもおかしくありません。
では次の会員情報削除サービスはどうでしょう。

GET /services/member?class=search.Member&method=deleteMember&arg=kazuhito_gomi HTTP/1.1
User-Agent: Fiddler
Host: www.mamezou.com

URIはユニークとなっていますが、操作の内容がHTTPメソッドで表現されていません。本来であればDELETEメソッドを使用すべきですが、GETメソッドが使用されています。つまりRESTfulなサービスではありません。

どのアーキテクチャがより適切であるかというのは、プロジェクトの要件や特性、ポリシーによって変わるものなので、一概に言うことは出来ません。重要なのは、いままさに構築しようとしている対象のシステムが、どのアーキテクチャスタイルに則るのかを規定、理解し推進することです。RESTfulを目指していたはずが、いつのまにかREST-RPCハイブリッドになっていたなんてことがないように。

※1: 名著「RESTful Webサービス」の著者らによって提言された造語です。

2. RESTの誤解

前述した通り、RESTはあくまでそのアーキテクチャスタイルのことを指しています。そのため誤解や誤った実装を見逃してしまうことも多々あります。実装したアプリケーションは動作しているわけですから。

よくある誤解として以下のものがあります。

送受信するHTTPデータはXMLでなくてはならない

そんなことはありません。WWWもRESTスタイルアーキテクチャの一形態です。エンティティボディ(送受信するメッセージ)は、HTMLでも単なる文字列でもCSV形式のデータでもJSON形式のデータでも何でも良いのです。XMLが利用されることが一般的なだけです。「XMLやHTTPを使った」、「XMLなどをレスポンスとして」、「XMLを直接操作する」といった表現で解説されている情報が多く、これが誤解を招いています ※2

※2: 「や、など」といった言葉は読み手に想像を任せていますから。

SOAPより軽いWebサービス仕様

SOAPとRESTは同じ天秤で量れるものではありません。SOAPは多くの仕様(WS-*で表現されるもの)を含んでおり、『Webサービスという枠組みの中』に存在するものです。一方、RESTはHTTP上でのみ成立する『Webという枠組みの中』に存在するものです。

(誤解を恐れずに)単に『Webサービスを実現するもの』と捉えれば、確かにRESTはSOAPより軽量ではありますが、RESTは非同期メッセージングなどの複雑な処理は得意としません。むしろ複雑な処理はSOAPの方が得意でしょう。これはそもそもの対象(目指しているもの)が違うからです。

SOAPの対抗となるAPI仕様

先述しましたが、RESTはAPIを提供するためのものではありません。『REST API』ですとか、『SOAP/REST』と表現しているサイトが多いから誤解が生じるのだと私は思います。『REST Style HTTP API』と理解すれば決してSOAPの対抗ではないことがわかります。

4つの統一インタフェースではエンタープライズシステムに不十分ではないか

HTTPはドキュメント指向であり、かつRESTはステート(リソースの状態)を公開するものです。ステートを変更するためには、たった4種類の統一インタフェースでも充分なのです。

プログラムからはSOAP Webサービスの方が簡単に接続できる

SOAPを用いたWebサービスの場合、Visual StudioなどのIDEが生成するプロキシがSOAPの複雑な部分を隠蔽してくれるため、単なるメソッド呼び出し(RPC)のように簡単に接続することが出来ます。

しかし、この『簡単さ』がクセ者で、『簡単』であるが故に、アプリケーション開発者はプロキシの動作やSOAPの重量なコストのことなど気にも留めずに「SOAP Webサービス」を多用し始めます。SOAP Webサービスをむやみに多用しているシステムにおいて、テスト工程で初めて性能問題が顕在化し、結果大きな手戻りが生じるというのはよく聞く話です。

一方RESTスタイルで接続する場合は、接続ユーティリティの実装やインタフェースの規定といった開発コストはあるものの、接続コストはSOAP接続より圧倒的に低く、原理的には単純です。また、統一インタフェース(GET/POST/PUT/DELETE)を意識することで、サービスの粒度が適度に保たれますので、過剰に遅い処理が生じるリスクも低くなります。

SOAP Webサービスは簡単に使えるが実はクセ者

3. RESTの誤った実装

RESTスタイルのアーキテクチャを採用する際、以下のような誤った実装に陥りがちなので注意が必要です。

何でもGET、何でもPOST

現在のHTML仕様ではGETとPOSTしかサポートしていないために※3、この2つの方法しか馴染みがないのが一般的です。ですが、RESTではクライアントを特定していません。ブラウザの場合もあるでしょうし、独自のクライアントアプリケーションの場合もあるでしょうし、他システムの場合もあるでしょう。

何でもかんでもGETとPOSTで代用しようとすると、実際にサーバー側で行われる処理が分かり難くなると同時にHTTPの仕様を無視することになり、結果的にRESTとは程遠いものとなります。

なぜ『何でもGET』、『何でもPOST』に陥ってしまうのでしょうか。

  • 何でもGET
    • URIが全ての情報を含んでいるので実装が簡単だから
    • 特定のURIをブラウザに入力すれば容易にテストが行えるから
  • 何でもPOST
    • GETはURIの長さに制約があるのでいつか溢れてしまうが、POSTには制約がないから(もしくはGETの制約に比べると緩いから)
    • フォームデータや検索条件などをPOSTで送信した経験があるから

これらは現実的な発想にも思えますが、好意的に解釈してもRESTとは言えません。では今現在、ブラウザをクライアントとする場合どのようにしたら良いのでしょうか?

  1. 非RESTスタイルを採用する箇所を局所化する
  2. 各ページにJavaScriptを埋め込み、リクエスト送信時に適切なHTTPメソッドに置き換える

弊社が2008年に参画したプロジェクトでは、フロントサーバーとバックエンドサーバーの連携をRESTスタイルで実現するというアーキテクチャを採用しました(1.の方式)。クライアントとフロントサーバーとのやり取りは旧態依然のGETとPOSTのみですが、こうすることでHTML4の仕様上の制約を受けるのはフロントサーバーのみとなり、バックエンドサーバーは影響を受けません。

プロジェクト発足当初から、「10年使えるシステムにしたい」という顧客要望があったため、HTML4の制約を受ける箇所を局所化し、全体に影響を及ぼさないようにしました。

※3: 2010年9月勧告予定のHTML5では、PUTとDELETEもサポートされる予定です。

HTTPステータスコードの無視

HTTP仕様では、処理の結果はステータスコードで表現することとなっていますが、これを無視したシステムを作ることも出来ます。

例えば、常に200(OK)が返ってくるけれども、HTTPレスポンスの内容によっては「成功」の場合もあり、「リソースが見つからない」の場合もあり、「サーバーエラー」の場合もあったりすることが出来ます。つまり、処理の結果はレスポンスのエンティティボディに含み、クライアントはエンティティボディの内容によって結果を判断します。

これには次のような欠点があります。

  • エンティティボディを解析してみないと、成功したのかエラーが発生したのか判断出来ず、常に解析処理が必要となるため極めて非効率
  • サービス仕様(システム独自のエラーコード)を詳細に理解していないと、エラー原因が直感的に理解出来ない
  • 他システムと連携することになった場合、独自の仕様を相手側に強く意識させてしまうので、相手側のアーキテクチャを歪めてしまう可能性がある

RESTスタイルのアーキテクチャであるならば、サーバー側の処理結果はHTTPステータスコードで表現すべきです。HTTP仕様には豊富なステータスコードが用意されており、ほとんどの場合はそれで事足りるはずです※4。もちろん詳細なエラーコードが必要な場合もあるでしょう。その場合は、詳細なエラーコードはエンティティボディに含め、おおまかな処理結果をステータスコードで表現するのが良いです。決して、処理が失敗しているのに200を返してはいけません。

※4: どうしても既存のHTTPステータスでは表現出来ない場合には、システム独自のHTTPステータスを定義しても良いと思います。ただし、独自定義だということをきちんと明確にしておくべきです。

HTTPステータスコードの誤用

HTTPステータスコードには仕様として多くのものが定義されています。200(OK), 201(Created), 204(No Content), 404(Not Found), 409(Conflict), 500(Internal Server Error)などがあります。

例えば、会員情報を登録するサービス仕様があったとしましょう。

クライアントから送信されたリソース(ユーザIDや氏名、住所など)を検証し、正しいリソースであれば会員DBに登録するサービスを実装する。

既に会員情報が登録されていたらエラーとする。

どんなステータスコードを返せば良いでしょうか。ここで500を返してはいけません。確かに一般的なシステムの考え方であればサーバーエラー(500)でしょう。しかし、サーバーは仕様通りに動作しており、「同じリソースが既に存在している=リソースの競合」と考える方が自然です。このような場合は競合(409)を利用するべきでしょう※5

※5: 400(Bad Request)でも良いかも知れません。少なくともクライアント側の操作に問題があるわけですから、4xx系のステータスコードになるでしょう。

適当なURIの割り当て

「一意であれば何でも良いのでは?」という意見もあります。確かにその通りと言えます。しかし、多くの場合、リソースは概念的な階層構造を持っています。例えば、「東京都新宿区西新宿にある豆蔵という会社の五味さん」といったように。これをURIで表現すると、「/東京都/新宿区/西新宿/豆蔵/五味さん」になるでしょう。決して「/新宿区/豆蔵/西新宿/五味さん/東京※6」ではありません。URIは、リソースだけでなくロケールも一意に特定するという役割も持っています。

ですので、階層やロケールも意識した上でURIを決定すべきです※7

※6: 「新宿区にある豆蔵という会社で、あ...西新宿ね。その会社の五味さん。ちなみに新宿区は東京だよ。」なんて表現、分かり難くて仕方ないですよね。

※7: URIの/(スラッシュ)は階層構造を連想させます。地図情報を考えてみましょう。緯度経度のようにそれぞれは並列に存在するものであり、階層構造という意味に適さないものもあります。このようなものを表現する場合は、マトリクスURLを使用します。マトリクスURLを使用すると、緯度27度、経度34度のパスはhttp://xxxxx/27,34のようになります。

リンク先の構築をクライアントに任せる

リンクはサーバー側に存在するリソースの位置を示すものであり、格納場所を知っているのはサーバーだけです。変更もサーバー側の意思だけで行えます。もし、リンクの構築をクライアント側で行うとしたら、サーバーはクライアントの実装制約によって格納先の変更など多くの自由を失うことになります。

PUTとPOSTの誤用

PUTとPOSTの使い分けは非常に難しいです。どちらも新規リソースの作成用に使用されますが、一般的には以下の指針に基づいて決定します。

  • リソースに割り当てられるURIはクライアントによって決定されるのか、サーバーによって決定されるのか

前者の場合はPUTを、後者の場合はPOSTを使用します。
ご存知の通りPUTは「置く」という意味を持ちます。つまり、置く側(クライアント)は「どこに置くか」を知っているはずです。一方、POSTは「投函する」という意味を持ちます。投函する側(クライアント)は「どこに投函するか」は意識していますが、「どこに置かれるか」は知りません。

4.プロジェクトで使えるのか

さて、ここまでRESTについてお話してきました。おそらく『原理原則や特徴はわかったが、大規模なシステムで本当に使えるのか? 』といった疑問が出てくると思います。答えはYesです。

弊社では、2008年にRESTスタイルアーキテクチャを採用した大規模B2Cサイトのリプレースにアーキテクトチームとして参画しました。サイト名を公表することは出来ませんが、多くのみなさんがご存知のサイトです。

システムの概要は以下の通りです(メインに担当した箇所は赤点線)。

RESTスタイルアーキテクチャを採用した大規模B2Cサイトのリプレース(システムの概要)

弊社は主に、システム全体のアーキテクチャとJavaで実装するアプリケーションのアーキテクチャ策定を担当しました。サブシステムにはPHPで実装されているものもあり、汎用性を持つためにエンティティボディにはXMLを採用しました。

Javaで実装したサブシステムには、java.netで開発されていて、コミュニティの活動が最も活発なJerseyフレームワークを採用しました。Jerseyフレームワークは、JAX-RS仕様の参照実装で、JAXBの開発メンバである川口耕介さんも参加しているものです。本格的なシステムでJerseyを採用したのはこれが初めてなのではないかと(勝手に)思っています※8

※8: Jerseyは非常にシンプルで使いやすいフレームワークです。一度試して頂ければ、その簡単さがすぐにわかるでしょう。

 

RESTful Webサービスを実現するには、以下のように考慮しなくてはならない点があります。

バッチ処理などのHTTPメソッドへの対応が困難なもの

一般的なシステムであれば、日次でリソースを一括で書き換える※9こともあり、ひとつのバッチ処理で作成、更新、削除を一括で行う場合、HTTPメソッドへのマッピングをどうすれば良いかという壁にぶつかります。その時は以下の観点で分類してみます。

  1. べき等性は保証されているか。
  2. 粗粒度でリソースを捉え※10、リソースの初期状態と最終状態ではどう違うのか。
べき等性 リソース状態(初期状態⇒最終状態)
なし⇒あり なし⇒なし*1 あり⇒なし あり⇒あり
保証されている POST or PUT *2 - DELETE PUT
保証されていない POST - POST POST

*1: そもそも処理として成立しない。

*2: 処理の内容によって変わる。「5. RESTのコンセプトと特徴」を参照のこと。

べき等性が保証されていないのであればPOSTメソッドが適切でしょう。POSTは『リソース※11を作成する』ということ以外に、『クライアントより指定された何かしらの処理を行う』という役割に使用されることがあります。書籍「RESTful Webサービス」では、これをオーバーロードPOST(POST(p※12))と命名しています。POST(p)の使用は推奨されていませんが、私は恐れずにPOST(p)を使用するべきかと考えます。なぜPOST(p)を使用するに至ったのか、それを説明出来ればそれで良いと思います。

RESTful HTTP as POST(p)

※9: ここではリソースの作成や更新、及び削除全てを纏めて、「書き換える」と表現しています。

※10: 例えば、「A商品の情報、B商品の情報」ではなく、「商品の情報」といった視点で捉えてみます。

※11: 主に親リソースに対する従属リソースを作成する場合

※12: Processのこと。

URIの設計

全てのURIをアーキテクトが決定するわけにはいきませんが、何の指針もないと統一性のない(正しい階層構造となっていない)ものとなってしまうので、設計の初期に指針を決定する必要があります。前述した通り、一般的にはリソースが配置されているロケールと階層構造を意識して指針を決めます。緯度、経度といったように階層構造を持たない場合は、マトリクスURLを採用します。

重要なのは、『クライアントが操作出来るリソース毎にURIを割り当てるべきで、クライアントが実行出来る操作毎にURIを割り当てるべきではない』ということです。

認証

HTTPヘッダ(Authorization)に認証情報※13を載せて、リクエストの都度、認証情報を基にリクエストの承認を行います。これはRESTの思想に合致します。

通常、認証にはBasic認証やDigest認証を利用しますが、場合によっては独自の認証用ヘッダを追加し、システム独自で認証処理を実装します。プロジェクト毎のポリシーに則って実装します。

※13: システム固有のトークンなど。

ロングトランザクション

チケット予約の仮押さえなどロングトランザクションが必要な場合、リクエスト間でひとつのトランザクションを共有してはいけません。これはRESTの思想に反することになります。

リソースに『空き』、『仮押さえ』、『確定』といったように状態を持たせたり、悲観ロックを使用したりするなどします。

ロングトランザクション

5. 最後に

いかがでしたでしょうか?2 回の記事で REST の概念と勘所についてお話させて頂きました。REST とはどこからやってきて、どのように実現されるものなのかを理解して頂けましたでしょうか。

2009年7月2日、W3CはXHTML 2の策定を打ち切り、HTML 5の標準化に注力することを発表しました。先にも少し触れましたが、HTML 5ではPUT, DELETEメソッドがサポートされる予定です。正式に勧告されメジャーブラウザがこれを実現すると、RESTスタイルであることが一般化すると考えられます。もちろんRESTはHTML 5に依存しているわけではないので、従来のJSP+Servletでも十分通用します。

今後みなさんがWebアプリケーションやWebサービス、またはシステム統合(連携)の企画・設計・実装を行う際には、まずRESTの考え方を振り返ってみると、きっとシンプルかつ設計思想が明確なアーキテクチャになるかと思います。少なくとも知らないよりは。