65
HYPERMEDIA: THE MISSING ELEMENT to Building Adaptable Web APIs in Rails Toru Kawamura @tkawa RubyKaigi 2014 RESTful Web APIs 読書会 #19 2014.10.09 ハイパーメディア: RailsでWeb APIをつくるには、これが足りない

Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

Embed Size (px)

DESCRIPTION

RubyKaigi 2014での発表に、当日カットしたスライドを加えて「RESTful Web APIs 読書会」で話したものです。 http://www.circleaf.com/events/155 オリジナルバージョン http://www.slideshare.net/tkawa1/rubykaigi2014-hypermedia-the-missing-element

Citation preview

Page 1: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

HYPERMEDIA: THE MISSING ELEMENT

to Building Adaptable Web APIs in Rails

Toru Kawamura @tkawa

!RubyKaigi 2014

RESTful Web APIs 読書会 #19 2014.10.09

ハイパーメディア: RailsでWeb APIをつくるには、これが足りない

Page 2: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

@tkawaToru Kawamura

• フリーランス Ruby/Rails プログラマ

• Technology Assistance Partner at SonicGarden Inc.

• REST厨 (RESTafarian) inspired by Yohei Yamamoto (@yohei)

• Sendagaya.rb 共同主催

• “RESTful Web APIs” 読書会主催

Page 3: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

Web API

Page 4: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

“Web”

http://www.opte.org/the-internet/

Page 5: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

http://pixabay.com/en/spider-web-net-grid-silk-drops-13516/

「クモの巣」

Page 6: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

https://www.flickr.com/photos/tamaki/260594564/

「クモの糸」

Page 7: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

• プライベート

• 内部から使われる

• SPAや専用のクライアントが使う

• だいたい予想できるコントロールできる

• パブリック

• 外部から使われる

• 汎用的な目的の クライアントが使う

• 予想しづらいコントロールしづらい

Page 8: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

http://www.slideshare.net/yohei/webapi-36871915– 「WebAPIのこれまでとこれから」by @yohei

Page 9: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

http://pixabay.com/en/spider-web-net-grid-silk-drops-13516/

今回は「クモの巣」メインの話です

Page 10: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

Change

Page 11: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

変化は避けられない !

Web APIは変化に適応しなければならない

Page 12: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

2種類の Change

バージョンが変わる バージョンが変わらない

互換性がない 互換性がある

クライアントを壊す クライアントを壊さない

Page 13: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

2種類の Change

バージョンが変わる バージョンが変わらない

互換性がない 互換性がある

クライアントを壊す クライアントを壊さない

壊す Change 壊さない Change

Page 14: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

壊すChangeはよくない

• ひどいユーザ体験を生む

• クライアント開発者にコードの書き直し・再デプロイを強いる

• もし     だったら…

Page 15: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

バージョンが変わる バージョンが変わらない

互換性がない 互換性がある

クライアントを壊す クライアントを壊さない

壊す Change 壊さない Change

なぜ起こる?

Page 16: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

人間が読める説明書から作られるクライアントがたくさんある

GET /v1/statuses?id=#{id} GET /v1/statuses?id=#{id}

Page 17: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

GET /v2/statuses/#{id} GET /v1/statuses?id=#{id}×コードの書き直しが必要

Page 18: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

機械が読める説明書から作られるクライアントもある

