

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

# 拡張機能を使用して DynamoDB 拡張クライアントオペレーションをカスタマイズする
<a name="ddb-en-client-extensions"></a>

DynamoDB 拡張クライアント API は、マッピング操作以外の機能を提供するプラグイン拡張機能をサポートしています。拡張機能は、読み取りおよび書き込みオペレーション中に 2 つのフックメソッドを使用してデータを変更します。
+ `beforeWrite()` - 発生する前に書き込みオペレーションを変更する
+ `afterRead()` - 読み取りオペレーションの実行後にその結果を変更する

一部のオペレーション (アイテムの更新など) は書き込みと読み取りの両方を実行するため、両方のフックメソッドが呼び出されます。

## 拡張機能のロード方法
<a name="ddb-en-client-extensions-loading"></a>

拡張機能は、拡張クライアントビルダーで指定された順序でロードされます。1 つのエクステンションが以前のエクステンションによって変換された値に作用する可能性があるため、ロード順序は重要な場合があります。

デフォルトでは、拡張クライアントは 2 つの拡張機能をロードします。
+ `[VersionedRecordExtension](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.html)` - 楽観的ロックを提供する
+ `[AtomicCounterExtension](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/extensions/AtomicCounterExtension.html)` - カウンター属性を自動的に増分する

拡張クライアントビルダーでデフォルトの動作を上書きして、任意の拡張機能を読み込むことができます。デフォルトの拡張機能を使いたくない場合は、none を指定することもできます。

**重要**  
独自の拡張機能をロードしても、拡張クライアントはデフォルトの拡張機能をロードしません。いずれかのデフォルトの拡張機能と同じ動作をさせたい場合は、拡張機能のリストに明示的に追加する必要があります。

次の例は、 `VersionedRecordExtension` の後に `verifyChecksumExtension` という名前のカスタム拡張機能をロードする方法を示しています。この例では、`AtomicCounterExtension` はロードされていません。

```
DynamoDbEnhancedClientExtension versionedRecordExtension = VersionedRecordExtension.builder().build();

DynamoDbEnhancedClient enhancedClient = 
    DynamoDbEnhancedClient.builder()
                          .dynamoDbClient(dynamoDbClient)
                          .extensions(versionedRecordExtension, verifyChecksumExtension)
                          .build();
```

## 利用可能な拡張機能の詳細と設定
<a name="ddb-en-client-extensions-details"></a>

以下のセクションでは、 SDK で使用可能な各拡張機能に関する詳細情報を記載します。

### `VersionedRecordExtension` を使用して楽観的ロックを実装する
<a name="ddb-en-client-extensions-VRE"></a>

`VersionedRecordExtension` 拡張機能は、アイテムがデータベースに書き込まれるとアイテムのバージョン番号を増加して追跡することで、楽観的ロックを提供します。実際に保存されているアイテムのバージョン番号がアプリケーションが最後に読み取った値と一致しない場合、書き込みが失敗する原因となる条件が書き込みごとに追加されます。

#### 設定
<a name="ddb-en-client-extensions-VRE-conf"></a>

アイテムのバージョン番号を追跡するために使用する属性を指定するには、テーブルスキーマの数値属性にタグを付けます。

次のスニペットでは、`version` 属性がアイテムのバージョン番号を保持するように指定しています。

```
    @DynamoDbVersionAttribute
    public Integer getVersion() {...};
    public void setVersion(Integer version) {...};
```

これと同等の静的テーブルスキーマのアプローチは、次のスニペットで示されます。

```
    .addAttribute(Integer.class, a -> a.name("version")
                                       .getter(Customer::getVersion)
                                       .setter(Customer::setVersion)
                                        // Apply the 'version' tag to the attribute.
                                       .tags(VersionedRecordExtension.AttributeTags.versionAttribute())
```

#### 仕組み
<a name="ddb-en-client-extensions-VRE-how-it-works"></a>

`VersionedRecordExtension` を使用した楽観的ロックは、次の `DynamoDbEnhancedClient` および `DynamoDbTable` メソッドに対して以下のような影響があります。

**`putItem`**  
新しいアイテムには、初期バージョン値 0 が割り当てられます。これは `@DynamoDbVersionAttribute(startAt = X)` で設定できます。

