第5部 Webサービスの設計
第15章 読み取り専用のWebサービスの設計
15.1 リソース設計とは何か
- リソース設計とは
- クライアントとサーバ間のインターフェースの設計(WebサービスやWebAPIの外部設計)
- どのようにリソースを分割し、URIで名前をつけ、相互にリンクを持たせるか
- 設計とは
- システムをどのような構成でどのように開発するのかを検討し、図や文書に残す作業
- リソースの設計図
- リソースの種類、表現、操作方法、リンク関係
15.2 リソース指向アーキテクチャのアプローチ
- リソース指向アーキテクチャ
- Webサービスで提供するデータを特定する
- データをリソースに分ける
- リソースにURIで名前をつける
- クライアントに提供するリソースの表現を設計する
- リンクとフォームを利用してリソース同士を結びつける
- イベントの標準的なコースを検討する
- エラーについて検討する
15.3 郵便番号検索サービスの設計
- 郵便番号検索サービスの要件
- 日本の郵便番号情報を提供する
- 郵便番号の前方一致で、郵便番号情報を検索できる
- 住所および住所の読みで、郵便番号情報を全文検索できる
- データはすべて読み取り専用である
15.4 Webサービスで提供するデータを特定する
- リソース設計の最初の工程
- サービスで提供するデータを理解し、特定する
- 7桁の郵便番号
- その郵便番号が表現する住所(都道府県名、市町村名、町域名)
- 住所のカタカナ読み
- サービスで提供するデータを理解し、特定する
15.5 データをリソースに分ける
- 用意すべきリソースとは
- Web上の名前のついた情報
- 機能をリソースに落とし込む場合は、機能の結果をリソースとして捉える
- クライアントがサービスを利用するときに最初にアクセスするスタート地点
- リソースの具体例
- 郵便番号リソース
- 1つの郵便番号に対応するリソース
- 郵便番号の住所や読みも入っている
- 検索結果リソース
- 郵便番号の一部や住所の一部で郵便番号を検索した結果のリソース
- 地域リソース
- 都道府県、市区町村、町域のリソース
- 上位の地域は、下位の地域の情報を含んでいる
- トップレベルリソース
- Webサービスのスタート地点
- 都道府県リソースへのリンクと、検索フォームを含んでいる
- 郵便番号リソース
- 通常はこの分割作業を何回か繰り返し、最適なリソース分割を求める
15.6 リソースにURIで名前を付ける
- 郵便番号リソース
- http://zip.ricollab.jp/1120002
- 郵便番号で一意に識別できるので、郵便番号を使う
- “-”は、プログラムから扱いやすいよう付けないものを正規リソースとして、”-”付きは代理リソースとして用意する(”-”なしにリダイレクトされるようにする)
- 検索結果リソース
- http://zip.ricollab.jp/search?q=小石川
- qは検索クエリを表現するクエリパラメータ
- 実際は”小石川”の部分はUTF-8で%エンコードする
- 地域リソース
- トップレベルリソース
-
http://zip.ricollab.jp
- ルートとなるURIを与える
-
http://zip.ricollab.jp
15.7 クライアントに提供するリソースの表現を設計する
- Web上の代表的な表現形式
分類 | 表現形式 |
---|---|
XML表現 | XHTML |
Atom | |
独自XML | |
軽量フォーマット表現 | JSON/JSONP |
YAML | |
CSV | |
マルチメディア表現 | 画像(GIF、JPEG、PNG) |
映像(MPEG、WMV、MOV) | |
マルチページ画像(PDF、TIFF) |
-
XML表現
- 独自フォーマットをなるべく作り出さないこと(まずは既存のフォーマットを探す)
- XHTML
- 基本的な文書構造を持ち、リンクやフォームといったハイパーメディアで必要となる機能を一通り揃えている
- Atom
- ブログや検索結果、ポッドキャストなどのリスト情報を表現するのに向いている
- 著者や更新日時が必須なので、それらを持たないデータには適用しづらい
- 郵便番号リソースのXHTML表現(112-0002)
<html xmls="http://www.w3.org/1999/xhtml"> <head> <title>〒112-0002</title> </head> <body> <h1>〒112-0002</h1> <dl> <dt>番号</dt> <dd class="zipcode">1120002</dd> <dt>住所</dt> <dd class="address"> <span class="prefecture">東京都</span> <span class="city">文京区</span> <span class="town">小石川</span> </dd> <dt>フリガナ</dt> <dd class="address"> <span class="prefecture">トウキョウト</span> <span class="city">ブンキョウク</span> <span class="town">コイシカワ</span> </dd> </dl> </body> </html>
- 検索結果リソースのXHTML表現(「112」の検索結果)
<html xmls="http://www.w3.org/1999/xhtml"> <head> <title>「112」の検索結果</title> </head> <body> <h1>「<span class="query">112</span>」の検索結果</h1> <p><span class="totalResults">101</span>件中1件目から<span class="itemsPerPage">10</span>件</p> <ul class="result"> <li> <span class="zipcode">1120000</span> <span class="address">東京都文京区以下に掲載がない場合</span> </li> <li> <span class="zipcode">1120001</span> <span class="address">東京都文京区白山(2〜5丁目)</span> </li> ... <li> <span class="zipcode">1120013</span> <span class="address">東京都文京区音羽</span> </li> </ul> </body> </html>
- 地域リソースのXHTML表現(東京都)
<html xmls="http://www.w3.org/1999/xhtml"> <head> <title>東京都の一覧</title> </head> <body> <h1 class="area"><span class="prefecture">東京都</span>の一覧</h1>h1> <ul class="result"> <li> <span class="city">千代田区</span> (<span class="yomi">チヨダク</span>) </li> <li> <span class="city">中央区</span> (<span class="yomi">チュウオウク</span>) </li> ... <li> <span class="city">小笠原村</span> (<span class="yomi">オガサワラムラ</span>) </li> </ul> </body> </html>
-
軽量フォーマット表現
- 利点と欠点
フォーマット 利点/欠点 JSON ○ JavaScriptとの相性が良い、配列やハッシュなどのデータ構造もある × YAMLに比べると書きづらい YAML ○ 読みやすさ、書きやすさが一番 × ライブラリが比較的少ない CSV ○ 表形式のデータには最適、読み込み可能なソフトも多数存在 × 文字コードの扱いに難あり、エスケープ文字問題もある - 郵便番号リソースのJSON表現(112-0002)
{ "zipcode": "1120002", "address": { "prefecture": "東京都", "city": "文京区", "town": "小石川" }, "yomi": { "prefecture": "トウキョウト", "city": "ブンキョウク", "town": "コイシカワ" } }
- 検索結果リソースのJSON表現(「112」の検索結果)
{ "query": "112" "totalResults": 101, "itemPerPage": 10, "result": [{ "zipcode": "1120000", "address": "東京都文京区以下に掲載がない場合" }, { "zipcode": "1120001", "address": "東京都文京区白山(2〜5丁目)" }, ... { "zipcode": "1120013", "address": "東京都文京区音羽" }] }
- 地域リソースのJSON表現(東京都)
{ "area": { "prefecture": "東京都" }, "result": [{ "name": "千代田区", "yomi": "チヨダク" }, { "name": "中央区", "yomi": "チュウオウク" }, ... { "name": "小笠原村", "yomi": "オガサワラムラ" }] }
-
URIでリソースを指定する
- 郵便番号リソースのXHTML表現のURI
- 郵便番号リソースのJSON表現のURI
- 地域リソースのXHTML表現のURI
- 地域リソースのJSON表現のURI
- 郵便番号リソースのJSONPのURI
- 地域リソースのJSONPのURI
- 検索結果リソースのXHTML表現のURI
- 検索結果リソースのJSON表現のURI
- 検索結果リソースのJSONP表現のURI
15.8 リンクとフォームを利用してリソース同士を結びつける
-
検索結果リソース
- 検索結果リソースのXHTML表現(「112」の検索結果)
<html xmls="http://www.w3.org/1999/xhtml"> <head> <title>「112」の検索結果</title> </head> <body> <h1>「<span class="query">112</span>」の検索結果</h1> <p><span class="totalResults">101</span>件中1件目から<span class="itemsPerPage">10</span>件</p> <ul class="result"> <li> <span class="zipcode">1120000</span> <a href="http://zip.ricollab.jp/1120000" class="address">東京都文京区以下に掲載がない場合</a> </li> <li> <span class="zipcode">1120001</span> <a href="http://zip.ricollab.jp/1120001" class="address">東京都文京区白山(2〜5丁目)</a> </li> ... <li> <span class="zipcode">1120013</span> <a href="http://zip.ricollab.jp/1120013" class="address">東京都文京区音羽</a> </li> </ul> <p><a href="http://zip.ricollab.jp/search?q=112&page=2" rel="next">次へ</a></p> </body> </html>
- 検索結果リソースのJSON表現(「112」の検索結果)
{ "query": "112" "totalResults": 101, "itemPerPage": 10, "next": "http://zip.ricollab.jp/search?q=112&type=json&page=2" "result": [{ "zipcode": "1120000", "address": "東京都文京区以下に掲載がない場合", "link": "http://zip.ricollab.jp/1120000" }, { "zipcode": "1120001", "address": "東京都文京区白山(2〜5丁目)", "link": "http://zip.ricollab.jp/1120001" }, ... { "zipcode": "1120013", "address": "東京都文京区音羽", "link": "http://zip.ricollab.jp/1120013" }] }
-
地域リソース
- 地域リソースのXHTML表現(東京都)
<html xmls="http://www.w3.org/1999/xhtml"> <head> <title>東京都の一覧</title> </head> <body> <h1 class="area"><span class="prefecture">東京都</span>の一覧</h1>h1> <ul class="result"> <li> <a href="http://zip.ricollab.jp/東京都/千代田区" class="name">千代田区</a> (<span class="yomi">チヨダク</span>) </li> <li> <a href="http://zip.ricollab.jp/東京都/中央区" class="name">中央区</a> (<span class="yomi">チュウオウク</span>) </li> ... <li> <a href="http://zip.ricollab.jp/東京都/小笠原村" class="name">小笠原村</a> (<span class="yomi">オガサワラムラ</span>) </li> </ul> </body> </html>
- 地域リソースのJSON表現(東京都)
{ "area": { "prefecture": "東京都" }, "result": [{ "name": "千代田区", "yomi": "チヨダク", "link": "http://zip.ricollab.jp/東京都/千代田区.json" }, { "name": "中央区", "yomi": "チュウオウク", "link": "http://zip.ricollab.jp/東京都/中央区.json" }, ... { "name": "小笠原村", "yomi": "オガサワラムラ", "link": "http://zip.ricollab.jp/東京都/小笠原村.json" }] }
-
郵便番号リソース
- 郵便番号リソースのXHTML表現(112-0002)
<html xmls="http://www.w3.org/1999/xhtml"> <head> <title>〒112-0002</title> </head> <body> <h1>〒112-0002</h1> <dl> <dt>番号</dt> <dd class="zipcode">1120002</dd> <dt>住所</dt> <dd class="address"> <!-- 東京都へのリンク --> <a href="http://zip.ricollab.jp/東京都" class="prefecture">東京都</a> <!-- 東京都/文京区へのリンク --> <a href="http://zip.ricollab.jp/東京都/文京区" class="city">文京区</a> <!-- 東京都/小石川へのリンク --> <a href="http://zip.ricollab.jp/東京都/文京区/小石川" class="town">小石川</a> </dd> <dt>フリガナ</dt> <dd class="address"> <span class="prefecture">トウキョウト</span> <span class="city">ブンキョウク</span> <span class="town">コイシカワ</span> </dd> </dl> </body> </html>
-
トップレベルリソース
- 地域リソースの一覧
<html xmls="http://www.w3.org/1999/xhtml"> <head><title>郵便番号検索</title></head> <body> <h1>都道府県一覧</h1> <ul> <li><a href="http://zip.ricollab.jp/北海道">北海道</a></li> ... <li><a href="http://zip.ricollab.jp/沖縄県">沖縄県</a></li> </ul> </body> </html>
- 検索結果を生成するフォーム
<html xmls="http://www.w3.org/1999/xhtml"> <head><title>郵便番号検索</title></head> <body> <h1>郵便番号検索</h1> <form method="GET" action="http://zip.ricollab.jp/search"> <p> <input id="q" name="q" type="text" /> <input type="radio" id="type1" name="type" value="json" /> JSON, <input type="radio" id="type2" name="type" value="html" /> XHTML <input type="submit" id="submit" name="submit" value="検索" /> </p> <h1>郵便番号一覧</h1> <ul> ... </ul> </form> </body> </html>
-
リソース間のリンク関係を図に起こしておくと、リソースのリンクの問題を把握しやすくなる
15.9 イベントの標準的なコースを検討する
- 郵便番号を検索するコース
- フォームに郵便番号を入力
- 検索結果リソースを取得
- 目的の郵便番号リソースを取得
- 住所から郵便番号を検索するコース
- フォームに住所を入力
- 検索結果リソースを取得
- (必要であれば)ページをめくる
- 目的の郵便番号リソースを取得
- 地域リソースの段階をたどりながら郵便番号を選択するコース
- 都道府県リソースを選択し市町村一覧を表示
- 市町村リソースを選択し町域一覧を表示
- 町域リソースを選択し郵便番号一覧を表示
- 目的の郵便番号リソースを取得
15.10 エラーについて検討する
- 存在しないURIを指定した
- 404 Not Found
- 必須パラメータを指定していない
- 400 Bad Request
- サポートしないメソッドを使用した
- 405 Method Not Allowed
15.11 リソース設計のスキル
- URI設計方法、表現選択指針、リンクの設計をスキルとして身につけること
第16章 書き込み可能なWebサービスの設計
16.1 書き込み可能なWebサービスの難しさ
- 複数のユーザが同時に書き込みを行ったらどうなるのか
- バックアップしたデータをリストアするときのように同時に複数のリソースを更新するためにはどうすればよいのか
- 複数の処理手順を必ず実行するにはどうしたら良いのか
16.2 書き込み可能な郵便番号サービスの設計
- 追加する機能
- 郵便番号リソースの作成
- 郵便番号リソースの更新
- 郵便番号リソースの削除
- バッチ処理
- トランザクション
- 排他制御
16.3 リソースの作成
-
ファクトリリソースによる作成
- リソースを作成するための特別なリソース
- 今回はトップレベルリソース(http://zip.ricollab.jp/9999999)をファクトリリソースに指定
リクエストPOST / HTTP/1.1 Host: zip.ricollab.jp Content-Type: application/json { "zipcode": "9999999", "address": { "prefecture": "XX県", "city": "YY市", "town": "ZZ町" }, "yomi": { "prefecture": "エックスエックスケン", "city": "ワイワイシ", "town": "ゼットゼットチョウ" } }
レスポンスHTTP/1.1 201 Created Content-Type: application/json Location: http://zip.ricollab.jp/9999999 { "zipcode": "9999999", "address": { "prefecture": "XX県", "city": "YY市", "town": "ZZ町" }, "yomi": { "prefecture": "エックスエックスケン", "city": "ワイワイシ", "town": "ゼットゼットチョウ" } }
-
PUTによる作成
- PUTで作成する利点(POSTと比べて)
- POSTのサポートが不要のためサーバ側の実装が簡単になる
- クライアントが作成と更新を区別しなくてよい
- PUTで作成する欠点(POSTと比べて)
- クライアントがURI構造をあらかじめ知っておく必要がある
- リクエストの見た目上は、その操作が作成なのか更新なのかの区別がつかなくなる
リクエストPUT /9999999 HTTP/1.1 Host: zip.ricollab.jp Content-Type: application/json { "zipcode": "9999999", "address": { "prefecture": "XX県", "city": "YY市", "town": "ZZ町" }, "yomi": { "prefecture": "エックスエックスケン", "city": "ワイワイシ", "town": "ゼットゼットチョウ" } }
レスポンスHTTP/1.1 201 Created Content-Type: application/json { "zipcode": "9999999", "address": { "prefecture": "XX県", "city": "YY市", "town": "ZZ町" }, "yomi": { "prefecture": "エックスエックスケン", "city": "ワイワイシ", "town": "ゼットゼットチョウ" } }
- PUTで作成する利点(POSTと比べて)
16.4 リソースの更新
- 基本的にはPUTで更新する(バッチ更新はPOST)
- バルクアップデート
- リソース全体を送信して更新する方法
- クライアントの実装が簡単だが、送受信するデータが大きくなる
- パーシャルアップデート
- リソースの一部だけを送信して更新する方法
- 送受信するデータは少なくなるが、GETしたリソースの一部を修正してそのままPUTする、という使い方ができなくなる
- 更新できないプロパティを更新しようとした場合
- (郵便番号などを更新しようとした場合)400 Bad Requestを返す
16.5 リソースの削除
- 削除対象のリソースURIにDELETEを送る
- 削除対象リソースに子リソースが存在する場合、子リソースがどうなるのか考えて設計すること
16.6 バッチ処理
-
リソースの大量作成、更新をしたい場合、一括で送信(バッチ処理)できることが望ましい
-
バッチ処理のリクエスト
- 更新だが、URIを指定しないためにPOSTを使っている
POST / HTTP/1.1 Host: zip.ricollab.jp Content-type: application/json [ { "zipcode": "9999998", "address": { "prefecture": "XX県", "city": "YY市", "town": "ZZ町" }, "yomi": { "prefecture": "バツバツケン", "city": "ワイワイシ", "town": "ゼットゼットチョウ" } }, { "zipcode": "9999999", "address": { "prefecture": "XX県", "city": "YY市", "town": "ZZ町" }, "yomi": { "prefecture": "バツバツケン", "city": "ワイワイシ", "town": "ゼットゼットチョウ" } }
-
バッチ処理のレスポンス
-
更新処理の途中でエラーが起きた場合の対処方法を検討する
- バッチ処理をトランザクション化して、エラーが起きた場合は何も処理しないことをWebサービスの内部で保証する(次節で解説)
- どのリソースへの処理が成功して、どのリソースへの処理が失敗したのかをクライアントに伝える
-
207 Multi-Status
- HTTP1.1の拡張であるWebDAVが定義しているステータスコード
HTTP/1.1 207 Multi-Status Content-type: application/xml; charset="utf-8" <D:multistatus xmlns:D="DAV:"> <D:response> <D:href>http://zip.ricollab.jp/</D:href> <D:propstat> <D:status>HTTP/1.1 200 OK</D:status> </D:propstat> <D:propstat> <D:status>HTTP/1.1 500 Internal Server Error</D:status> <D:responsedescription> データベース接続エラーです。 </D:responsedescription> </D:propstat> </D:response> <D:responsedescription> 全2つのリクエスト中1つが成功しました。 </D:responsedescription> </D:multistatus>
-
独自のステータスフォーマット
HTTP/1.1 200 OK Content-type: application/json [ { "status": "200 OK", "description": "成功しました。" }, { "status": "500 Internal Server Error", "description": "データベース接続エラーです。" } ]
-
16.7 トランザクション
-
解決すべき問題
- 複数のリソースを削除する場合、全てのリソースが削除されるか、どれも削除されないことを保証するためにどうリソースを設計したら良いか
-
トランザクションリソース
-
トランザクションの開始
POST /transactions HTTP/1.1 Host: zip.ricollab.jp
HTTP/1.1 201 Created Location: http://zip.ricollab.jp/transactions/308
-
トランザクションリソースへの処理対象の追加
PUT /transactions/308/1 HTTP/1.1 Host: zip.ricollab.jp
HTTP/1.1 201 Created Location: http://zip.ricollab.jp/transactions/308/1
-
トランザクションの実行
PUT /transactions/308 HTTP/1.1 Host: zip.ricollab.jp Content-Type: application/x-www-form-urlencoded commit=true
HTTP/1.1 200 OK
HTTP/1.1 500 Internal Server Error
-
トランザクションリソースの削除
DELETE /transactions/308 HTTP/1.1 Host: zip.ricollab.jp
HTTP/1.1 200 OK
-
-
トランザクションリソース以外の解決方法
-
バッチ処理のトランザクション化
- 複数リソースに対する削除リクエストをPOSTで一括して送信し、サーバ内の実装でこれらを必ず全て削除するか、1つも削除しないかを保証する
POST /batch HTTP/1.1 Host: zip.ricollab.jp Content-Type: text/plain; charset=utf-8 DELETE /1 DELETE /2 DELETE /3
-
上位リソースに対する操作
- /entry/1を削除したら/entry/1/comment/1も/entry/1/comment/2も同時に削除できるようサーバ側で実装する
-
16.8 排他制御
-
解決すべき問題
- 複数クライアントが同時に同じリソースを更新した際に起こる競合をどう解消するか
-
悲観的ロック
-
LOCK/UNLOCK
- Timeoutヘッダ
- ロックがタイムアウトする時間を指定
- Authorizationヘッダ
- ロックするユーザの情報を指定
- 要素
- 排他ロックなのか共有ロックなのか指定
- 要素
- ロックの種類(書き込みロックなど)を指
LOCK /1120002 HTTP/1.1 Host: zip.ricollab.jp Timeout: Second-3600 Content-Type: application/xml; charset=utf-8 Authorization: Basic ... <D:lockinfo xmlns:D="DAV"> <D:lockscope><D:exclusive/></D:lockscope> <D:locktype><D:write/></D:/locktype> </D:lockinfo>
- 要素
- 現在有効なロックの情報
- 要素
- URIの階層上、どこまでをロックしているかを示す
- Infinityはロックしたリソースのパス以下全てのサブリソースをロックしていることを示す
- 要素
- このロックの所有者情報
- 要素
- このロックがタイムアウトする時間
- 要素
- 今後このロックを用いてリソースを操作する時は、Ifヘッダにこのロックトークンを指定する必要がある
HTTP/1.1 200 OK Content-Type: application/xml; charset=utf-8 <D:prop xmlns:D="DAV"> <D:lockdiscovery> <D:activelock> <D:lockscope><D:exclusive/></D:lockscope> <D:locktype><D:write/></D:/locktype> <D:depth>Infinity</D:depth> <D:owner> <D:href>mailto:yoheiy@gmail.com</D:href> </D:owner> <D:timeout>Second-3600</D:timeout> <D:locktoken> <D:href>opaquelocktoken:e71d4fae-5dec-22d6-fea5</D:href> </D:locktoken> </D:activelock> </D:lockdiscovery> </D:prop>
- ロック済みのリソースを編集するには以下のようにリクエストする
PUT /1120002 HTTP/1.1 If: (<opaquelocktoken:e71d4fae-5dec-22d6-fea5>) Content-Type: application/json { "zipcode": "1120002", "address": { "prefecture": "東京都", "city": "文京区", "town": "大石川" }, "yomi": { "prefecture": "トウキョウト", "city": "ブンキョウク", "town": "オオイシカワ" } }
- ロックの解除はUNLOCKを利用する
UNLOCK /1120034 HTTP/1.1 Host zip.ricollab.jp Content-Type: application/xml; charset=utf-8 Authorization: Basic ... Lock-Token: opaquelocktoken:e71d4fae-5dec-22d6-fea5
- Timeoutヘッダ
-
ロックリソースの導入
- ロックリソースの作成
POST /1120034 HTTP/1.1 Host: zip.ricollab.jp Content-Type: application/x-www-form-urlencoded Authorization: Basic ... scope=exclusive&timeout=300
HTTP/1.1 201 Created Location: http://zip.ricollab.jp/1120034/lock Content-Type: application/json { "locktype": "exclusive", "timeout": "2010-09-07T10:00:30Z", "owner": "yohei" }
- ロックリソースの削除
DELETE /1120034/lock HTTP/1.1 Host: zip.ricollab.jp Authorization: Basic ...
-
-
楽観的ロック
-
条件付きPUT
-
クライアントが更新リクエストを発行する際に、自分が更新しようとしているリソースに変更がないかどうか確認する仕組みが必要
-
条件付きGETと同様、Last-ModifiedかETagの値を使う
- 条件付きGET/PUT/DELETEとヘッダの対応
メソッド Last Modified Etag GET If-Modified-Since If-None-Match PUT/DELETE If-Unmodified-Since If-Match -
まずETagの値を得るためにまずリソースをGETで取得する
# リクエスト GET /1120002 HTTP/1.1 Host: zip.ricollab.jp
# レスポンス HTTP/1.1 200 OK Content-Type: application/json ETag: sample-etag-value { "zipcode": "1120002", "address": { "prefecture": "東京都", "city": "文京区", "town": "小石川" }, "yomi": { "prefecture": "トウキョウト", "city": "ブンキョウク", "town": "コイシカワ" } }
- 得られたETagの値を使って条件付きPUTを実行する
PUT /1120002 HTTP/1.1 Host: zip.ricollab.jp Content-Type: application/json If-Match: sample-etag-value { "zipcode": "1120002", "address": { "prefecture": "東京都", "city": "文京区", "town": "大石川" }, "yomi": { "prefecture": "トウキョウト", "city": "ブンキョウク", "town": "オオイシカワ" } }
HTTP/1.1 200 OK
-
-
条件付きDELETE
DELETE /1120002 HTTP/1.1 Host: zip.ricollab.jp Content-Type: application/json If-Match: sample-etag-value
HTTP/1.1 200 OK
-
412 Precondition Failed
- 条件付きOUT/DELETEを発行した際にリソースが変更されていた場合は412 Precondition Failedが返る
- 412 Precondition Failedが発生した場合の対処法
- 競合を起こしたユーザに確認をした上で、更新または削除する
- 競合を起こしたデータを、競合リソースとして別リソースに保存する(PUTの場合のみ)
- 競合を起こしたユーザに変更点を伝え、マージを促す( PUTの場合のみ)
-
16.9 設計のバランス
- なるべくシンプルに保つ
- 設計が複雑になってきたら、機能が無駄に増えてきたら、1段階メタな視点で全体を考え直すこと
- 全体をシンプルに保つことは、設計バランスを考える上で最も重要
- 困ったらリソースに戻って考える
- HTTPメソッドでは実現できない機能があると感じたら、それが独立した別リソースで代替できないかを考える
- 検索機能を実現するSEARCHメソッドをHTTPに追加するのではなく、「検索結果リソース」をGETする、と考えることが重要
- 本当に必要ならPOSTで何でもできる
第17章 リソースの設計
17.1 リソース指向アーキテクチャのアプローチの落とし穴
- リソース指向アーキテクチャの設計アプローチでは、以下の2点にコツがいる
- Webサービスで提供するデータを特定する
- データをリソースに分ける
17.2 関係モデルからの導出
-
関係モデル
- RDBMSの基礎となるデータモデルで、数学的基盤を持っている
- データの冗長性を排除するための正規化の手法が確立されており、効率的なデータベース設計ができる
-
郵便番号データのER図
- 郵便番号テーブル(都道府県IDと市区町村IDを参照)
- 郵便番号ID
- 郵便番号
- 都道府県ID(FK)
- 市区町村ID(FK)
- 町域名
- 町域名フリガナ
- 都道府県テーブル
- 都道府県ID
- 都道府県名
- 都道府県名フリガナ
- 市区町村テーブル(都道府県IDを参照)
- 市区町村ID
- 市区町村名
- 市区町村名フリガナ
- 都道府県ID(FK)
- 郵便番号テーブル(都道府県IDと市区町村IDを参照)
-
中心となるテーブルからのリソースの導出
- 今回中心となっているテーブルは郵便番号テーブル
- 郵便番号テーブルの1行(郵便番号の一つ一つ)がリソースになる
- テーブルの主キーをURIに組み込むと実装が簡単になるが、今回は郵便番号自体が一意なので郵便番号を使えば良い(可読性も向上し、URIが変わりにくくなる)
-
リソースが持つデータの特定
- まず郵便番号テーブルが持つ「郵便番号」「町域名」「町域名フリガナ」属性を郵便番号リソースに持たせる
- 次にそのテーブルが参照している別のテーブルも辿り、その属性も郵便番号リソースに持たせる
- 結果として、郵便番号リソースは郵便番号、住所(都道府県、市区町村、町域)、住所の振り仮名(都道府県、市区町村、町域)の3つのデータを持つことが到底できる
-
検索結果リソースの導出
-
検索結果リソースは、このデータベースから具体的に何を検索したいのか、というユーザの利用シナリオに基づいて導出する
- 住所の全部または一部による検索
- 郵便番号の全部または一部による検索
-
検索条件をURIに入れるよう設計する
http://zip.ricollab.jp/search?q={query}
-
-
階層の検討
- 関係モデルは階層構造は苦手
- 関係モデルの隠れた階層構造は別途ドキュメントなどから理解し、必要であればリソースの設計に反映する
- 今回の場合は、都道府県、市区町村、町域という地域リソースの階層構造ができる
-
トップレベルリソース
- ER図からは直接導出できないリソース
- 今回のように検索結果リソースを持つWebサービスであれば、検索フォームが置かれる
-
リンクによる結合
- リソース間を結合するリンクの設計にはER図の関連が利用できる
- 都道府県テーブルと市区町村テーブルの間の関連は、都道府県リソースから市区町村リソースへのリンクに相当する
-
まとめ
- 関係モデルからリソースを導出する場合は、データの持つ階層構造を考えることと、トップレベルリソースの存在を忘れないことが重要
- 関係モデルは正規化されていることが多いので、リソースを設計する場合はこれを崩して全ての情報を含めるようにすることも意識する
17.3 オブジェクト指向モデルからの導出
- 郵便番号データのクラス図
- 主要データクラスからのリソースの導出
- 主要なデータを表現しているクラスを見つける
- Zipcode
- このクラスのインスタンスの一つ一つが、それぞれURIを持ったリソースとなる
- 主要なデータを表現しているクラスを見つける
- オブジェクトの操作結果リソース
- クラスのメソッドで表現している処理そのものをリソースとして切り出すのではなく、その処理結果をリソースとする点に注意
- 階層の検討
- クラスのhas-a(1対nの関係)、is-a(あるクラスが別のクラスのサブクラスである関係)、継承関係などクラス間の構造を表現しているものがあれば、リソースでも同様の階層を持てないかどうかを検討する
- トップレベルリソース
- 関係モデルと同様に、トップレベルリソースを直接表現するクラスがないため、別途意識して導出する
- リンクによる結合
- リソースそのものを表現するオブジェクトは相互に参照を持ち、この参照はそのままリンクとして表現できる
- 検索結果リストからそれぞれのZipcodeクラスのインスタンスを参照しているところ
- ZipcodeからPrefecture、City、Townを参照しているところ
- リソースそのものを表現するオブジェクトは相互に参照を持ち、この参照はそのままリンクとして表現できる
- まとめ
- オブジェクト指向モデルからリソースを導出するときに重要なのは、クラスの持つメソッドを操作結果リソースへ変換すること
17.4 情報アーキテクチャからの導出
- 情報アーキテクチャ
- 複雑なデザイン(ユーザの操作、ページ遷移、ネットワーク通信、アプリケーションの実行など)を情報分類の観点から整理して、受け手にとって情報を探しやすくしたり、わかりやすく伝えたりするための技術
- 日本郵便のWebサイトの情報アーキテクチャ
- http://www.post.japanpost.jp/zipcode/
- ページの分け方、ページ間のリンク関係を参考にする
- トップページ
- 全国地図からの検索
- 住所での検索
- 郵便番号での検索
- 全国地図からの検索
- 都道府県名のリンクから五十音順に市区群が並んだページに移動する
- 市区群のページは市区町村のページと対になってリンク
- 市区群と市区町村のページでは、その地域内の郵便番号が一覧され、それぞれ個別の郵便番号表示ページにリンクしている
- 住所での検索
- プルダウンで都道府県を選択し、住所の一部を検索フォームに入力
- 検索結果では条件にマッチした地域の一覧を表示し、町域へとリンクする
- 郵便番号での検索
- 検索フォームに郵便番号を入力
- 検索結果では条件にマッチした地域の一覧を表示し、町域へとリンクする
- パンくずリスト
- 市区町村から上位の都道府県に戻れるように市区町村などの各ページに設置
- まとめ
- 情報アーキテクチャとリソース指向アーキテクチャは相互に補完関係にある
- リソース指向アーキテクチャの苦手な情報分類を情報アーキテクチャが補っている
- どのような操作を受け付け、全体としてどのようなハイパーメディアシステムを構成するのかは、リソース指向アーキテクチャが得意とする
- 情報アーキテクチャとリソース指向アーキテクチャは相互に補完関係にある
17.5 リソース設計で最も重要なこと
- リソース設計ではWebサービスとWebAPIを分けて考えないことがとても重要