{ "apiVersion": "1.0.0", "basePath": "http://petstore.swagger.wordnik.com/api", "resourcePath": "/store", "produces": [ "application/json" ], "apis": [ { "path": "/store/order/{orderId}", "operations": [ { "method": "GET", "summary": "Find purchase order by ID", "notes": "For valid response try integer IDs with value <= 5. Anything above 5 or nonintegers will generate API errors", "type": "Order", "nickname": "getOrderById", "authorizations": {}, "parameters": [

GET /v1/statuses?id=#{id} GET /v1/statuses?id=#{id}

Page 19: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

{ "apiVersion": "2.0.0", "basePath": "http://petstore.swagger.wordnik.com/api", "resourcePath": "/store", "produces": [ "application/json" ], "apis": [ { "path": "/store/order/{orderId}", "operations": [ { "method": "GET", "summary": "Find purchase order by ID", "notes": "For valid response try integer IDs with value <= 5. Anything above 5 or nonintegers will generate API errors", "type": "Order", "nickname": "getOrderById", "authorizations": {}, "parameters": [

GET /v2/statuses/#{id} GET /v1/statuses?id=#{id}×コードの再生成が必要

Page 20: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

{ uber: { version: "1.0", data: [{ url: "http://www.ishuran.dev/notes/1", name: "Article", data: [ { name: "articleBody", value: "First note's text" }, { name: "datePublished", value: null }, { name: "dateCreated", value: "2014-09-11T12:00:31+09:00" }, { name: "dateModified", value: "2014-09-11T12:00:31+09:00" }, { name: "isPartOf", rel: "collection", url: "/notes"

Page 21: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

{ uber: { version: "1.0", data: [{ url: "http://www.ishuran.dev/notes/1", name: "Article", data: [ { name: "articleBody", value: "First note's text" }, { name: "datePublished", value: null }, { name: "dateCreated", value: "2014-09-11T12:00:31+09:00" }, { name: "dateModified", value: "2014-09-11T12:00:31+09:00" }, { name: "isPartOf", rel: "collection", url: "/notes"

• APIの変更がクライアントに反映されればよい

• APIの説明を分割して各レスポンスに埋め込むのが良い方法

• APIについての多大な仮定は密結合を生む

「密結合」が原因

Page 22: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

バージョンが変わる バージョンが変わらない

互換性がない 互換性がある

クライアントを壊す クライアントを壊さない

壊す Change 壊さない Change

密結合による 疎結合による

Page 23: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

例で見る疎結合: FizzBuzzaaS• by Stephen Mizell

http://fizzbuzzaas.herokuapp.com/http://smizell.com/weblog/2014/solving-fizzbuzz-with-hypermedia

• サーバは与えられた100までの数のFizzBuzzを計算できる

• サーバは次のFizzBuzzが何になるか知っている

• クライアントは1から最後まで順番にすべてのFizzBuzzが欲しいhttp://sef.kloninger.com/posts/

201205fizzbuzz-for-managers.html

Page 24: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

密結合なクライアント

• すべてのURLとパラメータがハードコードされている

• カウントアップのような、サーバロジックと同じことをクライアントでも行っている

"/v2/fizzbuzz/#{i}"

(1..1000)

(1..100).each do |i| answer = HTTP.get("/v1/fizzbuzz?number=#{i}") puts answer end

Page 25: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

疎結合なクライアント

• ハードコードされたURLなし

• URLや条件を変えてもクライアントは壊れない

root = HTTP.get_root answer = root.link('first').follow puts answer while answer.link('next').present? answer = answer.link('next').follow puts answer end next リンクが重要

Page 26: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

• 実際にリンクをたどる代わりに、

埋め込みリソースを使うことで

リクエスト数を減らすことが可能

http://techlife.cookpad.com/entry/2014/09/08/093000

Page 27: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

「APIコール」のメタファーは危険

• クライアントがあらかじめURLとパラメータを用意してAPIを呼ぶ、というRPCのようなパラダイムから離れよう

• クライアントが次にすることは、レスポンスの中のリンクから選ぶこと

== ハイパーメディア

Page 28: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

これは想像上のものではなくすでにHTMLにある

Page 29: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

HTMLのWeb• WebアプリやWebサイトはずっと変わり続けているが、ブラウザは壊れていない

• HTMLのWebでは、なぜブラウザは壊れない?HTMLのリンク

http://www.youtypeitwepostit.com/messages

Page 30: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

HTMLの中のデータ

• HTMLが表現する意味は

「ヒューマンリーダブルなドキュメント」

• 段落、リスト、表、セクション、…

• これが人間にゆるく伝わればよい

Page 31: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

HTMLの中のワークフロー• Webアプリはワークフロー(の提案)を含む

• ワークフローは一連の画面遷移で表現される

— リンク と フォーム

”RESTful Web APIs” p.11 Figure 1-7

Page 32: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

ハイパーメディアはワークフローを示す

• 各画面は次に何ができるかの「メニュー」としてリンクやフォームを含む

• ブラウザはその「メニュー」の中から選んで次へ進む

• これがハイパーメディア で、FizzBuzzaaSもやっていたこと

3

4

Page 33: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

もしHTMLにリンクがなかったら?

• 各WebアプリごとにURLやパラメータをハードコードした専用クライアントを作りたくなる

• クライアントとサーバのコードが密結合して、変更できないWebアプリになってしまう

• これが今のWeb APIがやっていること

メッセージWebアプリ利用の手順書

1. アドレスバーに /messages と入力して GET

2. アドレスは /messages のまま、 title と body のパラメータに文字列をセットして POST

3. message-id を受け取って、アドレスバーに /messages/{message-id} と入力して GET

代わりにワークフロー手順書がある?

Page 34: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

クローラにはもう1つヒントが

• クローラはリンクをたどる(submitできるフォームもある)

• クローラはHTMLドキュメントの中にあるデータとその「意味」を理解している

• どうやって?https://support.google.com/webmasters/answer/99170

Page 35: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

Microdata

Page 36: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

• 構造化データをHTMLドキュメントに埋め込むしくみ

• データを変えることなくドキュメントの構造を変えられる

• データをURLに結びつけることで、大まかな「データの意味」も表す(これもリンクの一種)

Microdata<div itemscope itemtype="http://schema.org/Person"> My name is <span itemprop="name">Bob Smith</span> but people call me <span itemprop="nickname">Smithy</span>. Here is my home page: <a href="http://www.example.com" itemprop="url">www.example.com</a> I live in Albuquerque, NM and work as an <span itemprop="title">engineer</span> at <span itemprop="affiliation">ACME Corp</span>. </div>

Page 37: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

• 構造化データをHTMLドキュメントに埋め込むしくみ

• データを変えることなくドキュメントの構造を変えられる

• データをURLに結びつけることで、大まかな「データの意味」も表す(これもリンクの一種)

Microdata<div itemscope itemtype="http://schema.org/Person"> My name is <span itemprop="name">Bob Smith</span> but people call me <span itemprop="nickname">Smithy</span>. Here is my home page: <a href="http://www.example.com" itemprop="url">www.example.com</a> I live in Albuquerque, NM and work as an <span itemprop="title">engineer</span> at <span itemprop="affiliation">ACME Corp</span>. </div>

schema.org 標準語彙(ボキャブラリー)by Bing, Google, Yahoo! and Yandex

http://getschema.org/index.php/Main_Page

Page 38: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

schema.org

• フィールド名・カラム名は変わるが、標準名は変わらない

• 変えたときに壊れないように、変わらない基盤としての標準と結びつける

• schema.org はコーポレートスタンダードの1つといえる

Bing, Google, Yahoo! and Yandex

• 現在700種類以上のデータタイプを定義

Page 39: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

変化に適応するために必要なもの

• APIには構造化データが必要

• 柔軟なワークフローにはリンクとフォームが必要

data link form

HTML - ✓ ✓

HTML+Microdata ✓✓ ✓ ✓

✓✓: 「データの意味」を含む

Page 40: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

HTMLでWeb APIを作ることもできる

• “Microdata DOM API” でHTMLからデータを抽出できるhttp://www.w3.org/TR/microdata/#using-the-microdata-dom-api

• JavaScriptの実装: https://github.com/termi/Microdata-JS

• MicrodataからJSONに変換する仕様もいくつかある

• HTMLはリンクとフォームを持っているのが大きなアドバンテージ

var user = document.getItems('http://schema.org/Person')[0]; var name = user.properties['name'][0].itemValue; alert('Hello ' + name + '!');

Page 41: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

でもきっとJSON Web APIが欲しいはず

• リンクとフォームを埋めればいい

(できればデータの意味も)

data link form

HTML+Microdata ✓✓ ✓ ✓

JSON ✓ - -

✓✓: 「データの意味」を含む

Page 42: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

JSONでリンク・フォームを表す• リンクやフォームを表現できるJSONベースのフォーマットを使う

• 他にもSiren, Collection+JSON, Mason, Verbose など…

data link form

JSON ✓ - -

JSON +Link header ✓ ✓ -

HAL ✓ ✓ -

JSON-LD ✓✓ ✓ -

JSON-LD+Hydra ✓✓ ✓ ✓

UBER ✓ ✓ ✓✓✓: 「データの意味」を含む

Page 43: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

JSONでデータの意味を表す:「プロファイル」

• ALPSプロファイル

• MicrodataをHTML以外のどんなフォーマットにも適用可能にする

• JSON-LDコンテキスト

• ドキュメントとコンテキストの両方を同じ1つのフォーマットで扱える

Page 44: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

A Solution

Page 45: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

Hypermicrodata gem

• サーバサイドでHTMLをJSONに変換

• Microdataだけではなく

リンクとフォームもHTMLから抽出

• ベースのALPSプロファイルを用意して、データの意味も表しやすい形でJSONベースのフォーマットを生成

https://github.com/tkawa/hypermicrodata

Page 46: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

Example in HAL (application/hal+json){ "image": "/assets/bob.png", "name": "Bob Smith", "isPartOf": "/people", "_links": { "self": { "href": "http://www.example.com/people/1" }, "type": { "href": "http://schema.org/Person" }, "collection": { "href": "/people" }, "profile": { "href": "/assets/person.alps" } } }

class PeopleController < ApplicationController before_action :set_message, only: %i(show edit update destroy) include Hypermicrodata::Rails::HtmlBasedJsonRenderer ... end

.person{itemscope: true, itemtype: 'http://schema.org/Person', itemid: person_url(@person), data: {main_item: true}} .media .media-image.pull-left = image_tag @person.picture_path, alt: '', itemprop: 'image' .media-body %h1.media-heading %span{itemprop: 'name'}= @person.name = link_to 'collection', people_path, rel: 'collection'

Page 47: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

Hypermicrodata gemを使ったRailsによる設計手順

1. リソース設計

2. 状態遷移図を描く

3. データの名前を対応するURLに結びつける

4. HTMLテンプレート(Haml, Slimなど)を書いて、Microdata

でマークアップする

(その後、必要ならschema.org定義にないプロファイルと説明を書く)

Page 48: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

Example: Note API

Page 49: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

1. リソース設計

カラム名 説明 タイプtext noteの内容のテキスト text

published_at noteの公開時間 datetime

(id, created_at, updated_at) (自動生成)

$ rails g model Note text:text published_at:datetime

model: Notecontroller : NotesController

routing: resources :notes

Page 50: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

2. 状態遷移図を描く

Collection Memberitem

collection

create*†

update*, delete*

* 安全でない † 冪等でない

Railsの Collection & Member リソースパターンから始める (API ver.)

Page 51: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

Collection of Note

Note (text, published_at,

created_at, updated_at, id)

item

collection

create*†

update*, delete*,publish*

next, prev

Home

notes home

* 安全でない † 冪等でない

Page 52: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

3. データの名前を対応するURLに結びつける

Collection of Note http://schema.org/ItemList

Note http://schema.org/Article

text http://schema.org/articleBody

published_at http://schema.org/datePublished

created_at http://schema.org/dateCreated

updated_at http://schema.org/dateModified

id (各noteは個別のURLを持つので不要)

Home http://schema.org/SiteNavigationElement

Page 53: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

item IANA ‘item’ & http://schema.org/hasPart

collection IANA ‘collection’ & http://schema.org/isPartOf

notes -

create Activity Streams ‘create'

update Activity Streams ‘update’

delete Activity Streams ‘delete’

publish Activity Streams ‘post’

IANA registered Link Relation: http://www.iana.org/assignments/link-relations/Activity Streams Verbs: http://activitystrea.ms/registry/verbs/

Page 54: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

4. HTMLテンプレートとMicrodataを書く

/app/views/notes/index.html.haml

GET /notes HTTP/1.1 Host: www.example.com Accept: application/vnd.amundsen-uber+json

%div{itemscope: true, itemtype: 'http://schema.org/ItemList', itemid: notes_url, data: {main_item: true}} - @notes.each do |note| = link_to note.text.truncate(20), note, rel: 'item', itemprop: 'hasPart' = form_for Note.new do |f| = f.text_field :text = f.submit rel: 'create'

{ "uber": { "version": "1.0", "data": [{ "url": "http://www.example.com/notes", "name": "ItemList", "data": [ { "name": "hasPart", "rel": "item", "url": "/notes/1" }, { "name": "hasPart", "rel": "item", "url": "/notes/2" }, { "rel": "create", "url": "/notes", "action": "append", "model": "note%5Btext%5D={text}" }, { "rel": "profile", "url": "/assets/note.alps"} ] }] } }

Collection of Note

Link

Form

Page 55: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

%div{itemscope: true, itemtype: 'http://schema.org/Article', itemid: note_url(@note), data: {main_item: true}} %span{itemprop: 'articleBody'}= @note.text %span{itemprop: 'datePublished'}= @note.published_at %span{itemprop: 'dateCreated'}= @note.created_at %span{itemprop: 'dateModified'}= @note.updated_at = form_for @note, method: :put do |f| = f.text_field :text = f.submit rel: 'update' = button_to 'Destroy', @note, method: :delete, rel: 'delete' = button_to 'Publish', publish_note_path(@note), rel: 'publish' unless @note.published? = link_to 'Next note', note_path(@note.next), rel: 'next' if @note.next = link_to 'Prev note', note_path(@note.prev), rel: 'prev' if @note.prev = link_to 'Collection of Note', notes_path, rel: 'collection', itemprop: 'isPartOf'

/app/views/notes/show.html.haml

GET /notes/1 HTTP/1.1 Host: www.example.com Accept: application/vnd.amundsen-uber+json

Note

{ "uber": { "version": "1.0", "data": [{ "url": "http://www.example.com/notes/1", "name": "Article", "data": [ { "name": "articleBody", "value": "First note's text" }, { "name": "datePublished", "value": null }, { "name": "dateCreated", "value": "2014-09-11T12:00:31+09:00" }, { "name": "dateModified", "value": "2014-09-11T12:00:31+09:00" }, { "name": "isPartOf", "rel": "collection", "url": "/notes" }, { "rel": "update", "url": "/notes/1", "action": "replace", "model": "note%5Btext%5D={text}" }, { "rel": "delete", "url": "/notes/1", "action": "remove" }, { "rel": "publish", "url": "/notes/1/publish", "action": "append" }, { "rel": "next", "url": "/notes/2" }, { "rel": "profile", "url": "/assets/note.alps" } ] }] } }

Page 56: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

%div{itemscope: true, itemtype: 'http://schema.org/Article', itemid: note_url(@note), data: {main_item: true}} %span{itemprop: 'articleBody'}= @note.text %span{itemprop: 'datePublished'}= @note.published_at %span{itemprop: 'dateCreated'}= @note.created_at %span{itemprop: 'dateModified'}= @note.updated_at = form_for @note, method: :put do |f| = f.text_field :text = f.submit rel: 'update' = button_to 'Destroy', @note, method: :delete, rel: 'delete' = button_to 'Publish', publish_note_path(@note), rel: 'publish' unless @note.published? = link_to 'Next note', note_path(@note.next), rel: 'next' if @note.next = link_to 'Prev note', note_path(@note.prev), rel: 'prev' if @note.prev = link_to 'Collection of Note', notes_path, rel: 'collection', itemprop: 'isPartOf'

Note

{ "uber": { "version": "1.0", "data": [{ "url": "http://www.example.com/notes/1", "name": "Article", "data": [ { "name": "articleBody", "value": "First note's text" }, { "name": "datePublished", "value": null }, { "name": "dateCreated", "value": "2014-09-11T12:00:31+09:00" }, { "name": "dateModified", "value": "2014-09-11T12:00:31+09:00" }, { "name": "isPartOf", "rel": "collection", "url": "/notes" }, { "rel": "update", "url": "/notes/1", "action": "replace", "model": "note%5Btext%5D={text}" }, { "rel": "delete", "url": "/notes/1", "action": "remove" }, { "rel": "publish", "url": "/notes/1/publish", "action": "append" }, { "rel": "next", "url": "/notes/2" }, { "rel": "profile", "url": "/assets/note.alps" } ] }] } }

= button_to 'Publish', publish_note_path(@note), rel: 'publish' unless @note.published? = link_to 'Next note', note_path(@note.next), rel: 'next' if @note.next = link_to 'Prev note', note_path(@note.prev), rel: 'prev' if @note.prev

条件の表現

{ "rel": "publish", "url": "/notes/1/publish", "action": "append" }, { "rel": "next", "url": "/notes/2" },

publishできるが prevには行けない

Page 57: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

この設計手順の3つのメリット

Page 58: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

メリット 1: DRY• リンクとフォームをHTMLテンプレートに一度書けば、JSON生成時にも再利用できる

• Microdataマークアップもそのまま再利用できる

• Bonus: HTMLのMicrodataはSEO効果を上げる

(JSONにも可能性あり)

Page 59: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

メリット 2: リンクとフォームを意識できる

• JSON Web APIを作るときには、状態遷移の重要性を見落としやすい

• APIをHTMLのWebアプリのように表現することで、状態遷移に着目して適切にリンクとフォームを実装できる

Page 60: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

メリット 3: 制約

• 「HTMLドキュメントをMicrodataでマークアップして、それを一定のルールでフォーマットされたJSONに変換する」という制約

• この制約はよりよい設計のガイド

• “Constraints are liberating” 「制約は自由をもたらす」

Page 61: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

もしJSONだけを書くときは 注意すること

• リンク・フォームを意識するために:

• 状態遷移図を描きましょう

• APIを疎結合に保つために:

• model.to_json の代わりに Jbuilder/RABL のようなビューテンプレートやリプレゼンターを使いましょう

• リンクとフォームを持ったJSONベースのフォーマットを使いましょう

• さらに schema.org のような標準名を使うとベター

Page 62: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

– 「Webを支える技術」@yohei

“WebアプリとWeb APIを分けて考えない”

Page 63: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

結論: Web APIはHTML Webアプリと同じように設計しよう

• Web APIは特別なものではなく、ただ表現フォーマットが違うだけ

• 状態遷移図を描いて状態遷移を意識することで、リンクやフォームを忘れずにすむ

Page 64: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

最後に• 残念ながら、JSONフォーマット、クライアント実装やライブラリにはデファクトスタンダードがない

• RESTの制約・原則を意識するともっとうまくできる

• ハイパーメディアはRESTの最も重要な要素の1つで

変化に適応できるWeb APIへの重要なステップ

Page 65: Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails (増補日本語版)

よりよい、変化に適応できるWeb APIを作りましょう Thank you for your attention.

• L. Richardson & M. Amundsen “RESTful Web APIs” (O’Reilly)

• 山本陽平 “Webを支える技術” (技術評論社)

• Designing for Reuse: Creating APIs for the Future http://www.oscon.com/oscon2014/public/schedule/detail/34922

• API Design Workshop 配布資料 http://events.layer7tech.com/tokyo-wrk

• https://speakerdeck.com/zdne/robust-mobile-clients-v2

• http://www.slideshare.net/yohei/webapi-36871915

• http://smizell.com/weblog/2014/solving-fizzbuzz-with-hypermedia

• 山口 徹 “Web API デザインの鉄則” WEB+DB PRESS Vol.82

References