1. 序章

利用可能なさまざまなHTTPメソッドの中で、HTTPPATCHメソッドは独自の役割を果たします。 これにより、HTTPリソースに部分的な更新を適用できます。

このチュートリアルでは、HTTP PATCHメソッドとJSONパッチドキュメント形式を使用して、RESTfulリソースに部分的な更新を適用する方法を見ていきます。

2. ユースケース

JSONドキュメントで表されるHTTPCustomerリソースの例を検討することから始めましょう。

{ 
    "id":"1",
    "telephone":"001-555-1234",
    "favorites":["Milk","Eggs"],
    "communicationPreferences": {"post":true, "email":true}
}

この顧客の電話番号が変更され、顧客がお気に入りの製品のリストに新しいアイテムを追加したと仮定します。 つまり、Customertelephoneフィールドとfavoritesフィールドのみを更新する必要があります。

どうすればいいですか?

人気のあるHTTPPUTメソッドが最初に思い浮かびます。 ただし、PUTはリソースを完全に置き換えるため、部分的な更新をエレガントに適用するのに適した方法ではありません。 さらに、クライアントは、更新が適用されて保存される前にGETを実行する必要があります。

ここでHTTPPATCHメソッドが役に立ちます。

HTTPPATCHメソッドとJSONパッチ形式を理解しましょう。

3. HTTPPATCHメソッドとJSONパッチ形式

HTTP PATCHメソッドは、リソースに部分的な更新を適用するための優れた方法を提供します。 その結果、クライアントはリクエストの違いのみを送信する必要があります。

HTTPPATCHリクエストの簡単な例を見てみましょう。

PATCH /customers/1234 HTTP/1.1
Host: www.example.com
Content-Type: application/example
If-Match: "e0023aa4e"
Content-Length: 100

[description of changes]

HTTP PATCHリクエスト本文は、新しいバージョンを生成するためにターゲットリソースを変更する方法を説明します。 さらに、を表すために使用される形式 【変更内容】 リソースの種類によって異なります。 JSONリソースタイプの場合、変更を説明するために使用される形式はJSONパッチです。

簡単に言うと、JSONパッチ形式では、「一連の操作」を使用して、ターゲットリソースを変更する方法を記述します。 JSONパッチドキュメントは、JSONオブジェクトの配列です。 配列内の各オブジェクトは、1つのJSONパッチ操作を表します。

次に、JSONパッチの操作といくつかの例を見てみましょう。

4. JSONパッチ操作

JSONパッチ操作は、単一のopオブジェクトで表されます。

たとえば、ここでは、顧客の電話番号を更新するためのJSONパッチ操作を定義しています。

{
    "op":"replace",
    "path":"/telephone",
    "value":"001-555-5678"
}

各操作には、1つのpathメンバーが必要です。 また、一部の操作オブジェクトには、 からメンバーも。 パスとメンバーからの値はJSONポインターです。 ターゲットドキュメント内の場所を指します。 この場所は、ターゲットオブジェクト内の特定のキーまたは配列要素を指すことができます。

ここで、利用可能なJSONパッチ操作を簡単に見てみましょう。

4.1. add操作

add 操作を使用して、オブジェクトに新しいメンバーを追加します。 また、これを使用して既存のメンバーを更新し、指定されたインデックスの配列に新しい値を挿入することもできます。

たとえば、インデックス0で顧客のお気に入りリストに「パン」を追加しましょう。

{
    "op":"add",
    "path":"/favorites/0",
    "value":"Bread"
}

add操作後に変更された顧客の詳細は次のようになります。

{
    "id":"1",
    "telephone":"001-555-1234",
    "favorites":["Bread","Milk","Eggs"],
    "communicationPreferences": {"post":true, "email":true}
}

4.2. 削除操作

remove 操作は、ターゲット位置の値を削除します。 さらに、指定されたインデックスの配列から要素を削除できます。

たとえば、お客様のcommunicationPreferencesを削除しましょう。

{
    "op":"remove",
    "path":"/communicationPreferences"
}

remove操作後に変更された顧客の詳細は次のようになります。

{
    "id":"1",
    "telephone":"001-555-1234",
    "favorites":["Bread","Milk","Eggs"],
    "communicationPreferences":null
}

4.3. 置換操作

replace 操作は、ターゲット位置の値を新しい値で更新します。

例として、お客様の電話番号を更新してみましょう。

{
    "op":"replace",
    "path":"/telephone",
    "value":"001-555-5678"
}

replace操作後に変更された顧客の詳細は次のようになります。

