前回は、郵便番号検索サービスの設計を通して、読み取り専用Webサービスの設計についてまとめた。ここでは、郵便番号検索サービスの設計を通して、ブログやSNSなど書き込み可能なWebサービスの設計について説明する。
1 書き込み可能なWebサービスの難しさ
ブログやSNSなど書き込み可能なWebサービスは、読み取り専用のWebサービスに比べて考慮すべきことがたくさんある。例えば、複数のユーザが同時に書き込みを行ったらどうなるのか、複数の処理手順を必ず実行するにはどうしたらよいのかなどである。
2 書き込み可能なWebサービスの設計
前回作成した読み取り専用の郵便番号検索サービスに、以下の6つの書き込み機能を追加する。
- 郵便番号リソースの作成
- 郵便番号リソースの更新
- 郵便番号リソースの削除
- バッチ処理
- トランザクション
- 排他制御
3 リソースの作成
新しい郵便番号を追加する方法は2つある。ファクトリリソース(Factory resource)へPOSTする方法で、もう1つはPUTで直接作成する方法である。
ファクトリリソースによる作成
ファクトリリソースとは、リソースを作成するための特別なリソースである。ファクトリリソース自身はWebサービスによってあらかじめ用意しておき、POSTで新しいリソースを作成する。
PUTによる作成
郵便番号によってリソースのURI(パス)があらかじめ決まっているため、郵便番号リソースはPUTで作成できる。PUTで実装する際の利点は、サーバ側とクライアント側の実装が簡単になることである。その反面、クライアントがURI構造をあらかじめ知っておかなければならず、リクエストの見た目上はその操作が作成なのか更新なのかの区別がつかなくなる。
4 リソースの更新
リソースの更新は基本的にPUTで行う。後述のバッチ更新はPOSTで行う。
バルクアップデート
バルクアップデート(Bulk Update)とは、リソース全体を送信する更新方法のこと。バルクアップデートはクライアントの実装が簡単になる反面、送受信するデータが大きくなるという欠点がある。
パーシャルアップデート
パーシャルアップデート(Partial Update)とは、リソースの一部分だけを送信する更新方法のこと。パーシャルアップデートは送受信するデータが少なくなる反面、GETしたリソースの一部を修正してそのままPUTするという使い方ができなくなる。
更新できないプロパティを更新しようとした場合
郵便番号を更新しようとした場合は400 Bad Requestを返して、そのプロパティは更新できないことをクライアントに伝えるのがよい。一方、例えば作成日時や更新日時などサーバ側で自動的に値を更新するプロパティの場合は、200 OKを返すのがよい。
5 リソースの削除
リソースの削除は、削除したいリソースのURIにDELETEを送ればよい。一般的に、親リソースに従属する子リソースは、親リソースの削除に伴って削除するべきである。
6 バッチ処理
バッチ処理とは、作成・更新したいリソースを一括で送信することである。大量の郵便番号を作成したり更新したりする場合などに用いる。
バッチ処理のリクエスト
リソースの更新にもかかわらずPOSTを使っているのは、PUTだと更新対象のリソースをURIで指定しなければならないからである。
バッチ処理のレスポンス
バッチ処理中にエラーが起きた場合の設計方針は、大きく分けて以下の2つがある。1つはバッチ処理をトランザクション(Transaction)化して、途中で失敗した場合は何も処理しないことをWebサービスの内部で保証することである。もう1つはどのリソースへの処理が成功してどのリソースへの処理が失敗したのかをクライアントに伝えることである。伝え方には、207 Multi-StatusとWebDAVの<D:multistatus>要素を組み合わせる方法と、200 OKと独自のフォーマットを組み合わせる方法の2つがある。
207 Multi-StatusはHTTP 1.1の拡張であるWebDAVが定義しているステータスコードで、複数の結果を表現するものである。また、200 OKと<D:multistatus>要素に相当する独自のXMLやJSONを返してもよい。
7 トランザクション
トランザクションとは、複数のリソースにまたがった変更をひとまとまりに扱うことで、複数の処理のすべてが成功するか、失敗した場合はすべて元の状態に戻すことを保証することである。
解決すべき問題
例えば3つの郵便番号を一括削除する際に、3つのリソースをまとめて削除するか削除するか、どれも削除しないように保証する設計を考える。
トランザクションリソース
トランザクションリソースとは、トランザクション情報を表現するリソースである。HTTPの統一インタフェースで実現できない処理に遭遇したときは、新たなリソースを導入して解決を図る。
- トランザクションの開始:ファクトリリソースにPOSTを送信する
- トランザクションリソースへの処理対象の追加:トランザクションリソースの下に削除したいURIのパスを直接追加して、PUTでリソースを生成する
- トランザクションの実行:application/x-www-form-urlencoded形式でcommit=trueという記法など
- トランザクションリソースの削除
トランザクションリソース以外の解決方法
- バッチ処理のトランザクション化:バッチ処理を拡張する
- 上位リソースに対する操作:上位リソースを操作して下位リソースを一括操作する
8 排他制御
排他制御(Mutual Exclusion)とは、複数のクライアントが同時に1つのリソースを編集して競合(Conflict)が起きないように、1つのクライアントのみが編集するように制御する処理のことである。排他制御の方法は大きく分けて2つある。1つは悲観的ロック(Pessimistic Lock)で、もう1つは楽観的ロック(Optimistic Lock)である。
解決すべき問題
2つのクライアントが1つのリソースを編集しようとしているとする。このとき、クライアントAとクライアントBが同時にリソースを更新すると競合が発生する。その際、悲観的ロックと楽観的ロックで解決するにはどうすればよいか。
悲観的ロック
悲観的ロックとは、ユーザをあまり信用せずに、競合が発生しないようにする排他制御の方法である。HTTPで悲観的ロックを実現するには、WebDAVのLOCK/UNLOCKメソッドを使う方法と、独自のロックリソースを使う方法がある。
- LOCK/UNLOCK:ロックのリクエストにはLOCKを使う(Timeoutヘッダ、Authorizationヘッダ、<D:lockscope>要素、<D:locktype>要素)。LOCKの結果(<D:activelock>要素、<D:lockscope>要素と<D:locktype>要素、<D:depth>要素、<D:owner>要素、<D:timeout>要素、<D:locktoken>要素)
- ロックリソースの導入:LOCK相当の機能をWebサービスに組み込む
楽観的ロック
楽観的ロックとは、文書をロックせずに競合が起きたときに対処するしくみのこと。HTTPで楽観的ロックを実装するには、条件付きPUTと条件付きDELETEを利用する。
- 条件付きPUT:自分が更新しようとしているリソースに変更がないかどうかを確認するしくみ。Last-ModifiedかETagの値を使う。「If-」ヘッダが異なる
- 条件付きDELETE:他人が修正したのを知らずにリソースを削除するミスを防止できる
- 412 Precondition Failed:条件が合わない場合に返るステータスコード。対処方法は以下の3つ。1)競合を起こしたユーザに確認をした上で更新または削除する。2)競合を起こしたデータを競合リソースとして別リソースに保存する(PUTの場合のみ)。3)競合を起こしたユーザに変更点を伝え、マージを促す(PUTの場合のみ)
9 設計のバランス
設計はバランスをとる作業である。各種のパラメータを加味して、自分が最もバランスがとれていると感じるところに落ち着かせるのが設計作業である。筆者はその指針として、以下の3つの項目を挙げている。
- なるべくシンプルに保つ
- 困ったらリソースに戻って考える
- 本当に必要ならPOSTで何でもできる
最後に
リソースの作成・更新・削除、バッチ処理、トランザクションそして排他制御といった機能を追加するための手法について解説した。1つの問題に対して複数の解決方法が存在し、その採用と調整は難しい。そのため、Webサービス設計においては、シンプルさ、リソースに戻る、POSTを活用するという3つの指針を覚えておくことをおすすめする。
次回は、WebサービスとWeb APIを相互活用するといったリソース設計の方法についてまとめる。
![]() |