**`updateItem`**  
項目を取得し、その項目の 1 つ以上のプロパティを更新して変更を保存する場合には、クライアント側とサーバー側のバージョン番号が一致する場合のみ、オペレーションが成功します。  
成功すると、バージョン番号は自動的に 1 ずつ増加します。これは `@DynamoDbVersionAttribute(incrementBy = X)` で設定できます。

**`deleteItem`**  
`DynamoDbVersionAttribute` 注釈は効力を発揮しません。アイテムを削除するときは、条件式を手動で追加する必要があります。  
次の例では、条件式を追加して、削除されたアイテムが読み取られたアイテムであることを確認します。次の例では、`recordVersion` は `@DynamoDbVersionAttribute` で注釈を付けた Bean の属性です。  

```
// 1. Read the item and get its current version.
Customer item = customerTable.getItem(Key.builder().partitionValue("someId").build());
// `recordVersion` is the bean's attribute that is annotated with `@DynamoDbVersionAttribute`.
AttributeValue currentVersion = item.getRecordVersion();

// 2. Create conditional delete with the `currentVersion` value.
DeleteItemEnhancedRequest deleteItemRequest =
    DeleteItemEnhancedRequest.builder()
       .key(KEY)
       .conditionExpression(Expression.builder()
           .expression("recordVersion = :current_version_value")
           .putExpressionValue(":current_version_value", currentVersion)
           .build()).build();

customerTable.deleteItem(deleteItemRequest);
```

**`transactWriteItems`**  
+ `addPutItem`: このメソッドの動作は `putItem` と同じです。
+ `addUpdateItem`: このメソッドの動作は `updateItem` と同じです。
+ `addDeleteItem`: このメソッドの動作は `deleteItem` と同じです。

**`batchWriteItem`**  
+ `addPutItem`: このメソッドの動作は `putItem` と同じです。
+ `addDeleteItem`: このメソッドの動作は `deleteItem` と同じです。

