Web アプリケーションに HAL を適用する

HAL (Hypertext Application Language) とは Web API でやり取りされるリソースを、ハイパーメディアとしても扱えるようにするための仕様です。 ハイパーメディアとは HTML のアンカータグをイメージするとわかりやすいですが、ドキュメントといったメディアがリンクを介して繋がっている状態のことを指します。 例えば通常の Web API で取得できるリソース同士は特別何かで繋がっているわけではなく、お互いのリソースがある意味孤立した状態にありますが、そこにリンクを含めてあげることでお互いのリソース間を行き来することが出来るようになります。

HTML と HAL をハイパーメディアの観点で言うと、このような感じになるでしょうか。

  • HTML: 人間が操作するためのハイパーメディア
  • HAL: 機械が操作するためのハイパーメディア

具体的にリソースを HAL + JSON で表すとこんな感じになります。

{
    "userId": 10,
    "lastOrderDate": "Mon, 07 Jul 2014 18:26:21 GMT",
    "_links": {
        "self": { "href": "/users/10/orders" },
        "next": { "href": "/users/10/orders?page=2" }
    },
    "_embedded": {
        "orders": [{
            "orderId": 12,
            "orderDate": "Mon, 01 Jul 2014 18:26:21 GMT",
            "_links": {
                "self": { "href": "/users/10/orders/12" }
            }
        }, {
            "orderId": 11,
            "orderDate": "Mon, 01 Jul 2014 18:26:21 GMT",
            "_links": {
                "self": { "href": "/users/10/orders/11" }
            }
        }]
    }
}

userIdlastOrderDate といったリソースの情報と共に、それに付随したリンクが _links というプロパティに含まれています。 このリソースを使用するクライアントはそのリンクを「辿る」ことにより、別のリソースの操作を行おうという試みです。

こういったハイパーメディアを Web API に利用する考えは HATEOASREST Level3 など、昨今注目を集めているように思えます。

ブラウザと Web API の相性の悪さ

ここで少しブラウザと Web API*1 の関係について考えてみます。 ブラウザは人間が操作するものであり、HTML 上のリンクをクリックして別のドキュメントに遷移したりします。 しかし昨今の Web アプリはそういった画面遷移の他にも、JavaScript で Web API 用の URL を組み立て、Ajax を使って呼び出したりすることが多くなっています。 しかしそういった Ajax を使った Web API の呼び出しは、それまで HTML で表せていたハイパーメディアとしての領域をはみ出してしまいます。 リソース同士の繋がりを HTML とは別のもの(例えばクライアントサイドで定義したURLなど)を利用して表現しなければならないからです。 このように Web API が絡んでくると、クライアントサイドは既存のハイパーメディアの上にただ単純に乗っかっていれば済む話ではなくなるため、ブラウザとの相性が悪いと個人的には思っています。

ブラウザで HAL を使う

そこで、前述した HAL をブラウザでも使いたい!というのが本エントリーの主旨です。 具体的に言うと、HTML の他に HAL 形式のリソースを同時にブラウザに渡し、JavaScript ではその HAL 形式のリソースを参照します。 同じリソースを表しているのに、HTML と HAL という2つの表現として渡されてしまうという欠点はありますが、クライアントサイドは HAL に書かれているものだけを使えばいいので、サーバサイドとクライアントサイドが疎結合アーキテクチャになります。

例えば以下の様な HTML があるとします(HAL で表したリソースを type="application/hal+json" の script タグに埋め込んでおきます)。

<html>
<head>
    <script type="application/hal+json" id="hal-resource">
    {
        "_links": {
            "self": { "href": "http://example.com/users/10/orders" },
            "next": { "href": "http://example.com/users/10/orders?page=2" }
        },
        "_embedded": [{
            "id": "order-1",
            "_links": {
                "self": { "href": "http://example.com/users/10/orders/1" }
            }
        }, {
            "id": "order-2",
            "_links": {
                "self": { "href": "http://example.com/users/10/orders/2" }
            }
        }]
    }
    </script>
</head>
<body>
    <ol>
        <li id="order-1">Order 1</li>
        <li id="order-2">Order 2</li>
    </ol>
</body>
</html>

ここで次のページを Ajax で取得したいといった時、普通のやり方だと JS で URL を組み立ててそれを使ったりするでしょう。

var userId = 10;
var nextPage = 2;
var url = "/users/" + userId + "/orders?page=" + nextPage;
$.ajax({
    type: "GET",
    url: url,
    success: function (...) { ... },
    error: function (...) { ... },
});

一方、HAL を使うと、JS 側では渡されたリソースのリンクを辿るだけで次のページを取得できます。

JavaScript で HAL を扱うには halfred(https://github.com/basti1302/halfred) というライブラリがおすすめです。

var hal = $("#hal-resource").html();
var resource = halfred.parse(JSON.parse(hal));
var url = resource.link("next").href;

$.ajax({
    type: "GET",
    url: url,
    success: function (...) { ... },
    error: function (...) { ... },
});

このように単純にブラウザを HAL のクライアントとみなすことで、Web API との親和性も高くなります。

さいごに

HAL を Web アプリケーションに使用した場合の一番わかりやすい利点は、エンドポイントの管理がサーバサイドで完結し疎結合アーキテクチャになる点です。 もちろん HAL なんて使わなくても同様のことは簡単に実現できますが、標準に乗っかることでブラウザ以外の別のクライアントでも同じやり方を採用できたりするので、色々ハッピーかなと思ったりする年の瀬でした。

参考

*1:ここでは WebAPI を「HTTP プロトコルを利用してリソースを操作するためのコンピュータ用のインターフェース」として考えています。