見出し画像

Roy FieldingのRESTを見てみよう

HAL (Hypertext Application Language) によるRoy FieldingのRESTの実装例を見つけた!

Roy FieldingのREST

というといろんな人からRESTってRoy Fieldingでしょ?何いってるの?と言われるのですが、我々のよく知るRESTはRoy FieldingのRESTとは違うんです。
我々のよく知るRESTの実装へのRoy Fieldingによる反論記事はこちら。

違いのわかりやすいところを引用しておくと

allow servers to instruct clients on how to construct appropriate URIs, such as is done in HTML forms and URI templates, by defining those instructions within media types and link relations.

(HTMLのフォームとURIテンプレートなどを使って行われているように、メディアタイプやリンクを使って定義することで、サーバーはクライアントに適切なURIを構築できるようにする)

REST APIs must be hypertext-driven, Roy Fieldingブログ

A REST API should be entered with no prior knowledge beyond the initial URI (bookmark) and set of standardized media types that are appropriate for the intended audience

(REST APIは初期URIと、対象とするオーディエンスに適した標準のメディアタイプ以外の事前知識を必要とするものではあってはならない)

REST APIs must be hypertext-driven, Roy Fieldingブログ

この2つをみるとわかるように、REST APIの使い方をデベロッパーポータルで調べないと最初のAPIのリクエストを送れないような現在我々がよく知るREST APIはRoy Fieldingが求めるRESTではないんです。

最初に実行すべき初期URIが決まっており、ここを叩けば自動的にAPIの使い方がわかるような作りをしているべきで、ではどこを見ればわかるようになっているべきかというとメディアタイプ (Content-type) とリンクなんです。

実装例を見てみよう!

ではどのくらい違うか実装例を見てみましょう。

Github REST API

APIのドキュメントはこちら。

まず着目したいのはメディアタイプ。Roy FieldingのRESTの場合、メディアタイプがただのJSONやXMLではありません。以下のように拡張されたJSONを使っています。

application/vnd.github+json

https://docs.github.com/en/rest/overview/media-types

GitHubの場合はこれをベースとしてバージョンの指定ができたりと色々な機能をカスタムメディアタイプを指定させることで実現しています。
ただしGitHubの場合はただのJSONでも使えるように、application/jsonをサポートしています。

レスポンスに含まれたURLに着目しましょう。/repos の解説ページから引用します。パッとみた感じでも、キーの接尾語に "_url" と書いてあり、値に何らかのURLが入っている行がたくさんあります。調べるとこのURLは、Github REST APIの別のオブジェクトに関連づいたURLであることがわかると思います。つまり「これこれのデータが欲しければこっちのURLを叩いてくれ」という指示になっています。

[
  {
    "id": 1296269,
    "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5",
    "name": "Hello-World",
    "full_name": "octocat/Hello-World",
    "owner": {
      "login": "octocat",
      "id": 1,
      "node_id": "MDQ6VXNlcjE=",
      "avatar_url": "https://github.com/images/error/octocat_happy.gif",
      "gravatar_id": "",
      "url": "https://api.github.com/users/octocat",
      "html_url": "https://github.com/octocat",
      "followers_url": "https://api.github.com/users/octocat/followers",
      "following_url": "https://api.github.com/users/octocat/following{/other_user}",
      "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
      "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
      "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
      "organizations_url": "https://api.github.com/users/octocat/orgs",
      "repos_url": "https://api.github.com/users/octocat/repos",
      "events_url": "https://api.github.com/users/octocat/events{/privacy}",
      "received_events_url": "https://api.github.com/users/octocat/received_events",
      "type": "User",
      "site_admin": false
    },
    "private": false,
    "html_url": "https://github.com/octocat/Hello-World",
    "description": "This your first repo!",
    "fork": false,
    "url": "https://api.github.com/repos/octocat/Hello-World",
    "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}",
    "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}",
    "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}",
    "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}",
    "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}",
    "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}",
    "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}",
    "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}",
    "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}",
    "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors",
    "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments",
    "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads",
    "events_url": "https://api.github.com/repos/octocat/Hello-World/events",
    "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks",
    "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}",
    "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}",
    "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}",
    "git_url": "git:github.com/octocat/Hello-World.git",
    "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}",
    "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}",
    "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}",
    "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}",
    "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}",
    "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages",
    "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges",
    "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}",
    "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}",
    "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}",
    "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}",
    "ssh_url": "git@github.com:octocat/Hello-World.git",
    "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers",
    "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}",
    "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers",
    "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription",
    "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags",
    "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams",
    "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}",
    "clone_url": "https://github.com/octocat/Hello-World.git",
    "mirror_url": "git:git.example.com/octocat/Hello-World",
    "hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks",
    "svn_url": "https://svn.github.com/octocat/Hello-World",
    "homepage": "https://github.com",
    "language": null,
    "forks_count": 9,
    "stargazers_count": 80,
    "watchers_count": 80,
    "size": 108,
    "default_branch": "master",
    "open_issues_count": 0,
    "is_template": false,
    "topics": [
      "octocat",
      "atom",
      "electron",
      "api"
    ],
    "has_issues": true,
    "has_projects": true,
    "has_wiki": true,
    "has_pages": false,
    "has_downloads": true,
    "archived": false,
    "disabled": false,
    "visibility": "public",
    "pushed_at": "2011-01-26T19:06:43Z",
    "created_at": "2011-01-26T19:01:12Z",
    "updated_at": "2011-01-26T19:14:43Z",
    "permissions": {
      "admin": false,
      "push": false,
      "pull": true
    },
    "template_repository": null
  }
]

