# Webを支える技術 第5部 Webサービスの設計② ## 第16章 書き込み可能なWebサービスの設計 ### 16.1 書き込み可能なWebサービスの難しさ ブログなど、書き込み可能なWebサービスの場合、複数人の同時書き込みや複数のリソースの同時更新、複数の処理手順の実行等、**マルチリクエストの対応**を考慮しなければならない。 ### 16.2 書き込み可能なWebサービスの設計 15章の郵便番号検索サービスに加えて、郵便番号リソースの作成・更新・削除及び、バッチ処理・トランザクション・排他制御の機能を追加する。 ### 16.3 リソースの作成 #### 16.3.1 ファクトリリソースによる作成 **ファクトリリソース**とは、リソース作成のための特別なリソースを指す。 あらかじめWebサービス等で用意しておき、POSTメソッドで新しいリソースを作成する。 #### 16.3.2 PUTメソッドによる作成 PUTメソッドを利用する場合、新しく作成したいリソースのURIにリクエストを直接送り込める。 通常はPOSTメソッドを用いるが、今回は以下の理由により、**PUTメソッドでの作成も有効**である。 * POSTメソッドをサポートする必要が無く、サーバ側の実装が簡単である。 * クライアントが作成・更新の区別を行わなくてよいため、クライアントがの実装が簡単である。 但し、次のような欠点もある。 * クライアントがURI構造を予め知っておく必要がある。 * リクエストの見た目上は、その操作が作成なのか更新なのかの区別がつかなくなる。 ### 16.4 リソースの更新 リソースの更新は基本的にPUTメソッドで行う。 #### 16.4.1 バルクアップデート 更新したいリソース全体を、そのままメッセージボディに入れる方式。 クライアント側の手間が省ける利点がある一方、データ量が大きくなる欠点がある。 #### 16.4.2 パーシャルアップデート リソースの一部分だけを送信する方式。 スポット修正を行う際のデータ量が少なく処理の高速化が見込めるが、GETしたデータを修正してPUTするという一連の使い方ができなくなる。 #### 16.4.3 更新不可のプロパティの場合 郵便番号等、恒久的に変わらないプロパティに対しては、更新のリクエストはスルーし、400 Bad Requestを返して、更新できない旨をクライアントに伝えると良い。 また、自動更新が定義されているプロパティの場合も、更新のリクエストはスルーし、200 OKを返すのが良い。 ### 16.5 リソースの削除 DELETEメソッドで削除可能である。 但し、階層構造の親リソースを削除する場合は、参照の不一致を避けるため、通常は子リソースをまとめたもの全てを削除するとみなす。 ### 16.6 バッチ処理 大量のリソース作成・更新を行う際は、一度にまとめて送信するバッチ処理を行うと良い。 #### 16.6.1 バッチ処理のリクエスト バッチ処理で用いるメソッドは、POSTメソッドである。理由はPUTのようにURIで更新対象のリソースを指定できないためである。そのため、送信先はトップレベルとなる。 #### 16.6.2 バッチ処理のレスポンス **バッチ処理**は、複数の処理を一括で受理するため、処理途中でエラーが発生した際に、不完全な形で作成・更新が行われてしまうリスクがある。 これを回避するのが**トランザクション**の仕組みである。(16.7で解説) また、不完全な形で作成・更新された場合に、**どのリソースがどのような状態であるのかをクライアントに伝える**方法もある。以下この方法について解説する。 * **207 Multi Statusー<D:multistatus>型** 207 Multi Statusは、HTTP1.1の拡張であるWebDAVが定義しているステータスコードである。 このレスポンスコードに加えて、XML文の<D:status>タグで、バッチ処理の一つ一つに対しステータスコードを返す。 * **200 OKー独自フォーマット型** WebDAVを使用しない方法として、通常の200 OKに加えて同時のXMLやJSONを定義して、それぞれのステータスコードを返すものである。 ### 16.7 トランザクション **処理の原子性**(完全実行or実行無しの2択)、**一貫性**(内容に矛盾を生じさせない)、**独立性**(他のトランザクションの影響を受けない)、**耐久性**(正常終了したトランザクションの実行結果が失われない)の**4つの性質(ACID特性)を満たすのがトランザクションである。** #### 16.7.1 トランザクションリソース HTTPでトランザクションを行う場合、**トランザクションリソース**を作成する。 1. トランザクションリソースをPOSTで作成する。この時点でトランザクション開始となる。 2. トランザクションリソースに対し、PUTで処理対象のリソースのURIを追加する。 3. トランザクションリソースを実行する。PUTでcommit = trueを付け加える。レスポンスに200 OKが返れば処理成功、400,500番台のエラーが返ってきたときは処理失敗となりロールバックされる。 4. 処理が成功した場合、DELETEで削除する。なお、トランザクション処理を行う前に削除しても、開始前の状態と変わらない。 #### 16.7.2 トランザクションリソース以外の解決方法 * バッチ処理のトランザクション化 バッチ処理を拡張し、内部処理をトランザクション化してACID特性を保証する。 * 上位リソースに対する操作 サーバ側で上位リソース削除に対する下位リソースの一斉削除を規定した場合、内部的にACID特性を保証する必要がある。 ### 16.8 排他制御 複数のクライアントが同時にリソースを更新すると、競合が発生してしまう。 そこで求められるのが排他制御である。 排他制御には悲観的ロックと楽観的ロックの2種類がある。 #### 16.8.1 悲観的ロック **悲観的ロック**は、**ユーザをあまり信用せず**に競合を防ぐ排他制御の方法である。 ##### 16.8.1.1 LOCK/UNLOCK型 LOCK/UNLOCKは、バッチ処理でも登場したWebDAVが定義しているメソッドである。 1. ロックリクエスト ロックリクエストで使用するLOCKは、以下の項目を指定している。 * タイムアウトヘッダ(ロックのタイムアウト時間の指定) * <D:lockinfo>要素内の<D:lockscope>:ロックの範囲の指定 * 排他ロック(他者は参照も更新も不可。<D:exclusive>を指定>) * 共有ロック(他者は参照可能だが更新は不可。<D:shared>を指定>) * <D:lockinfo>要素内の<D:locktype>:ロックの種類の指定 * 書き込みロック(書き込みに対するロック。<D:write>を指定>) 2. ロックレスポンス ロックレスポンスは200 OKが返り、<D:prop>要素が返ってくる。<D:prop>は以下の項目を指定している。 * <D:activelock>:現在有効なロックの情報 * <D:lockscope>, <D:locktype>:ロックの範囲・種類。リクエストの通りになる。 * <D:depth>:URIの階層上どこまでロックされいるかを示す。Infinityは指定リソース及び以下すべての子リソースのロックとなる。 * <D:owner>:ロックの所有者。 * <D:timeout>:タイムアウトの設定。リクエストのタイムアウトヘッダの通りとなる。 * <D:locktoken>:今後このロックを用いてリソースを操作する際に必要な、ロックトークンを表す。リクエスト時にロックトークン指定のない場合、423 Lockedが返る。 3. 更新・解除処理 更新時は、ロックトークンを指定したうえでPUTする。 解除時はUNLOCKメソッドでロックを解除する。 ##### 16.8.1.2 独自ロックリソース型 WebDAVを使用しない方法として、バッチ処理の場合と同様に独自のロックリソースを作成する。 1. ロックリクエスト:タイムアウトとロック範囲の指定のみ記述する。 2. ロックレスポンス:ロック対象リソースの子リソースにロックリソースを作成する。 3. 他のユーザが編集がロックをかけようとしたときに返すコード * 423 Locked:WebDAV由来。 * 400 Bad Request:HTTP1.1由来。 4. 削除処理:DELETEメソッドでロックリソースを削除しロックを解除する。 #### 16.8.2 楽観的ロック 悲観的ロックの問題点は、対象範囲が拡大してしまうと、ロック解除までに膨大な待ち時間が生じる可能性がある点である。 そこで、通常の編集では文書をロックせず、**競合が起きた場合にのみ楽観的ロックを実行する。** ##### 16.8.2.1 条件付きPUT 条件付きGETと同様に、**Last-Modified及びETagの値を、実行の条件として用いるPUTメソッドである。** 値の条件として、EtagにMatchすれば、あるいはLast-Modifiedがもし変更されていなければ、条件付きPUTを実行する。 * **If-Match**(ETag向け) * **If-Unmodified-since**(Last Modified向け) ##### 16.8.2.2 条件付きDELETE 条件付きPUTと同様の実行条件をもとに、DELETEメソッドを実行する。 ##### 16.8.2.3 条件が合わない時 リソースの変更が生じており、条件が合わない場合は、412 Precondition Failedが返る。 対処法として以下の3つの方法がある。 * 競合したユーザに確認したうえで更新・削除を行う。 * 競合したデータを、競合リソースとして別リソースに保存する。(PUTの場合のみ) * 競合したユーザに変更点を伝え、マージを促す。(PUTの場合のみ) ### 16.9 設計のバランス リソース設計で生じる問題には様々な要因があり、最適な解決方法を見つけるのは至難の業である。 こうした要因を加味し、バランスを取る作業が設計作業である。 以下筆者が重要だと感じる指針を示す。 * なるべくシンプルに保つ:設計が複雑になったり、無駄に機能が増えたりしたら、一度全体を俯瞰してシンプルに考え直すべきである。 * 困ったらリソースに戻って考える:HTTPメソッドで実現できない機能があると感じたら、独立した別リソースで代替できないかを考える。 * 本当に必要ならPOSTで何でもできる:バッチ処理等、PUTメソッドで表現できない場合はPOSTメソッドを利用するのが良い。 ## 第17章 リソースの設計 ### 17.1 リソース指向アーキテクチャのアプローチの落とし穴 リソース指向アーキテクチャの手順においての落とし穴は、Webサービスで提供するデータを特定し、データをリソースに分ける方法が分からないことである。 この章では、ある程度確立されている既存の設計手法で得られた成果物をもとに、リソース設計する方法を紹介する。 ### 17.2 関係モデルからの導出 **関係モデルは、RDBMSの基礎となっているデータモデル**である。このモデルの成果である**ER図**からリソースを導出する。 #### 17.2.1. 中心となるテーブルからリソースを導出する 郵便番号データの場合、郵便番号マスタが中心となる。この**中心となるテーブルの1行を1リソースとするのが定石の一つ**である。 したがって、郵便番号マスタの1行1行、つまり郵便番号一つ一つがリソースとなる。 なお、今回は郵便番号のユニーク性を考慮して、URIには主キーの郵便番号IDではなく郵便番号をそのまま用いる。 #### 17.2.2. リソースが持つデータの特定 ER図は通常、データの重複を防ぐために正規化を行ったうえで表現する。 しかしリソース設計では、**リソース一つ一つが参照無しの自己記述的なものとなるよう、あえて正規化を崩す。** #### 17.2.3. 検索結果リソースの導出 関係モデルは特性上検索と強く結びついている。こうした機能をモデル化するのではなく、**「検索結果」のリソースとして表現する。** 具体的には、住所の全部または一部による検索、郵便番号の全部または一部による検索という検索条件を予想し、それぞれの検索条件をURIに入れるよう設計する。 #### 17.2.4. 階層の検討 階層構造は関係モデルの苦手な分野であるため、別途ドキュメントなどから理解し、必要であればリソース設計に反映する。 結果的に階層そのものがリソースとなることが多い。 #### 17.2.5. トップレベルリソース トップレベルリソースもまた、ER図から直接導出できない。他のリソースへとリンクする大本となるリソースで、検索が伴うWebサービスであれば検索フォームが設置される。 #### 17.2.6. リンクによる結合 リンクによる結合は、**ER図の関連**がヒントとなる。 #### 17.2.7 まとめ 導出できるリソース:郵便番号リソース、検索結果リソース 導出できないリソース:地域リソース、トップレベルリソース リンク:ER図の関連が利用可能。 その他:正規化崩しを行い、リソース単独で情報を持つようにすること。 ### 17.3 オブジェクト指向モデルからの導出 #### 17.3.1 郵便番号のクラス図 * Zipcode:郵便番号、都道府県、市区町村、町域のフィールドを持つ。 * ZipcodeManager:郵便番号と住所から検索する機能を持つ。 * Prefucture:都道府県の名前とフリガナを持つ。 * City:市区町村の名前とフリガナを持つ。 * Town:町域の名前とフリガナを持つ。 #### 17.3.2 主要データクラスからのリソース導出 今回最も主要なのはZipcodeで、このデータを表現しているクラスのインスタンス一つ一つが、 URIを持ったリソースとなる。 #### 17.3.3 オブジェクトの操作結果リソース メソッドで表現する処理そのものをリソースとして切り出す形となる。 検索メソッド→検索結果リソース #### 17.3.4 階層の検討 PrefuctureーCityーTownクラスはそれぞれhas-a関係という包括関係を持つ。 これは階層構造に反映することができる。 その他にもis-a関係等があるが、出現した場合は同様に階層を持てないか検討してみる。 #### 17.3.5 トップレベルリソース トップレベルリソースは、オブジェクト指向モデルからは直接導出できない。 #### 17.3.6 リンクによる結合 オブジェクトは相互に参照関係を持つため、この参照をそのままリンクとして表現可能となる。 #### 17.2.7 まとめ 導出できるリソース:郵便番号リソース、検索結果リソース、地域リソース 導出できないリソース:トップレベルリソース リンク:クラス関係が利用可能。 その他:メソッドをリソースへと変換すること。 ### 17.4 情報アーキテクチャからの導出 **情報アーキテクチャ**とは、**知識やデータの組織化**を意味し、**「情報を分かりやすく伝え」「受け手が情報を探しやすくする」** ための表現技術である。 今回は日本郵便の郵便番号検索サイトを利用する。トップページには3つの検索方法が用意されている。 #### 17.4.1 検索方法1:全国地図からの検索 1. 全国地図から都道府県を選択 2. 市区群リストから市区を選択 3. 市区町村のリストから市区町村を選択 4. 町域内郵便番号リストから郵便番号を選択 5. 個別の郵便番号ページが表示される #### 17.4.2 検索方法2:住所での検索 1. プルダウンから都道府県を選択 2. 住所の一部を検索フォームに入力する 3. 検索結果を表示する→町域へとリンク 4. 町域無内郵便番号リストから郵便番号を選択 5. 個別の郵便番号ページが表示される #### 17.4.3 検索方法3:郵便番号での検索 1. 郵便番号を検索フォームに入力する 2. 検索結果を表示する→町域へとリンク 3. 町域無内郵便番号リストから郵便番号を選択 4. 個別の郵便番号ページが表示される #### 17.4.4 パンくずリスト 市区町村などの各ページには、パンくずリストが設置されている。 各リストのリンクを踏むことで上位リソースに戻ることができる。 #### 17.4.5 まとめ 日本郵便の検索サイトと、15章で設計したリソースのリンク関係はほぼ同一であった。 したがって、**よく設計された情報アーキテクチャを持つWebサイトの構造は、そのままWebサービスに適用できるのである。** これは、情報アーキテクチャとリソース指向アーキテクチャが、相互に補完関係を持つからである。 リソース指向アーキテクチャが苦手とする情報のリソースへの分類に対し、情報アーキテクチャという分類手法を用いて解決している。 一方で情報アーキテクチャ単体ではWebサービスの設計は完了しないため、リソース指向アーキテクチャによりシステムの構成部分の設計が行われている。 両アーキテクチャの融合は、設計手法としての完成度はまだ低いものの、今後成長する分野として期待される。 ### 17.5 リソース設計で最も重要なこと 情報アーキテクチャとリソース指向アーキテクチャは相性が良く、その他のモデルについてもリソース導出は可能であった。 **WebサービスとWebAPIを分けて考えないことが最も重要であるといえる。** ###### tags: `読書`