Azure Cosmos DB の部分更新をJava SDKから試す



Cosmos DB の部分更新（いわゆるPatch）が Private Preview ですがサポートされました。

[Partial document update for Azure Cosmos DB in private preview | Azure updates | Microsoft Azure](https://azure.microsoft.com/en-us/updates/partial-document-update-for-azure-cosmos-db-in-private-preview/)

申し込んでは見ましたが、使える気配がないので、エミュレータでサポートされていると聞いて試してみました。とはいえデフォルトのままでは機能が有効になっていないので、`/EnablePreview` オプションを付けて起動します。

```
 .\CosmosDB.Emulator.exe /EnablePreview
```

## 対応SDK 
GitHubのソースをみると、数ヶ月前からIssueがたっていたり、色々と動きがあったようです。執筆時点で最新の `4.15.0` は 更新操作をサポートしています。

```xml
<dependency>
    <groupId>com.azure</groupId>
    <artifactId>azure-cosmos</artifactId>
    <version>4.15.0</version>
</dependency>
```
いまのところ Java以外では、.NET SDK が対応しているのみです。

## サポートする操作

サポートする操作は以下の通りで、

* 追加 (Add)
* 削除 (Remove)
* 置換 (Replace)
* 設定 (Set)
* プラスマイナスのインクリメント (Increment)

単一アイテムの更新はもちろんできますが、、条件付や、複数アイテムなどもできます。

操作について少し補足すると

* 追加は、Upsert のように機能する
* 更新は、更新しようとする要素が存在しない場合は失敗する
* 追加と設定は配列以外の時は、同じように機能するが、配列の時はちょっと違う（後述）

ざっくり言うと、Json Patch RFCのセマンティクスに従っているようです。

## 使ってみる

適当なJSONをエミュレータに設定しておき、

```json
{
    "id": "b1dd9d5e-f334-4b2b-9165-4ff9e508e469",
    "name": "statemachine",
    "age": 20,
}
```

以下のコードで更新します

```java
CosmosPatchOperations ops = CosmosPatchOperations.create();
        ops.add("/new_element", "パッチで追加");
        ops.replace("/name", "patched statemachine");
        ops.increment("/age", 1);
        ops.set("/ikemen", true);

        CosmosItemResponse<JsonNode> response = container.patchItem(
            id,
            partitionKey,
            ops,
            JsonNode.class)
            .block();

        System.out.println(response.getItem().toPrettyString());
```

`CosmosPatchOperations` インスタンスを作成して、`add` , `replace`, `increment` などの更新操作を追加していきます。最後に、`patchItem`メソッドで更新操作します。JsonNode.classを指定しておくと、POJO定義しなくても適当にレスポンスのインスタンスが取得できて楽ちんです。**が、後で説明する設定が必要です。**

```json
{
    "name": "patched statemachine",
    "age": 21,
    "id": "b1dd9d5e-f334-4b2b-9165-4ff9e508e469",
    "new_element": "パッチで追加",
    "ikemen": true,
}
```

## Set と Replaceの違い

前述したとおり Set と Replace は、配列の時に違う動きをするようです。このような配列に対して、

```json
    "array": [
        1,
        2,
        3
    ],
```

`add` を行うと

```java
  ops.add("/array/0", 0);
```

先頭（指定したインデックス）に追加されます。

```json
    "array": [
        0,
        1,
        2,
        3
    ],
```

上の入力にたいして、`ops.set("/array/0", 99)`を行うと指定したインデックスが設定されます。

```json
    "array": [
        99,
        1,
        2,
        3
    ],
```

予測通りの挙動でしょうか。単一要素の場合はあまり気にしなくてもよいとは思いますが、配列の場合は注意して操作する必要がありそうです。もうちょっと複雑な配列とスキーマをもっていると、更新操作をするのも楽じゃない気がします。

## 補足

Java SDK の場合、書き込み系の操作のとき、デフォルトでコンテンツのレスポンスを返してくれません。久しく忘れていてはまりました。

クライアントをビルドするときに`contentResponseOnWriteEnabled` で設定するか、個々の操作でもオプションを指定可能ですので、ご注意を。

```java

        var client = new CosmosClientBuilder()
            .endpoint(endpoint)
            .key(key)
            .gatewayMode(new GatewayConnectionConfig())
            .contentResponseOnWriteEnabled(true)
            .buildAsyncClient();
```

## まとめ

更新操作はJSONのパスを指定する方法なので、POJOクラスを活用できないのは残念です。更新の性質上しょうがないのかもしれませんが、なにか方法があれば便利そうな気もするし、メンドウな気もします（あまり深くは考えてません）

とはいえ、いままで出来なかった更新操作がようやくできるようになったので、活用方法は色々あるかとは思います。

以下のGithubにフィードバックグループがあるので、一言言いたい人は是非。

https://github.com/AzureCosmosDB/PatchPrivatePreviewFeedbackGroup