ページネーション (データをリスト取得した時のページ指定) の実装で、レスポンスヘッダーにリンクが含まれているのも面白いです。

Link: <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=15>; rel="next",
  <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=34>; rel="last",
  <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=1>; rel="first",
  <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=13>; rel="prev"


HALとHAL実装例: Hund API

HALはメディアタイプで、Roy FieldingのRESTの実装で実装例が多いのがHALと聞いたことがあります。
ヘッダーに指定する際には以下のように記載します。同じようにJSONを拡張したものです。

application/hal+json

https://en.wikipedia.org/wiki/Hypertext_Application_Language

WikipediaのBodyのサンプルを見ていただくと、GitHubのフォーマットとは異なるものの、リンクが指定できることがわかります。以下は本の情報を取得した時のレスポンスサンプルだと思いますが、著者情報の取得方法がメタデータとしてリンクの形式で格納されています。アンダースコアで始まるキー以下の情報がメタデータで、リンクは 接頭語なしの "_links" 以下にあります。

{
  "_links": {
    "self": {
      "href": "http://example.com/api/book/hal-cookbook"
    }
  },
  "_embedded": {
    "author": {
      "_links": {
        "self": {
          "href": "http://example.com/api/author/shahadat"
        }
      },
      "id": "shahadat",
      "name": "Shahadat Hossain Khan",
      "homepage": "http://author-example.com"
    }
  },
  "id": "hal-cookbook",
  "name": "HAL Cookbook"
}

さて実装例のHund API。[Hypermedia (HAL) Support] のところでサンプルを参照できます。

実装できるようになる必要はないけど、見分けられる必要はある

実装例をわざわざ探さなければいけないほど見つからないRoy FieldingのRESTですが、知識としては習得しておくと便利。

なぜかというと本やオンラインの記事などで勉強する時に、「いまからRoy FieldingのRESTの話をします」「今からよくあるRESTの話をします」という前置きなしに、いきなり「REST」「RESTful」と始まるので、文脈で見分けるしかないんです。

例えば以下の本はRoy FieldingのRESTの解説本です。いい本です。Roy Fieldingが目指したものがとてもよくわかります。

よく紹介する本の中では、この本に出てくる実装サンプルはよくみるとRoy FieldingのRESTであり、4章アーキテクチャの中に出てくるRESTの解説はRoy FieldingのRESTを前提としています。
ほとんどはそのまま我々がよく知るRESTを前提としてそのまま読めるのですが、カスタムメディアタイプを複数作ることの是非の議論は当然我々のよく知るRESTでは当てはまらないものです。

といったように、自分が何らかのお勉強をしようとして手に取った本が、本当に自分の勉強したい対象かどうか、吸収しようとしている知識は本当に自分が目的とするRESTかを判別しながら本を読む必要があります。

RESTのお勉強を本や何らかのメディアでされる方は、まずは見分け方を身につけておくと良いでしょう。


この記事が気に入ったらサポートをしてみませんか?