ヘッダーを使用した条件付き操作

このセクションでは、Spring Data REST がどのように標準の HTTP ヘッダーを使用してパフォーマンスを向上させ、操作を条件付けし、より洗練されたフロントエンドに貢献するかを示します。

ETagIf-MatchIf-None-Match ヘッダー

ETag ヘッダー [IETF] (英語) は、リソースにタグを付ける方法を提供します。これにより、クライアントが相互にオーバーライドするのを防ぐと同時に、不要な呼び出しを減らすことができます。

次の例を考えてみましょう。

例 1: バージョン番号付きの POJO
class Sample {

	@Version Long version; (1)

	Sample(Long version) {
		this.version = version;
	}
}
1@Version アノテーション(Spring Data JPA を使用している場合は JPA アノテーション、他のすべてのモジュールでは Spring Data org.springframework.data.annotation.Version アノテーション)は、このフィールドにバージョンマーカーとしてフラグを立てます。

前の例の POJO は、Spring Data REST によって REST リソースとして提供される場合、バージョンフィールドの値を含む ETag ヘッダーを持ちます。

次のような If-Match ヘッダーを指定すると、そのリソースを条件付きで PUTPATCHDELETE できます。

curl -v -X PATCH -H 'If-Match: <value of previous ETag>' ...

リソースの現在の ETag 状態が If-Match ヘッダーと一致する場合にのみ、操作が実行されます。このセーフガードは、クライアントがお互いを踏みつけるのを防ぎます。2 つの異なるクライアントがリソースをフェッチし、同一の ETag を持つことができます。1 つのクライアントがリソースを更新すると、レスポンスで新しい ETag を取得します。しかし、最初のクライアントにはまだ古いヘッダーがあります。そのクライアントが If-Match ヘッダーを使用して更新を試みた場合、それらが一致しなくなったため、更新は失敗します。代わりに、そのクライアントは HTTP 412 Precondition Failed メッセージを受信して送り返します。その後、クライアントは追いつくことができますが、必要です。

「バージョン」という用語は、データストアが異なる場合のセマンティクスや、アプリケーション内のセマンティクスが異なる場合もあります。Spring Data REST は、データストアのメタモデルに効果的に委譲して、フィールドがバージョン管理されているかどうかを識別し、バージョン管理されている場合は、ETag 要素が一致する場合にのみリストされた更新を許可します。

If-None-Match ヘッダー [IETF] (英語) は代替手段を提供します。条件付き更新の代わりに、If-None-Match は条件付きクエリを許可します。次の例を考えてみましょう。

curl -v -H 'If-None-Match: <value of previous etag>' ...

上記のコマンド(デフォルト)は GET を実行します。Spring Data REST は、GET の実行中に If-None-Match ヘッダーをチェックします。ヘッダーが ETag と一致する場合、何も変更されていないと結論付け、リソースのコピーを送信する代わりに、HTTP 304 Not Modified ステータスコードを送り返します。意味的には、「この提供されたヘッダー値がサーバー側のバージョンと一致しない場合は、リソース全体を送信してください。それ以外の場合は、何も送信しないでください。」

この POJO は ETag ベースの単体テストからのものであるため、アプリケーションコードで期待されているように、@Entity (JPA)または @Document (MongoDB)アノテーションはありません。これは、@Version を含むフィールドがどのように ETag ヘッダーになるかにのみ焦点を当てています。

If-Modified-Since ヘッダー

If-Modified-Since ヘッダー [IETF] (英語) は、最後のリクエスト以降にリソースが更新されたかどうかを確認する方法を提供します。これにより、アプリケーションは同じデータを再送信する必要がなくなります。次の例を考えてみましょう。

例 2: ドメイン型でキャプチャーされた最終変更日
@Document
public class Receipt {

	public @Id String id;
	public @Version Long version;
	public @LastModifiedDate Date date;  (1)

	public String saleItem;
	public BigDecimal amount;

}
1Spring Data Commons の @LastModifiedDate アノテーションを使用すると、この情報を複数の形式(JodaTime の DateTime、レガシー Java Date および Calendar、JDK8 日付 / 時刻型、long/Long)でキャプチャーできます。

前の例の日付フィールドを使用すると、Spring Data REST は次のような Last-Modified ヘッダーを返します。

Last-Modified: Wed, 24 Jun 2015 20:28:15 GMT

次の例に示すように、この値をキャプチャーして後続のクエリに使用すると、更新されていないときに同じデータが 2 回フェッチされるのを防ぐことができます。

curl -H "If-Modified-Since: Wed, 24 Jun 2015 20:28:15 GMT" ...

上記のコマンドでは、指定された時間以降にリソースが変更された場合にのみ、リソースをフェッチするように要求しています。その場合、クライアントを更新するための改訂された Last-Modified ヘッダーを取得します。そうでない場合は、HTTP 304 Not Modified ステータスコードを受け取ります。

ヘッダーは、将来のクエリのために送り返すために完全にフォーマットされています。

ヘッダー値を異なるクエリと組み合わせて一致させないでください。結果は悲惨なものになる可能性があります。ヘッダー値は、まったく同じ URI とパラメーターをリクエストする場合にのみ使用してください。

より効率的なフロントエンドの設計

ETag 要素を If-Match および If-None-Match ヘッダーと組み合わせることで、コンシューマーのデータプランとモバイルバッテリーの寿命により適したフロントエンドを構築できます。そうするには:

  1. ロックが必要なエンティティを特定し、バージョン属性を追加します。

    HTML5 は data-* 属性を適切にサポートしているため、バージョンを DOM に保存します(data-etag 属性など)。

  2. 最新の更新を追跡することでメリットが得られるエントリを特定します。これらのリソースをフェッチするときは、Last-Modified 値を DOM(おそらく data-last-modified)に格納します。

  3. リソースをフェッチするときは、リソースに簡単に戻れるように、DOM ノード(おそらく data-uri または data-self)にも self URI を埋め込みます。

  4. If-Match を使用し、HTTP 412 Precondition Failed ステータスコードも処理するように PUT/PATCH/DELETE 操作を調整します。

  5. If-None-Match および If-Modified-Since を使用し、HTTP 304 Not Modified ステータスコードを処理するように GET 操作を調整します。

ETag 要素と Last-Modified 値を DOM(またはネイティブモバイルアプリの場合は他の場所)に埋め込むことで、同じものを何度も取得しないことで、データとバッテリー電力の消費を減らすことができます。また、他のクライアントとの衝突を回避し、代わりに、違いを調整する必要があるときにアラートを受け取ることもできます。

このように、フロントエンドを少し調整し、エンティティレベルで編集するだけで、バックエンドは、顧客フレンドリーなクライアントを構築するときに利用できる時間に敏感な詳細を提供します。