REST APIはHATEOASハイパーメディアリンクを実装すべきか?
TL;DR(要するに)
HATEOAS(Hypermedia as the Engine of Application State)は理論的には優雅ですが、実用上は複雑です。ほとんどのAPIは完全なHATEOASをスキップし、ページネーション、関連リソース、アクションのために選択的にハイパーメディアリンクを使用しています。Modern PetstoreAPIは、クライアントに完全にハイパーメディア駆動であることを強制することなく、実用的なハイパーメディアリンクを実装しています。
はじめに
あなたはREST APIの設計について読んでいます。そこでHATEOAS(Hypermedia as the Engine of Application State)に出くわしました。説明にはこうあります:「クライアントはURLをハードコードするのではなく、ハイパーメディアリンクを通じて全てのアクションを発見すべきです。」
あなたはこう考えます:「それは複雑そうだ。実際にこんなことをしている人がいるのだろうか?」
答えは「ほとんどそうではない」です。HATEOASは、RESTの制約の中で最もスキップされるものです。RESTを考案したロイ・フィールディングはそれが不可欠だと言いますが、ほとんどのAPI設計者は非実用的だと述べています。その結果、ほとんどの「REST」APIは、フィールディングの定義によると真のRESTfulではありません。
Modern PetstoreAPIは実用的なアプローチをとっています。つまり、価値をもたらす場所(ページネーション、関連リソース、アクション)でハイパーメディアリンクを使用しますが、クライアントに完全にハイパーメディア駆動であることを強制しません。
このガイドでは、HATEOASとは何か、なぜそれが議論の的になっているのか、そしてModern PetstoreAPIを参考に実用的なハイパーメディアリンクを実装する方法を学びます。
HATEOASとは何か?
HATEOASは、クライアントがAPIの機能性をドキュメントではなくハイパーメディアリンクを通じて発見すべきだとするRESTの制約です。
概念
URLをハードコードする代わりに:
// Client hardcodes URLs
const response = await fetch('https://petstoreapi.com/v1/pets/123');
const pet = await response.json();
// Client knows the URL structure
await fetch(`https://petstoreapi.com/v1/pets/${pet.id}/orders`);
クライアントはレスポンスのリンクをたどります:
// Client starts at root
const root = await fetch('https://petstoreapi.com/v1');
const rootData = await root.json();
// Client follows link to pets
const petsUrl = rootData._links.pets.href;
const pets = await fetch(petsUrl);
const petsData = await pets.json();
// Client follows link to specific pet
const petUrl = petsData._links.self.href;
const pet = await fetch(petUrl);
const petData = await pet.json();
// Client follows link to orders
const ordersUrl = petData._links.orders.href;
const orders = await fetch(ordersUrl);
理論
HATEOASを使用すると:
1. クライアントはURLをハードコードしない
URLはクライアントを壊すことなく変更できます。サーバーがURL構造を制御します。
2. クライアントは機能を発見する
リンクが存在すれば、そのアクションは利用可能です。存在しない場合、それは利用できない(またはこのユーザーには許可されていない)ことを意味します。
3. APIは自己文書化される
クライアントは、ウェブサイトを閲覧するように、リンクをたどってAPIを探索します。
例:完全なHATEOASレスポンス
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"name": "Fluffy",
"species": "CAT",
"status": "AVAILABLE",
"_links": {
"self": {
"href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24"
},
"update": {
"href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24",
"method": "PUT"
},
"delete": {
"href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24",
"method": "DELETE"
},
"orders": {
"href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/orders"
},
"adopt": {
"href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/adopt",
"method": "POST"
}
}
}
クライアントはURLパターンを知る必要はありません。リンクをたどるだけです。
HATEOASに関する議論
HATEOASは、理論と実践が乖離しているため、議論の的となっています。
HATEOASを支持する意見
1. 疎結合
クライアントはURL構造に依存しません。サーバーはクライアントを壊すことなくURLを変更できます。
2. 発見性
クライアントはドキュメントを読むことなくAPIを探索できます。
3. ステート駆動型アクション
リンクは利用可能なアクションを示します。ペットが引き取られた場合、「adopt」リンクは表示されなくなります。
4. 真のREST
ロイ・フィールディングは、HATEOASがRESTにとって不可欠であると述べています。それがなければ、RESTを行っていることにはなりません。
HATEOASに反対する意見
1. 複雑さ
クライアントはハイパーメディア解析ロジックを必要とします。シンプルなHTTPクライアントが複雑なステートマシンになってしまいます。
2. パフォーマンス
クライアントはURLを発見するために複数のリクエストを行います。直接URLにアクセスする方が高速です。
3. デバッグの難しさ
リンクをたどることでデバッグが難しくなります。単純にcurlでURLを叩くわけにはいかず、リンクの連鎖をたどる必要があります。
4. ツールが不十分
ほとんどのHTTPクライアント、テストツール、ドキュメントジェネレーターは、ハードコードされたURLを前提としています。
5. 誰もやっていない
GitHub、Stripe、Twilio、Twitterなど、主要なAPIは完全なHATEOASを使用していません。彼らがそれを必要としないなら、あなたも必要でしょうか?
現実
ほとんどのAPIは「REST」だと主張しますが、HATEOASをスキップしています。これらは実際には「HTTP API」または「RESTライクなAPI」です。真のREST(HATEOASを含む)は稀です。
実用的なハイパーメディアリンク
完全なHATEOASの代わりに、価値をもたらす場所でハイパーメディアリンクを使用しましょう。
1. ページネーションリンク
問題: クライアントがページネーションURLを構築する必要がある。
解決策: 次/前のリンクを提供する。
{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"totalPages": 10
},
"links": {
"self": "https://petstoreapi.com/v1/pets?page=2&limit=20",
"first": "https://petstoreapi.com/v1/pets?page=1&limit=20",
"prev": "https://petstoreapi.com/v1/pets?page=1&limit=20",
"next": "https://petstoreapi.com/v1/pets?page=3&limit=20",
"last": "https://petstoreapi.com/v1/pets?page=10&limit=20"
}
}
利点: クライアントはページネーションURLを構築せず、リンクをたどる。
2. 関連リソースリンク
問題: クライアントが関連リソースのURLパターンを知る必要がある。
解決策: 関連リソースへのリンクを提供する。
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"name": "Fluffy",
"_links": {
"self": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24",
"orders": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/orders",
"owner": "https://petstoreapi.com/v1/users/019b4127-54d5-76d9-b626-0d4c7bfce5b6"
}
}
利点: クライアントはドキュメントなしで関連リソースを発見する。
3. アクションリンク
問題: クライアントが利用可能なアクションを知る必要がある。
解決策: 利用可能なアクションのリンクを提供する。
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"status": "AVAILABLE",
"_links": {
"adopt": {
"href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/adopt",
"method": "POST"
}
}
}
もしペットがすでに引き取られている場合:
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"status": "ADOPTED",
"_links": {
// No "adopt" link - action not available
}
}
利点: リンクは状態に基づいて利用可能なアクションを示す。
4. カーソルベースのページネーション
問題: クライアントがカーソルURLを構築する必要がある。
解決策: 不透明な次/前のURLを提供する。
{
"data": [...],
"links": {
"next": "https://petstoreapi.com/v1/pets?cursor=eyJpZCI6IjAxOWI0MTMyIn0"
}
}
利点: クライアントはカーソルを解析せず、リンクをたどる。
Modern PetstoreAPIがハイパーメディアをどのように利用しているか
Modern PetstoreAPIは選択的なハイパーメディアリンクを使用しています。
ページネーションリンク
全てのコレクションエンドポイントにはページネーションリンクが含まれています:
GET /v1/pets?limit=20
{
"data": [...],
"pagination": {
"limit": 20,
"hasMore": true
},
"links": {
"self": "https://petstoreapi.com/v1/pets?limit=20",
"next": "https://petstoreapi.com/v1/pets?cursor=eyJpZCI6IjAxOWI0MTMyIn0&limit=20"
}
}
関連リソースリンク
リソースのレスポンスには関連リソースへのリンクが含まれています:
GET /v1/pets/019b4132-70aa-764f-b315-e2803d882a24
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"name": "Fluffy",
"ownerId": "019b4127-54d5-76d9-b626-0d4c7bfce5b6",
"_links": {
"self": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24",
"owner": "https://petstoreapi.com/v1/users/019b4127-54d5-76d9-b626-0d4c7bfce5b6",
"orders": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/orders"
}
}
完全なHATEOASはなし
Modern PetstoreAPIは、クライアントがハイパーメディア駆動であることを要求しません。クライアントは以下のことができます:
オプション1:リンクをたどる(ハイパーメディア駆動)
const pet = await fetch(petUrl);
const ownerUrl = pet._links.owner.href;
const owner = await fetch(ownerUrl);
オプション2:URLを構築する(従来型)
const pet = await fetch(`https://petstoreapi.com/v1/pets/${petId}`);
const owner = await fetch(`https://petstoreapi.com/v1/users/${pet.ownerId}`);
どちらのアプローチも機能します。リンクは利便性のために提供されており、強制ではありません。
Apidogを使ったハイパーメディアAPIのテスト
Apidogは、ハイパーメディアリンクをテストし、リンクの正確性を検証するのに役立ちます。
リンクの存在をテストする
レスポンスに期待されるリンクが含まれていることを確認する:
// Apidog test script
pm.test("レスポンスにページネーションリンクが含まれていることを確認", () => {
const links = pm.response.json().links;
pm.expect(links).to.have.property('self');
pm.expect(links).to.have.property('next');
});
リンクの有効性をテストする
リンクをたどり、機能することを確認する:
// Apidog test script
const nextUrl = pm.response.json().links.next;
pm.sendRequest(nextUrl, (err, response) => {
pm.test("次のリンクが200を返すか確認", () => {
pm.expect(response.code).to.equal(200);
});
});
リンク形式をテストする
リンクが期待される形式に従っていることを確認する:
pm.test("リンクが絶対URLであることを確認", () => {
const links = pm.response.json().links;
Object.values(links).forEach(link => {
pm.expect(link).to.match(/^https:\/\//);
});
});
HATEOASを使うべき時
価値をもたらすときにハイパーメディアリンクを使用しましょう。そうでないときはスキップしましょう。
ハイパーメディアリンクを使用すべきケース:
1. ページネーション - クライアントがページネーションURLを構築すべきではない
2. 関連リソース - 便利なナビゲーション
3. 状態依存型アクション - リソースの状態に基づいて利用可能なアクションを表示する
4. 複雑なワークフロー - クライアントを多段階プロセスに誘導する
HATEOASをスキップすべきケース:
1. シンプルなCRUD API - クライアントがURLを簡単に構築できる
2. 内部API - チーム間でURL変更を調整できる
3. パフォーマンスが重要なAPI - 余分なリンクがレスポンスサイズを増やす
4. モバイルAPI - 帯域幅が重要であり、リンクはオーバーヘッドを増やす
結論
HATEOASは理論的には優雅ですが、実用上は複雑です。ほとんどのAPIは完全なHATEOASをスキップし、価値をもたらす場所で選択的にハイパーメディアリンクを使用します。
Modern PetstoreAPIは、ページネーションリンク、関連リソースリンク、アクションリンクといった実用的なハイパーメディアを、クライアントに完全にハイパーメディア駆動であることを強制することなく示しています。
Apidogを使用して、ハイパーメディアリンクをテストし、リンクの正確性を検証し、APIが有用なナビゲーションを提供していることを確認してください。
主なポイント:
- 完全なHATEOASは稀で複雑である
- 選択的なハイパーメディアリンクは、複雑さを伴わずに価値を提供する
- ページネーションリンクは最も有用なハイパーメディア機能である
- クライアントにハイパーメディア駆動であることを強制しない
- リンクが正しく機能することを確認するためにテストする
実用的なハイパーメディア実装を見るために、Modern PetstoreAPIドキュメントを探索してください。
FAQ
REST APIにHATEOASは必須ですか?
ロイ・フィールディング(RESTの考案者)によると、はい。しかし実際には、いいえ。ほとんどの「REST」APIはHATEOASをスキップしており、技術的には「HTTP API」または「RESTライクなAPI」です。
HATEOASは何の略ですか?
Hypermedia as the Engine of Application State(アプリケーション状態のエンジンとしてのハイパーメディア)です。これは、クライアントがURLをハードコードするのではなく、ハイパーメディアリンクを通じてAPIの機能を発見することを意味します。
主要なAPIはHATEOASを使用していますか?
いいえ。GitHub、Stripe、Twilio、およびほとんどの主要なAPIは完全なHATEOASを使用していません。一部のハイパーメディアリンク(ページネーション、関連リソース)を含めることはありますが、クライアントがハイパーメディア駆動であることを要求しません。
HATEOASとハイパーメディアリンクの違いは何ですか?
HATEOASは、クライアントが完全にハイパーメディア駆動であることを要求する制約です。ハイパーメディアリンクは、レスポンス内の単なるリンクです。HATEOASを強制せずにリンクを含めることができます。
自分のAPIにHATEOASを実装すべきですか?
おそらく完全なHATEOASは実装すべきではありません。ページネーションや関連リソースのために選択的なハイパーメディアリンクを使用しましょう。特定の理由がない限り、クライアントにハイパーメディア駆動であることを強制しないでください。
HATEOAS APIはどのようにテストしますか?
Apidogを使用して、リンクの存在を確認し、リンクをたどり、リンクの正確性を検証します。リンクが期待されるレスポンスを返すことをテストしてください。
HALフォーマットとは何ですか?
HAL(Hypertext Application Language)は、ハイパーメディアリンクの標準フォーマットです。_linksと_embeddedフィールドを使用します。Modern PetstoreAPIはHALにインスパイアされたリンク形式を使用しています。
クライアントはハイパーメディアリンクを無視できますか?
はい。APIがリンクを提供しているが、クライアントがそれらを使用することを要求しない場合、クライアントはURLを直接構築できます。これはほとんどのAPIが採用している実用的なアプローチです。