{ 
    "id":"1", 
    "telephone":"001-555-5678", 
    "favorites":["Bread","Milk","Eggs"], 
    "communicationPreferences":null
}

4.4. move操作

move 操作は、指定された場所の値を削除し、それをターゲットの場所に追加します。

たとえば、「パン」を顧客のお気に入りリストの一番上からリストの一番下に移動してみましょう。

{
    "op":"move",
    "from":"/favorites/0",
    "path":"/favorites/-"
}

move操作後に変更された顧客の詳細は次のようになります。

{ 
    "id":"1", 
    "telephone":"001-555-5678", 
    "favorites":["Milk","Eggs","Bread"], 
    "communicationPreferences":null
}

上記の例の/favorites /0および/favorites /-は、favorites配列の開始インデックスと終了インデックスへのJSONポインターです。

4.5. コピー操作

copy 操作は、指定された場所の値をターゲットの場所にコピーします。

たとえば、favoritesリストの「Milk」を複製してみましょう。

{
    "op":"copy",
    "from":"/favorites/0",
    "path":"/favorites/-"
}

copy操作後に変更された顧客の詳細は次のようになります。

{ 
    "id":"1", 
    "telephone":"001-555-5678", 
    "favorites":["Milk","Eggs","Bread","Milk"], 
    "communicationPreferences":null
}

4.6. テスト操作

test 操作は、「パス」の値が「値」と等しいことをテストします。 PATCH操作はアトミックであるため、いずれかの操作が失敗した場合はPATCHを破棄する必要があります。 test 操作を使用して、前提条件と事後条件が満たされていることを検証できます。

たとえば、顧客の電話フィールドの更新が成功したことをテストしてみましょう。

{
    "op":"test", 
    "path":"/telephone",
    "value":"001-555-5678"
}

上記の概念を例に適用する方法を見てみましょう。

5. JSONパッチ形式を使用したHTTPPATCHリクエスト

Customerのユースケースを再検討します。

JSONパッチ形式を使用して顧客の電話およびお気に入りリストの部分的な更新を実行するためのHTTPPATCHリクエストは次のとおりです。

curl -i -X PATCH http://localhost:8080/customers/1 -H "Content-Type: application/json-patch+json" -d '[
    {"op":"replace","path":"/telephone","value":"+1-555-56"},
    {"op":"add","path":"/favorites/0","value":"Bread"}
]'

最も重要なのは、JSONパッチリクエストのContent-Typeapplication/ json-patch +jsonであるということです。 また、リクエストの本文はJSONパッチ操作オブジェクトの配列です。

[
    {"op":"replace","path":"/telephone","value":"+1-555-56"},
    {"op":"add","path":"/favorites/0","value":"Bread"}
]

サーバー側でこのようなリクエストをどのように処理しますか?

1つの方法は、操作を順番に評価し、それらをアトミックユニットとしてターゲットリソースに適用するカスタムフレームワークを作成することです。  明らかに、このアプローチは複雑に聞こえます。 また、パッチドキュメントを消費する標準化されていない方法につながる可能性があります。

幸い、JSONパッチリクエストの処理を手作りする必要はありません。

もともとJSR353で定義されたJSONProcessing1.0またはJSON-P1.0のJavaAPIは、 JSON374でJSONパッチのサポートを導入しました。 JSON-P APIは、JSONパッチの実装を表すJsonPatchタイプを提供します。

ただし、JSON-PはAPIにすぎません。 JSON-P APIを使用するには、JSON-PAPIを実装するライブラリを使用する必要があります。 この記事の例では、json-patchというライブラリを使用します。

次に、上記のJSONパッチ形式を使用してHTTPPATCHリクエストを使用するRESTサービスを構築する方法を見てみましょう。

6. SpringBootアプリケーションでのJSONパッチの実装

6.1. 依存関係

json-patch の最新バージョンは、MavenCentralリポジトリーから見つけることができます。

まず、依存関係をpom.xmlに追加しましょう。

<dependency>
    <groupId>com.github.java-json-tools</groupId>
    <artifactId>json-patch</artifactId>
    <version>1.12</version>
</dependency>

それでは、 CustomerJSONドキュメントを表すスキーマクラスを定義しましょう。

public class Customer {
    private String id;
    private String telephone;
    private List<String> favorites;
    private Map<String, Boolean> communicationPreferences;

    // standard getters and setters
}

次に、コントローラーメソッドを見ていきます。

6.2. RESTコントローラーメソッド

次に、お客様のユースケースにHTTPPATCHを実装できます。

