SlideShare a Scribd company logo
HYPERMEDIA: 
THE MISSING ELEMENT 
to Building Adaptable Web APIs in Rails 
ハイパーメディア: RailsでWeb APIをつくるには、これが足りない 
Toru Kawamura 
@tkawa 
! 
RubyKaigi 2014
@tkawa 
Toru Kawamura 
• Freelance Ruby/Rails programmer 
• Technology Assistance Partner at 
SonicGarden Inc. 
• RESTafarian 
inspired by Yohei Yamamoto (@yohei) 
• Co-organizer of Sendagaya.rb 
• Organizer of the 
reading group of 
“RESTful Web APIs”
Web API
“Web” 
http://www.opte.org/the-internet/
http://pixabay.com/en/spider-web-net-grid-silk-drops-13516/
https://www.flickr.com/photos/tamaki/260594564/
• Private 
• For internal use 
• For SPA or dedicated 
clients only 
• Almost expected, 
almost controllable 
• Public 
• For external use 
• For general-purpose 
clients 
• Less expected, 
less controllable
“Whether an API should be RESTful or not 
depends on the requirement” 
– 「WebAPIのこれまでとこれから」by @yohei 
http://www.slideshare.net/yohei/webapi-36871915
http://pixabay.com/en/spider-web-net-grid-silk-drops-13516/
Change
Change is inevitable 
! 
Web APIs must adapt to changes 
変化は避けられない 
Web APIは変化に適応しなければならない
Two types of Change 
With versioning Without versioning 
Incompatible Compatible 
Breaks clients Does not break clients 
Breaking Change Non-Breaking Change
Breaking Changes are Harmful 
壊す変更は有害 
• Terrible user experience 
ひどいユーザ体験 
• Forces client developers to rewrite/redeploy code 
クライアント開発者にコ��ドの書き直し・再デプロイを強いる 
• What if on …
Because of what? 
なぜ起こるの? 
With versioning Without versioning 
Incompatible Compatible 
Breaks clients Does not break clients 
Breaking Change Non-Breaking Change
Many clients are built from 
human-readable documentation 
人間が読める説明書から作られる 
クライアントがたくさんある 
GET /v1/statuses?id=#{id} GET /v1/statuses?id=#{id}
GET /v2/statuses/#{id} GET /v1/statuses?id=#{id} ×Need to rewrite code
Some clients are built from 
machine-readable documentation 
{ 
"apiVersion": "1.0.0", 
"basePath": "http:// 
petstore.swagger.wordnik.com/api", 
"resourcePath": "/store", 
"produces": [ 
"application/json" 
], 
"apis": [ 
{ 
"path": "/store/order/{orderId}", 
"operations": [ 
{ 
GET /v1/statuses?id=#{id} GET /v1/statuses?id=#{id} 
"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} ×Need to regenerate code 
{ 
"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": [
{ 
uber: { 
Because of Coupling 
version: "1.0", 
data: [{ 
url: "http://www.ishuran.dev/notes/1", 
name: "Article", 
data: [ 
• API { 
name: changes "articleBody", 
should be reflected in clients 
value: "First note's text" 
}, 
{ 
It is name: • value: }, 
good "datePublished", 
null 
to split up explanations of the API and 
{ 
embed name: "dateCreated", 
value: "2014-them 09-11T12:into 00:31+09:00" 
each API response 
}, 
{ 
name: "dateModified", 
value: "2014-09-11T12:00:31+09:00" 
• A }, 
{ 
lot of assumptions about the API make a tight 
name: "isPartOf", 
rel: "collection", 
coupling 
url: "/notes" 
密結合のせい 
APIの変更がクライアントに反映されるべき 
APIの説明を分割して各レスポンスに埋め込むのが良い 
APIについての多大な仮定は密結合を生む
With versioning Without versioning 
Incompatible Compatible 
Breaks clients Does not break clients 
Breaking Change Non-Breaking Change 
because of the Coupling because of the Decoupling
Decoupling in a example: 
FizzBuzzaaS 
• by Stephen Mizell 
例で見る疎結合 
http://fizzbuzzaas.herokuapp.com/ 
http://smizell.com/weblog/2014/solving-fizzbuzz-with-hypermedia 
• Server knows how to calculate 
FizzBuzz for given number (<= 100) 
サーバは100までの数のFizzBuzzを計算できる 
• Server knows what the next FizzBuzz 
will be 
サーバは次のFizzBuzzが何になるか知っている 
• Client wants all FizzBuzz from one to 
the last in order 
クライアントは1から最後まで順番にすべてのFizzBuzzが欲しい 
http://sef.kloninger.com/posts/ 
201205fizzbuzz-for-managers. 
html
Coupled client 
密結合なクライアント 
answer = HTTP.get("/v1/fizzbuzz?number=#{i}") 
puts answer 
end 
"/v2/fizzbuzz/#{i}" 
(1..1000) 
(1..100).each do |i| 
• Every URL and parameter is hardcoded 
• Duplicates the server logic such as counting 
up 
すべてのURLとパラメータがハードコードされている 
カウントアップのようなサーバロジックと同じことをやっている
Decoupled client 
疎結合なクライアント 
root = HTTP.get_root 
answer = root.link('first').follow 
puts answer 
while answer.link('next').present? 
answer = answer.link('next').follow 
puts answer 
end Link ‘next’ is the key 
• No hardcoded URLs 
• Client doesn’t break when changing 
URLs / the restriction 
ハードコードされたURLなし 
URLや条件を変えてもクライアントは壊れない
The “API Call” metaphor is 
dangerous 
「APIコール」のメタファーは危険 
• We need to move away from the paradigm where 
a client arranges a URL and parameters in advance 
and calls API (like RPC…) 
URLとパラメータを用意してAPIを呼ぶというRPCのようなパラダイムから離れよう 
• What a client does next should be to choose 
from links in the response 
== HYPERMEDIA 
クライアントが次にすることはリンクから選ぶこと 
これがハイパーメディア
This is not imaginary 
but already present in HTML 
これは想像上のものではなく、すでにHTMLにある
The HTML Web 
• Web apps and websites 
have been changing 
constantly without 
breaking browsers 
• Why don’t browsers 
break on the HTML Web? 
There are links 
in HTML 
WebアプリやWebサイトはずっと変わり続けているけど 
ブラウザは壊れていないのはなぜ? 
http://www.youtypeitwepostit.com/messages
Workflow in HTML 
• Web app includes a 
(suggested) workflow 
• Workflow is represented 
by a sequence of screen 
transitions 
— Links and Forms 
Webアプリはワークフローを含む 
ワークフローは一連の画面遷移で表現される 
それはリンクとフォーム 
”RESTful Web APIs” p.11 Figure 1-7
Hypermedia show the workflow 
ハイパーメディアはワークフローを示す 
• Each screen includes what a 
browser can do next 
through links and forms like a 
“menu” 
• A browser chooses from the 
“menu” to go to the next 
step 
• This is HYPERMEDIA and 
exactly what FizzBuzzaaS 
does 
3 
4 
各画面は次に何ができるかのリンクやフォームの 
「メニュー」を含み、ブラウザはその中から選ぶ 
これがハイパーメディア
One more hint in a Crawler 
クローラーにはもう1つヒントが 
• Crawlers follow links and can submit some forms 
• Crawlers understand the data in an HTML document 
and their “meaning” 
• How can they do that? 
クローラ��HTMLの中のデータと意味を理解している 
https://support.google.com/webmasters/answer/99170 どうやって?
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> 
• Mechanism that embeds structured data within an HTML 
document 
• Document structure can change without changing data 
• Connects data with a URL that roughly represents 
the “meaning of data” (this is also a kind of link) 
Microdataは構造化データをHTMLに埋め込むしくみ 
URLに結びつけることで大まかな「データの意味」も表す
Microdata 
<div itemscope itemtype="http://schema.org/Person"> 
My name is <span itemprop="name">Bob Smith</span> 
but people call me <span schema.itemprop="org 
nickname">Smithy</span>. 
Here is my home page: 
<a href="is the http://standard www.example.vocabulary com" itemprop="promoted url">www.example.I live in Albuquerque, NM and 
by 
com</a> 
work as an Bing, <span itemprop="Google, title">at <span itemprop="affiliation">Yahoo! engineer</span> 
ACME Corp</and Yandex 
span>. 
</div> 
• Mechanism that embeds structured data within an HTML 
document 
• Document structure can change without changing data 
• Connects data with a URL that roughly represents 
the “meaning of data” (this is also a kind of link) 
http://getschema.org/index.php/Main_Page
You could build a Web API in HTML 
HTMLでWeb APIを作ることもできる 
var user = document.getItems('http://schema.org/Person')[0]; 
var name = user.properties['name'][0].itemValue; 
alert('Hello ' + name + '!'); 
• “Microdata DOM API” allows clients to extract data from HTML 
http://www.w3.org/TR/microdata/#using-the-microdata-dom-api 
Microdata DOM APIでHTMLからデータを抽出できる 
• Available in JavaScript: https://github.com/termi/Microdata-JS 
• There are also some specs for translating Microdata into JSON 
MicrodataからJSONに変換もできる 
• HTML’s great advantage is that it has links and forms built-in 
HTMLはリンクとフォームを持っているのが大きなアドバンテージ
But you probably want a 
JSON Web API… 
でもたぶんJSON Web APIが欲しいよね 
data link form 
HTML 
+Microdata ✓✓ ✓ ✓ 
JSON ✓ - - 
✓✓: including “meaning of data” 
• You have to fill in links and forms 
(also the meanings of data, if possible) 
リンクとフォームを埋めればいい(できればデータの意味も)
Links and Forms in JSON 
• Use a JSON-based 
format that can represent 
links and forms 
• There are other formats 
Siren, Collection+JSON, 
Mason, Verbose, etc 
data link form 
JSON ✓ - - 
JSON 
+Link header ✓ ✓ - 
HAL ✓ ✓ - 
JSON-LD ✓✓ ✓ - 
JSON-LD 
+Hydra ✓✓ ✓ ✓ 
UBER ✓ ✓ ✓ 
リンクとフォームを表現できる 
JSONベースのフォーマットがある 
✓✓: including “meaning of data”
A Solution
Hypermicrodata gem 
https://github.com/tkawa/hypermicrodata 
• Translate HTML into JSON on Server-side 
• Extract not only Microdata but also links and 
forms from HTML 
• Generate a JSON-based format that naturally fits 
with an explanation of meaning of data 
サーバサイドでHTMLをJSONに変換 
Microdataだけではなく 
リンクとフォームもHTMLから抽出 
データの意味も表しやすい形で 
JSONベースのフォーマットを生成
Design procedure in Rails with 
Hypermicrodata gem 
Hypermicrodata gemを使ったRailsによる設計手順 
1. Design resources 
2. Draw a state diagram 
3. Connect names of data with corresponding URLs 
4. Write HTML templates (Haml, Slim, etc) with 
Microdata markup 
1. リソース設計 
2. 状態遷移図を描く 
3. データの名前を対応するURLに結びつける 
4. HTMLテンプレートを書き 
Microdataでマークアップ 
(Then, write profiles and explanations that are not defined in 
schema.org, if necessary)
1. Design resources 
column name short description type 
text content text of note text 
published_at published time of note datetime 
(id, created_at, updated_at) (auto-generated) 
$ rails g model Note text:text published_at:datetime 
model: Note 
controller: NotesController 
routing: resources :notes
2. Draw a state diagram 
Begin with Collection & Member Resource pattern of Rails (API ver.) 
item 
collection 
Collection Member 
create*† 
update*, delete* 
* unsafe 
† non-idempotent
Collection 
of Note 
Note 
(text, published_at, 
created_at, 
updated_at, id) 
item 
collection 
create*† 
update*, delete*, 
* unsafe 
† non-idempotent 
publish* 
next, prev 
Home 
notes home
3. Connect names of data with 
corresponding URLs 
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 (No need because each note has its own URL) 
Home http://schema.org/SiteNavigationElement
4. Write HTML templates with Microdata 
Collection of Note 
%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' 
/app/views/notes/index.html.haml 
GET /notes HTTP/1.1 
Host: www.example.com 
Accept: application/vnd.amundsen-uber+json 
= 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"} 
] 
}] 
} 
} 
Link 
Form
%div{itemscope: true, itemtype: 'http://schema.org/Article', 
itemid: note_url(@note), data: {main_item: true}} 
/app/views/notes/show.html.haml 
%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' 
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" } 
] 
}] 
} 
}
%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 
= button_to 'Publish', publish_note_path(@note), 
= link_to 'Next note', note_path(@note.next), 
= link_to 'Prev note', note_path(@note.prev), 
{ 
"uber": { 
rel: 'publish' unless @note.published? 
rel: 'next' if @note.next 
rel: 'prev' if @note.prev 
"version": "1.0", 
"data": [{ 
"url": "http://www.example.com/notes/1", 
"name": "Article", 
"data": [ 
explanation of 
restriction 
Now you can publish, 
but cannot go prev 
{ "name": "articleBody", "value": "First note's text" }, 
{ "name": "datePublished", "value": null }, 
{ "name": "dateCreated", "value": "2014-09-11T12:00:31+09:00" }, 
{ "{ rel": "name": "publish", "dateModified", "url": "value": "/"2014-notes/09-11T12:1/publish", 
00:31+09:00" }, 
"{ action": "name": "isPartOf", "append" "rel": }, 
"collection", "url": "/notes" }, 
{ "rel": "update", "url": "/notes/1", "action": "replace", 
{ "rel": "model": "next", "note%5Btext%"url": 5D={text}" "/notes/}, 
2" }, 
{ "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" } 
] 
}] 
} 
}
3 Pros of this design procedure 
• DRY 
• When providing both HTML and JSON 
• Awareness of links and forms 
• Framing the API as an HTML Web app gets you 
focused on these state transition 
• Constraints 
• “Constraints are liberating” 
この設計手順の3つのメリット 
HTMLとJSON両方提供するならDRY 
APIをWebアプリと同じように考えることで状態遷移に着目し 
リンクとフォームを意識できる 
「制約は自由をもたらす」
If you want to write only JSON, 
you should keep in mind 
もしJSONだけを書くときは注意すること 
• To stay focused on the link/form pattern: 
• Draw a state diagram 
リンク・フォームを意識するために 
状態遷移図を描きましょう 
• To keep your API decoupled: 
• Use view templates or representers such as Jbuilder/RABL 
instead of model.to_json 
疎結合のために、model.to_jsonはやめて 
ビューテンプレートを使いましょう 
• Use a JSON-based format with links and forms 
リンクとフォームを持ったJSONベースのフォーマットを使いましょう 
• In addition, it is better to use standard names such as schema.org 
schema.orgのような標準名を使うとさらに良いです
“WebアプリとWeb APIを分けて考えない” 
“Don’t consider Web app and Web API separately” 
– 「Webを支える技術」@yohei
Conclusion: 
Design Your Web API the same way 
as an HTML Web App 
結論: Web APIはHTML Webアプリと同じように設計しよう 
• A Web API is nothing special, It just has a different 
representation format 
Web APIは特別なものではなく、ただ表現フォーマットが違うだけ 
• Awareness of state transitions by drawing a 
diagram will remind you of links and forms 
状態遷移図を描いて状態遷移を意識することで、リンクやフォームを忘れずにすむ
Finally 
• Unfortunately, no de-facto standard JSON format, 
client implementations, libraries, etc 
残念ながら、デファクトスタンダードがない 
• We can do better by focusing on the principles and 
constraints of REST 
RESTの制約・原則を意識するともっとうまくできる 
• Hypermedia is one of the most important elements of 
REST, and a key step toward building Web APIs 
adaptable to change 
ハイパーメディアはRESTの最も重要な要素で 
変化に適応できるWeb APIへの重要なステップ
Build a Better & Adaptable Web API. 
Thank you for your attention. 
References 
• 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

More Related Content

Hypermedia: The Missing Element to Building Adaptable Web APIs in Rails

  • 1. HYPERMEDIA: THE MISSING ELEMENT to Building Adaptable Web APIs in Rails ハイパーメディア: RailsでWeb APIをつくるには、これが足りない Toru Kawamura @tkawa ! RubyKaigi 2014
  • 2. @tkawa Toru Kawamura • Freelance Ruby/Rails programmer • Technology Assistance Partner at SonicGarden Inc. • RESTafarian inspired by Yohei Yamamoto (@yohei) • Co-organizer of Sendagaya.rb • Organizer of the reading group of “RESTful Web APIs”
  • 7. • Private • For internal use • For SPA or dedicated clients only • Almost expected, almost controllable • Public • For external use • For general-purpose clients • Less expected, less controllable
  • 8. “Whether an API should be RESTful or not depends on the requirement” – 「WebAPIのこれまでとこれから」by @yohei http://www.slideshare.net/yohei/webapi-36871915
  • 11. Change is inevitable ! Web APIs must adapt to changes 変化は避けられない Web APIは変化に適応しなければならない
  • 12. Two types of Change With versioning Without versioning Incompatible Compatible Breaks clients Does not break clients Breaking Change Non-Breaking Change
  • 13. Breaking Changes are Harmful 壊す変更は有害 • Terrible user experience ひどいユーザ体験 • Forces client developers to rewrite/redeploy code クライアント開発者にコードの書き直し・再デプロイを強いる • What if on …
  • 14. Because of what? なぜ起こるの? With versioning Without versioning Incompatible Compatible Breaks clients Does not break clients Breaking Change Non-Breaking Change
  • 15. Many clients are built from human-readable documentation 人間が読める説明書から作られる クライアントがたくさんある GET /v1/statuses?id=#{id} GET /v1/statuses?id=#{id}
  • 16. GET /v2/statuses/#{id} GET /v1/statuses?id=#{id} ×Need to rewrite code
  • 17. Some clients are built from machine-readable documentation { "apiVersion": "1.0.0", "basePath": "http:// petstore.swagger.wordnik.com/api", "resourcePath": "/store", "produces": [ "application/json" ], "apis": [ { "path": "/store/order/{orderId}", "operations": [ { GET /v1/statuses?id=#{id} GET /v1/statuses?id=#{id} "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": [ 機械が読める説明書から作られる クライアントもある
  • 18. GET /v2/statuses/#{id} GET /v1/statuses?id=#{id} ×Need to regenerate code { "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": [
  • 19. { uber: { Because of Coupling version: "1.0", data: [{ url: "http://www.ishuran.dev/notes/1", name: "Article", data: [ • API { name: changes "articleBody", should be reflected in clients value: "First note's text" }, { It is name: • value: }, good "datePublished", null to split up explanations of the API and { embed name: "dateCreated", value: "2014-them 09-11T12:into 00:31+09:00" each API response }, { name: "dateModified", value: "2014-09-11T12:00:31+09:00" • A }, { lot of assumptions about the API make a tight name: "isPartOf", rel: "collection", coupling url: "/notes" 密結合のせい APIの変更がクライアントに反映されるべき APIの説明を分割して各レスポンスに埋め込むのが良い APIについての多大な仮定は密結合を生む
  • 20. With versioning Without versioning Incompatible Compatible Breaks clients Does not break clients Breaking Change Non-Breaking Change because of the Coupling because of the Decoupling
  • 21. Decoupling in a example: FizzBuzzaaS • by Stephen Mizell 例で見る疎結合 http://fizzbuzzaas.herokuapp.com/ http://smizell.com/weblog/2014/solving-fizzbuzz-with-hypermedia • Server knows how to calculate FizzBuzz for given number (<= 100) サーバは100までの数のFizzBuzzを計算できる • Server knows what the next FizzBuzz will be サーバは次のFizzBuzzが何になるか知っている • Client wants all FizzBuzz from one to the last in order クライアントは1から最後まで順番にすべてのFizzBuzzが欲しい http://sef.kloninger.com/posts/ 201205fizzbuzz-for-managers. html
  • 22. Coupled client 密結合なクライアント answer = HTTP.get("/v1/fizzbuzz?number=#{i}") puts answer end "/v2/fizzbuzz/#{i}" (1..1000) (1..100).each do |i| • Every URL and parameter is hardcoded • Duplicates the server logic such as counting up すべてのURLとパラメータがハードコードされている カウントアップのようなサーバロジックと同じことをやっている
  • 23. Decoupled client 疎結合なクライアント root = HTTP.get_root answer = root.link('first').follow puts answer while answer.link('next').present? answer = answer.link('next').follow puts answer end Link ‘next’ is the key • No hardcoded URLs • Client doesn’t break when changing URLs / the restriction ハードコードされたURLなし URLや条件を変えてもクライアントは壊れない
  • 24. The “API Call” metaphor is dangerous 「APIコール」のメタファーは危険 • We need to move away from the paradigm where a client arranges a URL and parameters in advance and calls API (like RPC…) URLとパラメータを用意してAPIを呼ぶというRPCのようなパラダイムから離れよう • What a client does next should be to choose from links in the response == HYPERMEDIA クライアントが次にすることはリンクから選ぶこと これがハイパーメディア
  • 25. This is not imaginary but already present in HTML これは想像上のものではなく、すでにHTMLにある
  • 26. The HTML Web • Web apps and websites have been changing constantly without breaking browsers • Why don’t browsers break on the HTML Web? There are links in HTML WebアプリやWebサイトはずっと変わり続けているけど ブラウザは壊れていないのはなぜ? http://www.youtypeitwepostit.com/messages
  • 27. Workflow in HTML • Web app includes a (suggested) workflow • Workflow is represented by a sequence of screen transitions — Links and Forms Webアプリはワークフローを含む ワークフローは一連の画面遷移で表現される それはリンクとフォーム ”RESTful Web APIs” p.11 Figure 1-7
  • 28. Hypermedia show the workflow ハイパーメディアはワークフローを示す • Each screen includes what a browser can do next through links and forms like a “menu” • A browser chooses from the “menu” to go to the next step • This is HYPERMEDIA and exactly what FizzBuzzaaS does 3 4 各画面は次に何ができるかのリンクやフォームの 「メニュー」を含み、ブラウザはその中から選ぶ これがハイパーメディア
  • 29. One more hint in a Crawler クローラーにはもう1つヒントが • Crawlers follow links and can submit some forms • Crawlers understand the data in an HTML document and their “meaning” • How can they do that? クローラはHTMLの中のデータと意味を理解している https://support.google.com/webmasters/answer/99170 どうやって?
  • 30. 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> • Mechanism that embeds structured data within an HTML document • Document structure can change without changing data • Connects data with a URL that roughly represents the “meaning of data” (this is also a kind of link) Microdataは構造化データをHTMLに埋め込むしくみ URLに結びつけることで大まかな「データの意味」も表す
  • 31. Microdata <div itemscope itemtype="http://schema.org/Person"> My name is <span itemprop="name">Bob Smith</span> but people call me <span schema.itemprop="org nickname">Smithy</span>. Here is my home page: <a href="is the http://standard www.example.vocabulary com" itemprop="promoted url">www.example.I live in Albuquerque, NM and by com</a> work as an Bing, <span itemprop="Google, title">at <span itemprop="affiliation">Yahoo! engineer</span> ACME Corp</and Yandex span>. </div> • Mechanism that embeds structured data within an HTML document • Document structure can change without changing data • Connects data with a URL that roughly represents the “meaning of data” (this is also a kind of link) http://getschema.org/index.php/Main_Page
  • 32. You could build a Web API in HTML HTMLでWeb APIを作ることもできる var user = document.getItems('http://schema.org/Person')[0]; var name = user.properties['name'][0].itemValue; alert('Hello ' + name + '!'); • “Microdata DOM API” allows clients to extract data from HTML http://www.w3.org/TR/microdata/#using-the-microdata-dom-api Microdata DOM APIでHTMLからデータを抽出できる • Available in JavaScript: https://github.com/termi/Microdata-JS • There are also some specs for translating Microdata into JSON MicrodataからJSONに変換もできる • HTML’s great advantage is that it has links and forms built-in HTMLはリンクとフォームを持っているのが大きなアドバンテージ
  • 33. But you probably want a JSON Web API… でもたぶんJSON Web APIが欲しいよね data link form HTML +Microdata ✓✓ ✓ ✓ JSON ✓ - - ✓✓: including “meaning of data” • You have to fill in links and forms (also the meanings of data, if possible) リンクとフォームを埋めればいい(できればデータの意味も)
  • 34. Links and Forms in JSON • Use a JSON-based format that can represent links and forms • There are other formats Siren, Collection+JSON, Mason, Verbose, etc data link form JSON ✓ - - JSON +Link header ✓ ✓ - HAL ✓ ✓ - JSON-LD ✓✓ ✓ - JSON-LD +Hydra ✓✓ ✓ ✓ UBER ✓ ✓ ✓ リンクとフォームを表現できる JSONベースのフォーマットがある ✓✓: including “meaning of data”
  • 36. Hypermicrodata gem https://github.com/tkawa/hypermicrodata • Translate HTML into JSON on Server-side • Extract not only Microdata but also links and forms from HTML • Generate a JSON-based format that naturally fits with an explanation of meaning of data サーバサイドでHTMLをJSONに変換 Microdataだけではなく リンクとフォームもHTMLから抽出 データの意味も表しやすい形で JSONベースのフォーマットを生成
  • 37. Design procedure in Rails with Hypermicrodata gem Hypermicrodata gemを使ったRailsによる設計手順 1. Design resources 2. Draw a state diagram 3. Connect names of data with corresponding URLs 4. Write HTML templates (Haml, Slim, etc) with Microdata markup 1. リソース設計 2. 状態遷移図を描く 3. データの名前を対応するURLに結びつける 4. HTMLテンプレートを書き Microdataでマークアップ (Then, write profiles and explanations that are not defined in schema.org, if necessary)
  • 38. 1. Design resources column name short description type text content text of note text published_at published time of note datetime (id, created_at, updated_at) (auto-generated) $ rails g model Note text:text published_at:datetime model: Note controller: NotesController routing: resources :notes
  • 39. 2. Draw a state diagram Begin with Collection & Member Resource pattern of Rails (API ver.) item collection Collection Member create*† update*, delete* * unsafe † non-idempotent
  • 40. Collection of Note Note (text, published_at, created_at, updated_at, id) item collection create*† update*, delete*, * unsafe † non-idempotent publish* next, prev Home notes home
  • 41. 3. Connect names of data with corresponding URLs 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 (No need because each note has its own URL) Home http://schema.org/SiteNavigationElement
  • 42. 4. Write HTML templates with Microdata Collection of Note %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' /app/views/notes/index.html.haml GET /notes HTTP/1.1 Host: www.example.com Accept: application/vnd.amundsen-uber+json = 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"} ] }] } } Link Form
  • 43. %div{itemscope: true, itemtype: 'http://schema.org/Article', itemid: note_url(@note), data: {main_item: true}} /app/views/notes/show.html.haml %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' 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" } ] }] } }
  • 44. %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 = button_to 'Publish', publish_note_path(@note), = link_to 'Next note', note_path(@note.next), = link_to 'Prev note', note_path(@note.prev), { "uber": { rel: 'publish' unless @note.published? rel: 'next' if @note.next rel: 'prev' if @note.prev "version": "1.0", "data": [{ "url": "http://www.example.com/notes/1", "name": "Article", "data": [ explanation of restriction Now you can publish, but cannot go prev { "name": "articleBody", "value": "First note's text" }, { "name": "datePublished", "value": null }, { "name": "dateCreated", "value": "2014-09-11T12:00:31+09:00" }, { "{ rel": "name": "publish", "dateModified", "url": "value": "/"2014-notes/09-11T12:1/publish", 00:31+09:00" }, "{ action": "name": "isPartOf", "append" "rel": }, "collection", "url": "/notes" }, { "rel": "update", "url": "/notes/1", "action": "replace", { "rel": "model": "next", "note%5Btext%"url": 5D={text}" "/notes/}, 2" }, { "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" } ] }] } }
  • 45. 3 Pros of this design procedure • DRY • When providing both HTML and JSON • Awareness of links and forms • Framing the API as an HTML Web app gets you focused on these state transition • Constraints • “Constraints are liberating” この設計手順の3つのメリット HTMLとJSON両方提供するならDRY APIをWebアプリと同じように考えることで状態遷移に着目し リンクとフォームを意識できる 「制約は自由をもたらす」
  • 46. If you want to write only JSON, you should keep in mind もしJSONだけを書くときは注意すること • To stay focused on the link/form pattern: • Draw a state diagram リンク・フォームを意識するために 状態遷移図を描きましょう • To keep your API decoupled: • Use view templates or representers such as Jbuilder/RABL instead of model.to_json 疎結合のために、model.to_jsonはやめて ビューテンプレートを使いましょう • Use a JSON-based format with links and forms リンクとフォームを持ったJSONベースのフォーマットを使いましょう • In addition, it is better to use standard names such as schema.org schema.orgのような標準名を使うとさらに良いです
  • 47. “WebアプリとWeb APIを分けて考えない” “Don’t consider Web app and Web API separately” – 「Webを支える技術」@yohei
  • 48. Conclusion: Design Your Web API the same way as an HTML Web App 結論: Web API��HTML Webアプリと同じように設計しよう • A Web API is nothing special, It just has a different representation format Web APIは特別なものではなく、ただ表現フォーマットが違うだけ • Awareness of state transitions by drawing a diagram will remind you of links and forms 状態遷移図を描いて状態遷移を意識することで、リンクやフォームを忘れずにすむ
  • 49. Finally • Unfortunately, no de-facto standard JSON format, client implementations, libraries, etc 残念ながら、デファクトスタンダードがない • We can do better by focusing on the principles and constraints of REST RESTの制約・原則を意識するともっとうまくできる • Hypermedia is one of the most important elements of REST, and a key step toward building Web APIs adaptable to change ハイパーメディアはRESTの最も重要な要素で 変化に適応できるWeb APIへの重要なステップ
  • 50. Build a Better & Adaptable Web API. Thank you for your attention. References • 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