書き込み可能なWebサービスの難しさ
考えなければならないユーザからの書き込み処理があるWebサービスたくさんある。
書き込み可能な郵便番号サービスの設計
前章郵便番号検索サービスに書き込み機能追加:
- 郵便番号リソースの作成
- 郵便番号リソースの更新
- 郵便番号リソースの削除
- バッチ処理
- トランザクション
- 排他制御
リソースの作成
方法 | 説明 |
---|---|
ファクトリリソースによる作成 | ファクトリリソース:リソースの作成するための特別なリソース ファクトリリソースはWebサビースにとって予め用意しておき、POSTで新しいリソースを作成 |
PUTによる作成 | 新しく作成したいリソースのURI(決めている場合のみ)にリクエストを直接送る。 クライアントはすでにURIを知っているので、レスポンスメッセージにLocationヘッダはない |
PUTによる作成の利点と欠点
利点 | 欠点 |
---|---|
POSTをサポートする必要ないため、サーバ側の実装が簡単 | クライアントがURI構造を予め知って置かなければならない |
クライアントが作成と更新を区別しなくて良くて、クライアント側の実装が簡単 | リクエストの見た目では、その操作は作成か更新かの区別がつかなくなる |
リソースの更新
方法 | 説明 |
---|---|
バルクアップデート | PUTの最も基本的な使い方:更新したいリソース全体をそのままメッセージボディに入れる。 クライアントの実装簡単、送受信データ大きい |
パーシャルアップデート |
更新したい部分だけを送信。 送受信データ少ない。 GETしたリソースの一部を修正してそのままPUTできない。サーバでパーシャルアップデートをサポートする場合、バルクアップデートもサポートする |
更新できないプロパティを更新しようとした場合
1.変えないプロパティ:400 Bad Requestを返して、そのプロパティは更新できないことをクライアントに伝える
2.自動的更新するプロパティ:リクエスト無視して200 OKを返す
リソースの削除
削除したいリソースのURIにDELETEを送り、リソース削除します。
親リソースの削除に伴って子リソースを削除するべきです。
バッチ処理
大量のリソースを作成や更新する場合、一括で送信(バッチ処理)できるようにWebサービスを実装する
バッチ処理のリクエスト
一括でPOSTする
バッチ処理のレスポンス
207 Multi-Statusーー複数の結果を表現する
複数の<D:status>
を使って、バッチ処理の一つ一つに対してステータスコードをぞれぞれ返す。
独自の複数ステータスフォーマット
バッチ処理の一つ一つに対してstatusとdescriptionというメンバを持ったオブジェクトの配列をJSONで返してる。
JSON採用するほうがいい
トランザクション
2つとも処理が成功するか、失敗した場合は2つとも元の状態に戻すことを保証するのがトランザクション。
トランザクションリソース
トランザクション情報を表現するリソースです。
トランザクションの開始
トランザクションリソスを作成してトランザクションを開始するにはフアクトリリソースにPOSTを送信します。
POST /transactions HTTP/1.1
Host:zipricollab.jp
トランザクションリソースの作成が成功すると、201 Created と共に生成したリソースのURIがLocationッダに入って返ってきます。
HTTP/1.1 201 Created
Location:http://zipricollabjp/transactions/308
トランザクションリソースへの処理対象の追加
POSTのボディやクエリパラメータにURIを指定してもよいのですが、ここでは簡単のためにトランザクションリソース(/transactions/308)の下に削除したいURIのパス(/1など)を直接追加して、PUT でリソースを生成することにします。
同様に/2、/3も追加します。ここまでか完了すると、/transactions/308というトランザクションリソースに/1、/2、/3の3つのリソースが関連付けられたことになります。
トランザクションの実行
トランザクションリソースにPUTでコミットを伝えるデータを書き込むことで、コミットを表現することにします。簡単のために application/x-www-form-urlencoded形式でcommit=trueという記法を用いています。
トランザクションリソースの削除
すべてが成功した場合は、トランザクションリソ-スを削除
リクエスト
DELETE /transactions/308 HTTP/1.1
Host:zip.ricollab.jp
レスポンス
HTTP/1.1 200 0K
これで、このトランザクションは完了です。http://zip.ricollab.jp/1、/2、/3
すべてのリソースを削除しました。トランザクションに関連したほかのリソース(http://zip.ricollab jp/transactions/308/*
)も同時に削除するようにサーバを実装します。
排他制御
排他制御とは、複数のクライアントが同時に1つのリソースを編集して競合(Confict)が起きないように、1つのクライアントのみが編集するように制御する処理のことを言います。
悲観ロック
悲観的ロックとは、ユーザをあまり信用せずに、競合か発生しないようにする排他制御の方法です。
LOCK/UNLOCK
WebDAVのLOCK/ UNLOCKの利用です。
ロックのリクエストにはLOCKを使います。
更新が終了したらクライアントはロックを解除します。ロックの解除は UNLOCKを利用します。UNLOCKではロックトークンをLock-Tokenダで指定します。
ロックの種類の例:
- Timeoutへッダ
- ロックがタイムアウトする時間(クライアント側の要求)を指定する。
- Authorizationヘッダロックするユーザの情報を指定する
-
<D:lockscope>
要素- このロックが排他ロック(Exclusive Lock)なのか共有ロック(Shared Lock)なのかを指定する。排他ロックは
<D:exclusive>
要素を指定し、このリソースを一人のユーザのみ編集できるようにする。共有ロックは要素を指定し、自分がこのリソ-スを編集しようとしている旨を表明して競合を起きにくくする
- このロックが排他ロック(Exclusive Lock)なのか共有ロック(Shared Lock)なのかを指定する。排他ロックは
-
<D:locktype>
要素- ロックの種類を指定する。WebDAVでは書き込みロック(Write Lock)を指定する
<D:write>
要素のみを定義しているロックが成功すると200 0Kとともに<D:prop>
要素が返ってきます。
- ロックの種類を指定する。WebDAVでは書き込みロック(Write Lock)を指定する
ロックリソースの導入
ロックを表現する子リソースを新しく導入して、ロックを実現する
まずはロックリソースを作成し、ロックの種類とタイムアウトだけを指定していてロックしたいリソースのURIに対してPOSTでロック情報を送っています。
ロック中のリソースに、ほかのユーザかロックをかけようとしたときに返すステータスコードには、423 Lockedをお勧めします。
ロックの解除は、タイムアウトまたはロックリソースの削除で実現します。
楽観的ロック
常に同じ文書を複数人が編集し続けることはあまりないという経験則から、通常の編集では文書をロックせずに、競合が起きたときに対処するしくみを「楽観的ロック」と言います。
条件付きPUT
条件付き PUTのやりとりでは、ETagの値を得るためにまずリソースを GETで取得します。
次に、得られたETagの値を使って条件付きPUTを実行します。リソースが変更されていなければETagの値は変わりませんので、リソースの更新は成功します。
条件付きDELETE
条件付きDELETEを利用すると、他人が修正したのを知らずにリソースを削除するミスを防止できます。条件付き DELETEの場合もETagの値や更新日時が必要です。それらの值を使って、条件付きPUTと同様にIf-Match またはIf-Unmodified-Sinceツダを用いてメソッドを条件付きにします。
条件付きGET/PUT/DELETEとへッダの対応
メソッド | Last-Modified | ETag |
---|---|---|
GET | lf-Modified-Since | lf-None-Match |
PUT/DELETE | If-Unmodified-Since | lf-Match |
412 Precondition Failedーー条件が合わない
条件付きPUTや条件付きDELETEを発行した際にリソースが変更されていた場合は、412 Precondition Failedが返ります。
412 Precondition Failed発生した場合の対処:
- 競合を起こしたユーザに確認をしたうえで、更新または削除
- 競合を起こしたデータを競合リソースとして別リソースに保存する(PUTのみ)
- 競合を起こしたユーザに変更点を伝え、マージを促す(PUTのみ)
設計のバランス
- なるべくシンプルに保つ
- 困ったらリソースに戻って考える
- 本当に必要ならPOSTで何でもできる