@PatchMapping(path = "/{id}", consumes = "application/json-patch+json")
public ResponseEntity<Customer> updateCustomer(@PathVariable String id, @RequestBody JsonPatch patch) {
    try {
        Customer customer = customerService.findCustomer(id).orElseThrow(CustomerNotFoundException::new);
        Customer customerPatched = applyPatchToCustomer(patch, customer);
        customerService.updateCustomer(customerPatched);
        return ResponseEntity.ok(customerPatched);
    } catch (JsonPatchException | JsonProcessingException e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    } catch (CustomerNotFoundException e) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
    }
}

このメソッドで何が起こっているのかを理解しましょう。

  • まず、 @PatchMapping アノテーションを使用して、メソッドをPATCHハンドラーメソッドとしてマークします。
  • application / json-patch + json 「Content-Type」を使用したパッチリクエストが到着すると、SpringBootはデフォルトのMappingJackson2HttpMessageConverterを使用してリクエストペイロードをJsonPatch[に変換します。 X214X]インスタンス。 その結果、コントローラーメソッドはリクエスト本文をJsonPatchインスタンスとして受け取ります。

メソッド内:

  1. まず、 customerService.findCustomer(id)メソッドを呼び出して、顧客レコードを検索します
  2. その後、顧客レコードが見つかった場合は、 applyPatchToCustomer(patch、customer)メソッドを呼び出します。 これにより、 JsonPatch が顧客に適用されます(これについては後で詳しく説明します)
  3. 次に、 customerService.updateCustomer(customerPatched)を呼び出して、顧客レコードを保存します
  4. 最後に、 200 OK 応答をクライアントに返し、応答にパッチを適用したCustomerの詳細を含めます。

最も重要なことは、本当の魔法は applyPatchToCustomer(patch、customer)メソッドで発生することです。

private Customer applyPatchToCustomer(
  JsonPatch patch, Customer targetCustomer) throws JsonPatchException, JsonProcessingException {
    JsonNode patched = patch.apply(objectMapper.convertValue(targetCustomer, JsonNode.class));
    return objectMapper.treeToValue(patched, Customer.class);
}
  1. まず、ターゲットCustomerに適用される操作のリストを保持するJsonPatchインスタンスがあります。
  2. 次に、ターゲットCustomercom.fasterxml.jackson.databind.JsonNodeのインスタンスに変換し、それをJsonPatch.applyメソッドに渡してパッチを適用します。 。 舞台裏では、JsonPatch.applyはターゲットへの操作の適用を扱います。 パッチの結果は、com.fasterxml.jackson.databind.JsonNodeインスタンスでもあります。
  3. 次に、 objectMapper.treeToValue メソッドを呼び出します。このメソッドは、パッチが適用されたcom.fasterxml.jackson.databind.JsonNodeのデータをCustomerタイプにバインドします。 これは、パッチが適用されたCustomerインスタンスです。
  4. 最後に、パッチを適用したCustomerインスタンスを返します

APIに対していくつかのテストを実行してみましょう。

6.3. テスト

まず、APIへのPOSTリクエストを使用して顧客を作成しましょう。

curl -i -X POST http://localhost:8080/customers -H "Content-Type: application/json" 
  -d '{"telephone":"+1-555-12","favorites":["Milk","Eggs"],"communicationPreferences":{"post":true,"email":true}}'

201Created応答を受け取ります。

HTTP/1.1 201
Location: http://localhost:8080/customers/1

Location 応答ヘッダーは、新しいリソースの場所に設定されます。 これは、新しいCustomeridが1であることを示しています。

次に、PATCHリクエストを使用して、この顧客に部分的な更新をリクエストしましょう。

curl -i -X PATCH http://localhost:8080/customers/1 -H "Content-Type: application/json-patch+json" -d '[
    {"op":"replace","path":"/telephone","value":"+1-555-56"}, 
    {"op":"add","path":"/favorites/0","value": "Bread"}
]'

パッチが適用された顧客の詳細を含む200 OK応答を受け取ります。

HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 14 Feb 2020 21:23:14 GMT

{"id":"1","telephone":"+1-555-56","favorites":["Bread","Milk","Eggs"],"communicationPreferences":{"post":true,"email":true}}

7. 結論

この記事では、SpringRESTAPIにJSONパッチを実装する方法について説明しました。

まず、HTTPPATCHメソッドとその部分的な更新を実行する機能について説明しました。

次に、JSONパッチとは何かを調べ、さまざまなJSONパッチの操作を理解しました。

最後に、json-patchライブラリを使用してSpringBootアプリケーションでHTTPPATCHリクエストを処理する方法について説明しました。

いつものように、この記事で使用されている例のソースコードは、GitHubから入手できます。