制約に従いながらもHTTPを自由にするRESTful――『JavaによるRESTfulシステム構築』
JavaによるRESTfulシステム構築 Bill Burke(著) arton(監修) 菅野良二(翻訳) オライリージャパン 2010年8月 ISBN-10: 4873114675 ISBN-13: 978-4873114675 3360円(税込) |
REST(Representational State Transfer)はアーキテクチャスタイルの1つである。RESTfulとは、RESTの制約に従ってRESTらしい振る舞いをするシステムを指し示す。英語の語感はよく知らないが「RESTらしく」とか「RESTのように」というニュアンスでよいのだと思う。それでは、RESTとは何だろうか。
Roy Fieldingは、HTTPプロトコル規格の策定に尽力した1人である。彼の著した博士論文の中で、RESTという概念は提唱された。
- Webがこんなにも普及している理由は何か?
- Webをスケーラブルにしているものは何か?
- Webのアーキテクチャを、どのようにして自身のアプリケーションに適用できるか?
RESTはこれらの問いに端を発し、「Web世界の原理原則」という答えを提示した。
エンジニアとして、3番目の問いに注目したい。狭義の意味で、RESTはWebアプリケーションに適用した設計スタイルを指し示す。RESTfulとはWebアプリケーションのための作法の1つと理解してもよい。
RESTでは以下の原則を定義する。
- アドレス可能なリソース
- 制約された統一インターフェイス
- 表現指向
- ステートレスな通信
- アプリケーション状態エンジンとしてのハイパーメディア
RESTfulなシステムを実現するためのJavaの標準APIがJAX-RS(Java API for RESTful Web Services)である。新しい時代のAPIらしく、JAX-RSを使うために実装継承や特殊なインターフェイスは必要ない。基本的な機能はアノテーションで提供される。現在のJavaのパラダイムでは、アノテーションによるメタプログラミングは必須だ。本書はアノテーションプログラムの良質なサンプルとしても活用できるだろう。
JAX-RSがRESTfulを実現するためにどのような作り込みが必要なのか、本書を紐解きながら順番に見ていこう。
■アドレス可能なリソース
RESTでは、「リソース」という概念で情報やデータを抽象化し、URIはリソースを指し示すものとして定義する。リソース中には、HTMLドキュメントや画像も含まれ、URIでアクセスする。これを「アドレス可能性」という。URIのないリソースがRESTの世界で存在することは許されない。すべてのリソースはURIで管理される。
JAX-RSで特定のURIと実装を結び付けるのは難しくない。
@Path("/restsample") public class RestSample { @Path("{id}") public String getData(){}
@Pathアノテーションにより RestSample#getData()というメソッドを「/restsample/任意の文字列」というURIと関連付けた。次に紹介するメソッド選択のアノテーションと組み合わせることにより、/restsample/123というアドレスからRestSample#getData()が呼び出せる。
■制約された統一インターフェイス
RESTでは、GET、PUT、POST、DELETEなどのHTTPメソッドだけを使ってアプリケーションを構築する。各メソッドは決められた制約を守って実装する。
GETはページや画像を取得するための読み込み専用メソッドであり、POSTはフォームデータを送信するメソッドだ。ブラウザではGETかPOSTぐらいでしか使うことがない。しかし、AjaxなどのHTTPクライアントを使えば、すべてのHTTPメソッドを扱うことができる。
PUTやPOST、DELETEはリソースを更新・削除する操作である。HTTPメソッドの中でPOST以外の操作は「べき等」となる。べき等とは「何回、操作を繰り返しても常に同じ結果が得られる」という意味だ。あるリソースを更新するPUTメソッドは、いつ実行しても結果は同じである。すでに更新しているからという理由でエラーを返さない。更新処理は常にリソースを「更新した結果」を返す。同様に削除処理は、いつでも「削除された結果」を返す。これがべき等だ。
DELETEメソッドは、リソースの削除に行うべきだといわれている。RESTでは、DELETEメソッドをキャンセルの意味で使うことを推奨しない。キャンセルは「状態を更新する操作」である。リソース更新はPUTやPOSTの仕事なのだ。論理削除と物理削除の違いと考えればいいだろう。
メソッド選択はアノテーションをバインドするだけである。
@GET @PATH("{id}") public String getData(@PathParam("id") int id){}
/restsample/123というURIが、GETメソッドでアクセスできるようになった。戻り値が文字列なので、ブラウザでも結果を表示できるだろう。さらに、@PathParamにより仮引数のidに123という値がインジェクションされる。クエリやフォームデータを取得する@QueryParamや@FormParamやCookieの値を取得する@CookieParamというアノテーションも同じようにに扱える。もちろん、複数のパラメータやマトリクスパラメータも同様にアノテーションを指定するだけでよい。
メタプログラミングの世界では、アノテーションが行う処理は意識しない。どのようなパラメータでも、対応するアノテーションの種類を指定するだけで値が取れる。これにより、煩雑なCookie取得やパラメータ取得のロジックから解放される。
しかし、これらは単なるアノテーションである。JAX-RS経由でなくても、通常のクラスとしてインスタンス化、メソッド呼び出しが可能である。JUnitを使い、あらゆる値のテストを自動化することもできる。最終的に、「ブラウザを立ち上げてフォームから入力」というテストは必要ではある。しかし、そこに至るまでの試行錯誤の手間は大幅に軽減されるだろう。
RESTでは、URIは単なる文字列ではない。サブリソースロケータというURIの断片を持つクラスを使えば、/hogeと/fugaというURI情報を持つ個別のクラスを組み合わせて、/hoge/fugaというURIを動的に構築できる。
一般的なフレームワークでは、URIはアプリケーション構築時に固定の文字列として定義することが多い。PHPやPerlなどのスクリプト系言語では、物理ファイルの場所に依存することもある。しかし、物理ファイルの置き場所と抽象表現であるURIには直接的なつながりはないはずだ。
■表現指向
クライアントに渡されるデータはHTMLドキュメントや画像ばかりではない。XML、JSON、YAMLなど、たくさんの形式でデータを受け取る。表現指向ではデータフォーマットは自由に操作できる。
HTTPには、Acceptヘッダを利用して、欲しいデータをMIMEタイプで指定するコンテンツネゴシエーションという機能がある。Acceptヘッダを使って、XMLやJSONデータなど、自分が欲しい形式でリクエストを投げる。サーバサイドが対応していれば任意のMIMEタイプでリソースが取得できる。レスポンスデータはサーバサイドでMIMEタイプが決定する。これはネゴシエーションであって、クライアントはこの形式のデータが欲しいと要求するだけにすぎない。
@Consumesは、メソッドが期待する、HTTP入力リクエストのメディアタイプを指定し、AcceptヘッダでリクエストされたMIMEタイプと一致するメソッドを選択する。そして、呼び出されたメソッドは、@Producesが指定するコンテンツタイプでデータを返却する。
@Consumes("application/xml") @Produces("text/plain") public String getData(){}
異なる表現形式をシームレスに扱うにはどうすればよいだろう。XMLを生成するために、
StringBuinder builder = new StringBuilder(); builder.append(""); builder.append(" ");piyo "); builder.append("
というコードを書いてはいけない。普通はJAXB(The Java Architecture for XML Binding)などのマーシャル、アンマーシャルを支援するAPIを使用する。
@XmlRootElement(name="hoge") @XmlAccessorType(XmlAccessType.FIELD) public class Hoge{ @XmlElement private String fuga; public String getFuga() { return fuga;} public void setFuga(String fuga) { this.fuga = fuga;} }
というBeanを用意すれば、データ形式の変換はこのようにできる。
// JAXBのコンテキストを取得 JAXBContext context = JAXBContext.newInstance(Hoge.class); // マーシャルを行うためのクラスを用意 Hoge hoge = new Hoge(); hoge.setFuga("piyo"); // マーシャラーを生成してインスタンスからXMLへ // マーシャリングしてファイルに書き出し context.createMarshaller().marshal(hoge, new File("hoge.xml"));
というコードでデータの内容をXMLファイルに保存できる。
XMLファイルから読み込む場合は、以下のようになる。
/ JAXBのコンテキストを取得 JAXBContext context = JAXBContext.newInstance(Hoge.class); // アンマーシャラーを生成してファイルから読み込んだXMLを // インスタンスへアンマーシャリング Hoge hoge = (Hoge)context.createUnmarshaller().unmarshal(new File("hoge.xml"));
データの相互変換がシンプルなコードで実現できることにより、表現できるタイプが格段に向上するだろう。
JAXBもアノテーションベースのAPIである。アノテーションの多用は、ある意味においてプログラムの可読性を下げることにつながる。他のAPIと組み合わせたとき、1つのクラスやメソッドに付けるアノテーションを大量に記述しなければならないケースがどうしても発生する。
アノテーション付きインターフェイスにより実装とアノテーションは切り離せる。いわゆるアノテーション汚染を回避するためのテクニックの1つとして覚えておきたい。
public interface hoge{ @GET @PATH("{id}") @Consumes("application/xml") @Produces("text/plain") public String getData(); }
■ステートレスな通信
RESTがいう「ステートレス」とは状態がないという意味ではない。「一時的なデータはセッションとしてサーバ側で保持しない」という意味である。データはクライアントが持ち、必要に応じてサーバに送信するように設計する。
セッションをサーバで管理しなければ、クラスタ環境でのセッション同期やレプリケーションなどの困難を避けることができる。サーバマシンの拡張性が容易になるだろう。
入力項目が多岐にわたり、複数のページ遷移が必要なアプリケーションもある。しかし、Ajaxを使えば、1つの入力フォームで済ませることはできる。あるいは、Web Storageという選択肢もある。RESTfulなシステムは、ページ遷移の考え方そのものを変えるのだ。
当然、ユーザー認証のようなデータは、セッション側に持たせるべきであるという意見もあるだろう。しかし、OAuthのような仕組みを導入すれば、アプリケーションとユーザー認証は別機能として切り離せる。どのページを表示して、どのリソースの操作を許可するかは、機能を提供するアプリケーションで意識する必要はない。認証・認可をつかさどるアプリケーションに任せるのだ。
■HATEOAS
最後に、HATEOAS(Hypermedia as the Engine of Application State)という少し変わったキーワードが出てきた。本書では、「アプリケーション状態エンジンとしてのハイパーメディア」と訳されている。これでもまだ分かりにくい。詳細に踏みこもう。
Webページで画面が遷移するということは「状態が変化する」ということでもある。HATEOASでは、あるリソースの中には次の状態に進む方法を明示すべきなのだとしている。遷移すべきURIは取得したリソースの中にある。それは、何を意味するのだろうか。
ほとんどのWebページには、次に遷移するためのリンクが埋め込まれている。
/restsample?start=1&size=5
というURIがあるとしよう。このURIは、あるアイテムを5つ表示するというリソースを表現している。次のページを表示する、
/restsample?start=6&size=5
というURIが、どのページに記述されているかを考えれば分かりやすいだろう。
ほかにも、ショッピングサイトで注文のキャンセル処理を考えるなら、
/order/{id}/cancel /order/{id}?cancel=true
のようなURIを、注文状態を表示するページなどに用意する。商品の発送が完了すればキャンセル不可となり、キャンセル処理を行うURIは表示されなくなる。許可されたリソースだけをナビゲート可能にすることによって、許可されない処理へ進む状態遷移エラーを軽減するのだ。
同時に、「注文をキャンセルする処理」というリソースはDELETEされる。アドレスを直叩きしてアクセスしても404 Not Foundが待っているだけだ。レスポンスコードに注目するのもRESTfulの特徴の1つである。
JAX-RSでHATEOASをサポートする機能はUriBuilderとUriInfoぐらいしかない。RESTfulとは、実装を縛るものではく、設計の指針である。HATEOASもまた、アーキテクチャスタイルという考え方であり、強制されるものではない。しかし、リンクとフォームによってWebがどのように拡大してきたかを考えれば、HATEOASを適用するメリットは存分にあるだろう。
■RESTを知る
HTTPはリクエストとレスポンスからなる分散協調システムという側面を持つ。RESTを深く知ると、HTTPはブラウザにHTMLドキュメントを表示させるためだけのプロトコルではないことに気付くだろう。
Strutsに代表されるフレームワークは、1つのリクエストから1つのレスポンスを生成してHTMLドキュメントを表示する。ページ単位で制御するフレームワークは、ページの世界に縛られるのかもしれない。HTTPが生まれて20年近くの月日が流れた。ブラウザでHTMLを表示するときに無意識にページという概念にとらわれてはいないだろうか。1つのリクエスト・1つのレスポンスで1枚のWebページが表示されるという考え方から抜け出せないでいるとRESTfulの本質は理解できない。
RESTfulという手法は、制約でありながらHTTPを自由にする。RESTは新しいWebの可能性を、わたしたちに見せてくれるだろう。
(『フリーなスキル』コラムニスト はがねのつるぎ)