**注記**  
DynamoDB グローバルテーブルでは[最終書き込み者優先](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/V2globaltables_HowItWorks.html#V2globaltables_HowItWorks.consistency-modes)を使用して、同時更新間の調整を行い、DynamoDB は最終書き込み者を判断するために最善を尽くします。グローバルテーブルを使用する場合、この「最終書き込み者優先」ポリシーは、すべてのレプリカが最終的に DynamoDB によって決定された最後の書き込みに基づいて収束するため、ロック戦略が期待どおりに機能しない可能性があることを意味します。

#### 無効にする方法
<a name="ddb-en-client-extensions-VRE-how-to-disable"></a>

楽観的ロックを無効にするには、`@DynamoDbVersionAttribute` 注釈を使用しないようにします。

### `AtomicCounterExtension` を使用してカウンターを実装する
<a name="ddb-en-client-extensions-ACE"></a>

`AtomicCounterExtension` 拡張機能は、レコードがデータベースに書き込まれるたびに、タグ付きの数値属性を増やします。開始値と増分値を指定できます。値を指定しない場合、開始値は 0 に設定され、属性の値は 1 ずつ増加します。

#### 設定
<a name="ddb-en-client-extensions-ACE-conf"></a>

どの属性がカウンターかを指定するには、テーブルスキーマのタイプ `Long` の属性にタグを付けます。

次のスニペットは、`counter` 属性のデフォルトの開始値と増分値の使用方法を示しています。

```
    @DynamoDbAtomicCounter
    public Long getCounter() {...};
    public void setCounter(Long counter) {...};
```

次のスニペットは、静的テーブルスキーマのアプローチを示しています。アトミックカウンタ拡張機能は、開始値を 10 に設定し、レコードが書き込まれるたびに値を 5 ずつ増やします。

```
    .addAttribute(Integer.class, a -> a.name("counter")
                                       .getter(Customer::getCounter)
                                       .setter(Customer::setCounter)
                                        // Apply the 'atomicCounter' tag to the attribute with start and increment values.
                                       .tags(StaticAttributeTags.atomicCounter(10L, 5L))
```

### `AutoGeneratedTimestampRecordExtension` でタイムスタンプを追加する
<a name="ddb-en-client-extensions-AGTE"></a>

`AutoGeneratedTimestampRecordExtension` 拡張機能は、アイテムがデータベースに正常に書き込まれるたびに、そのタイプ `[Instant](https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html)` のタグ付き属性を現在のタイムスタンプで自動的に更新します。このエクステンションはデフォルトではロードされません。

#### 設定
<a name="ddb-en-client-extensions-AGTE-conf"></a>

現在のタイムスタンプで更新する属性を指定するには、テーブルスキーマの `Instant` 属性にタグを付けます。

次のスニペットでは、`lastUpdate` 属性が拡張機能の動作の対象となります。属性は `Instant` タイプでなければならないという要件に注意してください。

```
    @DynamoDbAutoGeneratedTimestampAttribute
    public Instant getLastUpdate() {...}
    public void setLastUpdate(Instant lastUpdate) {...}
```

これと同等の静的テーブルスキーマのアプローチは、次のスニペットで示されます。

```
     .addAttribute(Instant.class, a -> a.name("lastUpdate")
                                        .getter(Customer::getLastUpdate)
                                        .setter(Customer::setLastUpdate)
                                        // Applying the 'autoGeneratedTimestamp' tag to the attribute.
                                        .tags(AutoGeneratedTimestampRecordExtension.AttributeTags.autoGeneratedTimestampAttribute())
```

### AutoGeneratedUuidExtension を使用して UUID を生成する
<a name="ddb-en-client-extensions-AGUE"></a>

`AutoGeneratedUuidExtension` 拡張機能は、新しいレコードがデータベースに書き込まれるときに、属性の一意の UUID (ユニバーサル一意識別子) を生成します。Java JDK [UUID.randomUUID()](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html#randomUUID--) メソッドを使用し、`java.lang.String` 型の属性に適用されます。このエクステンションはデフォルトではロードされません。

#### 設定
<a name="ddb-en-client-extensions-AGUE-conf"></a>

次のスニペットでは、`uniqueId` 属性が拡張機能の動作の対象となります。

```
    @AutoGeneratedUuidExtension
    public String getUniqueId() {...}
    public void setUniqueId(String uniqueId) {...}
```

これと同等の静的テーブルスキーマのアプローチは、次のスニペットで示されます。

```
     .addAttribute(String.class, a -> a.name("uniqueId")
                                        .getter(Customer::getUniqueId)
                                        .setter(Customer::setUniqueId)
                                        // Applying the 'autoGeneratedUuid' tag to the attribute.
                                        .tags(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute())
```

拡張機能で `putItem` メソッドに対してのみ UUID に入力されるようにし、`updateItem` メソッドに対しては入力されないようにするには、次のスニペットに示すように、[更新動作](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/UpdateBehavior.html)の注釈を追加します。

```
    @AutoGeneratedUuidExtension
    @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
    public String getUniqueId() {...}
    public void setUniqueId(String uniqueId) {...}
```

静的テーブルスキーマアプローチを使用する場合は、次の同等のコードを使用します。

```
     .addAttribute(String.class, a -> a.name("uniqueId")
                                        .getter(Customer::getUniqueId)
                                        .setter(Customer::setUniqueId)
                                        // Applying the 'autoGeneratedUuid' tag to the attribute.
                                        .tags(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute(),
                                              StaticAttributeTags.updateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS))
```

# カスタム拡張機能の例
<a name="ddb-en-client-extensions-custom"></a>

`DynamoDbEnhancedClientExtension` インターフェイスを実装することで、カスタム拡張機能を作成できます。次のカスタム拡張機能クラスは、データベース内の項目にまだ属性がない場合に、更新式を使用して `registrationDate` 属性を設定する `beforeWrite()` メソッドを示しています。

```
public final class CustomExtension implements DynamoDbEnhancedClientExtension {

    // 1. In a custom extension, use an UpdateExpression to define what action to take before
    //    an item is updated.
    @Override
    public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {
        if ( context.operationContext().tableName().equals("Customer")
                && context.operationName().equals(OperationName.UPDATE_ITEM)) {
            return WriteModification.builder()
                    .updateExpression(createUpdateExpression())
                    .build();
        }
        return WriteModification.builder().build();  // Return an "empty" WriteModification instance if the extension should not be applied.
                                                     // In this case, if the code is not updating an item on the Customer table.
    }

    private static UpdateExpression createUpdateExpression() {

        // 2. Use a SetAction, a subclass of UpdateAction, to provide the values in the update.
        SetAction setAction =
                SetAction.builder()
                        .path("registrationDate")
                        .value("if_not_exists(registrationDate, :regValue)")
                        .putExpressionValue(":regValue", AttributeValue.fromS(Instant.now().toString()))
                        .build();
        // 3. Build the UpdateExpression with one or more UpdateAction.
        return UpdateExpression.builder()
                .addAction(setAction)
                .build();
    }
}
```