

# DynamoDB の苦情管理システムのスキーマ設計
<a name="data-modeling-complaint-management"></a>

## 苦情管理システムのビジネスユースケース
<a name="data-modeling-schema-complaint-management-use-case"></a>

DynamoDB は、苦情管理システム (またはコンタクトセンター) のユースケースに最適なデータベースです。関連するほとんどのアクセスパターンが key-value ベースのトランザクションルックアップであるためです。このシナリオの一般的なアクセスパターンは、次のとおりです。
+ 苦情を作成および更新する
+ 苦情をエスカレートする
+ 苦情に関するコメントを作成および閲覧する
+ 顧客からのすべての苦情を取得する
+ エージェントからのすべてのコメントを取得し、すべてのエスカレーションを取得する 

一部のコメントには、苦情または解決策を説明する添付ファイルが付いている場合があります。これらはすべて key-value アクセスパターンですが、苦情に新しいコメントが追加されたときに通知を送信したり、分析クエリを実行して重大度 (またはエージェントのパフォーマンス) 別に苦情の分布を毎週調べたりするなど、追加の要件が伴う場合があります。ライフサイクル管理やコンプライアンスに関連するその他の要件としては、苦情を 3 年間記録した後に苦情データをアーカイブすることが挙げられます。

## 苦情管理システムのアーキテクチャ図
<a name="data-modeling-schema-complaint-management-ad"></a>

次の図は、苦情管理システムのアーキテクチャ図を示しています。この図は、苦情管理システムが使用するさまざまな AWS のサービス統合を示しています。

![複数の AWS のサービスとの統合を使用して、非トランザクション要件を満たすための結合ワークフロー。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-1-AD.jpg)


後述の DynamoDB データモデリングセクションで扱う key-value トランザクションアクセスパターンとは別に、非トランザクション要件が 3 つあります。上のアーキテクチャ図は、次の 3 つのワークフローに分けることができます。

1. 苦情に新しいコメントが追加されたときに通知を送信する

1. 週次データに対して分析クエリを実行する

1. 3 年より前のデータをアーカイブする

それぞれを詳しく見ていきましょう。

**苦情に新しいコメントが追加されたときに通知を送信する**

この要件を満たすには、次のワークフローを使用できます。

![Lambda 関数を呼び出して、DynamoDB Streams が記録した変更に基づいて通知を送信するワークフロー。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-2-Workflow1.jpg)


[DynamoDB ストリーム](Streams.md)は、DynamoDB テーブルに対するすべての書き込みアクティビティを記録する変更データキャプチャメカニズムです。これらの変更の一部またはすべてに応じてトリガーされるように Lambda 関数を設定できます。Lambda トリガーに[イベントフィルター](https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventfiltering.html)を設定して、ユースケースに関係のないイベントを除外できます。この例では、フィルターを使用して、新しいコメントが追加されたときにのみ Lambda をトリガーし、関連する E メール ID ([AWS Secret Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html) またはその他の認証情報ストアから取得可能) に通知を送信できます。

**週次データに対して分析クエリを実行する**

DynamoDB は、オンライントランザクション処理 (OLTP) を主眼とするワークロードに適しています。分析が必要な他の 10～20% のアクセスパターンについては、マネージド機能の [Amazon S3 へのエクスポート](S3DataExport.HowItWorks.md)を使用してデータを S3 にエクスポートできます。この機能を使用しても、DynamoDB テーブルのライブトラフィックには影響がありません。次のワークフローをご覧ください。

![Lambda 関数を定期的に呼び出して DynamoDB データを Amazon S3 バケットに保存するワークフロー。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-3-Workflow2.jpg)


[Amazon EventBridge](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-what-is) を使用すると、AWS Lambda をスケジュールどおりにトリガーできます。これにより、Lambda を定期的に呼び出すように cron 式を設定できます。Lambda は `ExportToS3` API コールを呼び出し、DynamoDB データを S3 に保存します。この S3 データに [Amazon Athena](https://docs.aws.amazon.com/athena/latest/ug/what-is) などの SQL エンジンからアクセスし、テーブルのライブトランザクションワークロードには影響を与えることなく、DynamoDB データに対して分析クエリを実行できます。重大度レベル別に苦情数を調べる Athena クエリの例は、次のようになります。

```
SELECT Item.severity.S as "Severity", COUNT(Item) as "Count"
FROM "complaint_management"."data"
WHERE NOT Item.severity.S = ''
GROUP BY Item.severity.S ;
```

この Athena クエリは次の結果を返します。

![重要度レベル P3、P2、P1 の苦情数を示す Athena クエリ結果。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-4-Athena.png)


**3 年より前のデータをアーカイブする**

DynamoDB の [Time to Live (TTL)](TTL.md) 機能を使用して、追加費用なしで、DynamoDB テーブルから古いデータを削除できます (ただし、2019.11.21 (現行) バージョンのグローバルテーブルレプリカは除きます。TTL 削除が他のリージョンにレプリケートされると書き込み容量が消費されるためです)。このデータを DynamoDB ストリームで表示し、使用できます。その後に、Amazon S3 にアーカイブできます。この要件のワークフローは次のとおりです。

![TTL 機能と DynamoDB Streams を使用して Amazon S3 バケットに古いデータをアーカイブするワークフロー。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-5-Workflow3.jpg)


## 苦情管理システムのエンティティ関係図
<a name="data-modeling-schema-complaint-management-erd"></a>

次に示すのは、苦情管理システムのスキーマ設計に使用するエンティティ関係図 (ERD) です。

![エンティティとしての顧客、苦情、コメント、エージェントを示す苦情管理システム ERD。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-6-ERD.jpg)


## 苦情管理システムのアクセスパターン
<a name="data-modeling-schema-complaint-management-access-patterns"></a>

以下は、苦情管理システムのスキーマ設計で検討するアクセスパターンです。

1. createComplaint

1. updateComplaint

1. updateSeveritybyComplaintID

1. getComplaintByComplaintID

1. addCommentByComplaintID

1. getAllCommentsByComplaintID

1. getLatestCommentByComplaintID

1. getAComplaintbyCustomerIDAndComplaintID

1. getAllComplaintsByCustomerID

1. escalateComplaintByComplaintID

1. getAllEscalatedComplaints

1. getEscalatedComplaintsByAgentID (新しいものから古いものへの順)

1. getCommentsByAgentID (2 つの日付間)

## 苦情管理システムのスキーマ設計の進化
<a name="data-modeling-schema-complaint-management-design-evolution"></a>

これは苦情管理システムであるため、ほとんどのアクセスパターンはプライマリエンティティとしての苦情を中心に展開します。`ComplaintID` のカーディナリティが高いと、基盤となるパーティションにデータが均等に分散されます。また、特定したアクセスパターンの最も一般的な検索条件となります。したがって、`ComplaintID` は、このデータセットのパーティションキー候補として適しています。

**ステップ 1: アクセスパターン 1 (`createComplaint`)、2 (`updateComplaint`)、3 (`updateSeveritybyComplaintID`)、4 (`getComplaintByComplaintID`) に対処する**

「metadata」(または「AA」) という汎用ソートキー値を使用して、`CustomerID`、`State`、`Severity`、`CreationDate` などの苦情固有の情報を保存できます。`PK=ComplaintID` と `SK=“metadata”` でシングルトンオペレーションを使用して、以下を行います。

1. [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html) で新しい苦情を作成する

1. [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html) で苦情メタデータの重大度やその他のフィールドを更新する

1. [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html) で苦情のメタデータを取得する

![苦情項目のプライマリキー、ソートキー、および属性値 (customer_id や重要度など)。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-7-Step1.png)


**ステップ 2: アクセスパターン 5 (`addCommentByComplaintID`) に対処する**

このアクセスパターンでは、苦情と苦情に関するコメントとの間に 1 対多リレーションシップモデルが必要です。ここでは、[垂直パーティショニング](data-modeling-blocks.md#data-modeling-blocks-vertical-partitioning)手法を通じてソートキーを使用し、さまざまなタイプのデータを含む項目コレクションを作成します。アクセスパターン 6 (`getAllCommentsByComplaintID`) と 7 (`getLatestCommentByComplaintID`) に注目すると、コメントを時間順に並べ替える必要があることがわかります。また、複数のコメントを同時に受け取ることができるため、[複合ソートキー](data-modeling-blocks.md#data-modeling-blocks-composite)手法を使用して時間と `CommentID` をソートキー属性に追加できます。

このようなコメント競合の可能性に対処する他のオプションとしては、タイムスタンプの粒度を増やすか、`Comment_ID` を使用する代わりに増分数値をサフィックスとして追加します。この場合、範囲ベースの操作を可能にするために、コメントに対応する項目のソートキー値の前に「comm\#」を付けます。

また、苦情メタデータの `currentState` に、新しいコメントを追加したときの状態が反映されることを確認する必要があります。コメントの追加は、エージェントへの苦情の割り当てや苦情の解決などを示す場合があります。コメントの追加と現在の状態の更新をオールオアナッシング方式で苦情メタデータにバンドルするには、[TransactWriteItems](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html) API を使用します。結果として得られるテーブルの状態は次のようになります。

![複合ソートキーを使用して苦情とそのコメントを 1 対多リレーションシップとして保存するテーブル。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-8-Step2.png)


テーブルにさらにデータを追加し、`PK` とは別のフィールドとして `ComplaintID` も追加しましょう。`ComplaintID` に追加のインデックスが必要な場合に備えて、モデルを将来的に保証するためです。また、一部のコメントには添付ファイルが含まれている場合があることに注意してください。添付ファイルは、Amazon Simple Storage Service に保存し、その参照または URL のみを DynamoDB で管理します。コストとパフォーマンスを最適化するために、トランザクションデータベースはできるだけリーンな状態に保つことがベストプラクティスです。これで、データは次のようになります。

![苦情メタデータと、各苦情に関連するすべてのコメントのデータを含むテーブル。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-9-Step3.png)


**ステップ 3: アクセスパターン 6 (`getAllCommentsByComplaintID`) と 7 (`getLatestCommentByComplaintID`) に対処する**

苦情に関するすべてのコメントを取得するには、ソートキーに対する [`query`](Query.md) オペレーションで `begins_with` 条件を使用できます。メタデータエントリを読み取るために追加の読み取り容量を消費したり、関連する結果をフィルタリングするためのオーバーヘッドを生じたりせずに、このようなソートキー条件を設定することで、必要なものだけを読み取ることができます。例えば、`SK` および `PK=Complaint123` begins\_with `comm#` を使用したクエリオペレーションは、メタデータエントリをスキップして、以下を返します。

![苦情のコメントのみを表示するソートキー条件を使用した、クエリオペレーションの結果。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-10-Step4.png)


パターン 7 (`getLatestCommentByComplaintID`) では苦情に関する最新のコメントが必要であるため、次の 2 つのクエリパラメータを追加しましょう。

1. 結果を降順にソートするために False に設定する `ScanIndexForward`

1. 最新のコメントを (1つだけ) 取得するために 1 に設定する `Limit`

アクセスパターン 6 (`getAllCommentsByComplaintID`) と同じように、ソートキー条件として `begins_with` `comm#` を使用してメタデータエントリを省略します。これで、クエリオペレーションで `PK=Complaint123` と `SK=begins_with comm#` に加えて、`ScanIndexForward=False` と `Limit` を使用し、この設計でアクセスパターン 7 を実行できるようになりました。結果として、次のターゲット項目が返されます。

![苦情の最後のコメントを取得するためのソートキー条件を使用したクエリオペレーションの結果。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-11-Step5.png)


さらにダミーデータをテーブルに追加してみましょう。

![受信した苦情に関する最新のコメントを取得するための、ダミーデータを含むテーブル。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-12-Step6.png)


**ステップ 4: アクセスパターン 8 (`getAComplaintbyCustomerIDAndComplaintID`) と 9 (`getAllComplaintsByCustomerID`) に対処する**

アクセスパターン 8 (`getAComplaintbyCustomerIDAndComplaintID`) と 9 (`getAllComplaintsByCustomerID`) は、新しい検索条件 `CustomerID` を導入します。これを既存のテーブルから取得するには、すべてのデータを読み取って当の `CustomerID` の関連項目をフィルタリングするために、コストの高い [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html) が必要になります。この検索をより効率的にするには、`CustomerID` をパーティションキーとして[グローバルセカンダリインデックス (GSI)](GSI.md) を作成できます。顧客と苦情の 1 対多リレーションシップおよびアクセスパターン 9 (`getAllComplaintsByCustomerID`) を考慮すると、`ComplaintID` がソートキーの適切な候補となります。

GSI のデータは次のようになります。

![特定の CustomerID ですべての苦情を取得するための 1 対多リレーションシップモデルを含む GSI。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-13-Step4-GSI.png)


 この GSI のクエリ例はアクセスパターン 8 (`getAComplaintbyCustomerIDAndComplaintID`) で `customer_id=custXYZ`、`sort key=Complaint1321` となります。結果は次のようになります。

![特定の顧客からの特定の苦情データを取得するための GSI に対するクエリオペレーションの結果。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-14-Step4-8.png)


アクセスパターン 9 (`getAllComplaintsByCustomerID`) でお客様の苦情をすべて取得する場合、GSI のクエリはパーティションキー条件が `customer_id=custXYZ` になります。結果は次のようになります。

![特定の顧客からのすべての苦情を取得するためにパーティションキー条件を使用したクエリオペレーションの結果。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-15-Step4-9.png)


**ステップ 5: アクセスパターン 10 (`escalateComplaintByComplaintID`) に対処する**

このアクセスでは、エスカレーションのディメンションを導入します。苦情をエスカレーションするには、`UpdateItem` を使用して `escalated_to` や `escalation_time` などの属性を既存の苦情メタデータ項目に追加します。DynamoDB のスキーマ設計は柔軟であるため、キー以外の属性のセットをさまざまな項目間で均一にすることも、個別にすることもできます。次の例を参照してください。

`UpdateItem with PK=Complaint1444, SK=metadata`

![UpdateItem API オペレーションを使用して苦情メタデータを更新した結果。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-16-Step5.png)


**ステップ 6: アクセスパターン 11 (`getAllEscalatedComplaints`) と 12 (`getEscalatedComplaintsByAgentID`) に対処する**

データセット全体からエスカレーションされると予想される苦情はほんの一握りです。したがって、エスカレーション関連の属性にインデックスを作成すると、効率的な検索と費用対効果の高い GSI ストレージが実現します。これを実現するには、[スパースインデックス](data-modeling-blocks.md#data-modeling-blocks-sparse-index)手法を活用できます。パーティションキーを `escalated_to`、ソートキーを `escalation_time` とする GSI は、次のようになります。

![エスカレーション関連の属性 (escalated_to と escalation_time) を使用した GSI 設計。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-17-Step6.png)


アクセスパターン 11 (`getAllEscalatedComplaints`) ですべてのエスカレーションされた苦情を取得するには、この GSI を単にスキャンします。このスキャンは、GSI のサイズにより、パフォーマンスとコスト効率が向上することに注意してください。特定のエージェント (アクセスパターン 12 (`getEscalatedComplaintsByAgentID`)) でエスカレーションされた苦情を取得する場合は、パーティションキーを `escalated_to=agentID` とし、`ScanIndexForward` を `False` に設定して新しいものから古いものへの順に取得します。

**ステップ 7: アクセスパターン 13 (`getCommentsByAgentID`) に対処する**

最後のアクセスパターンでは、新しいディメンション `AgentID` によるルックアップを実行する必要があります。また、2 つの日付間のコメントを読み取るには時間ベースの順序付けも必要であるため、パーティションキーを `agent_id`、ソートキーを `comm_date` として GSI を作成します。この GSI のデータは次のようになります。

![コメント日付を使用してソートした、特定のエージェントからのコメントを検索する GSI 設計。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-18.png)


この GSI のクエリの例は `partition key agentID=AgentA` および `sort key=comm_date between (2023-04-30T12:30:00, 2023-05-01T09:00:00)` となり、その結果は次のようになります。

![GSI でパーティションキーとソートキーを使用したクエリの結果。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-19.png)


すべてのアクセスパターンと各アクセスパターンにスキーマ設計で対処する方法を次の表にまとめています。


| アクセスパターン | ベーステーブル/GSI/LSI | Operation | パーティションキー値 | ソートキー値 | その他の条件/フィルター | 
| --- | --- | --- | --- | --- | --- | 
| createComplaint | ベーステーブル | PutItem | PK=complaint\_id | SK=metadata |  | 
| updateComplaint | ベーステーブル | UpdateItem | PK=complaint\_id | SK=metadata |  | 
| updateSeveritybyComplaintID | ベーステーブル | UpdateItem | PK=complaint\_id | SK=metadata |  | 
| getComplaintByComplaintID | ベーステーブル | GetItem | PK=complaint\_id | SK=metadata |  | 
| addCommentByComplaintID | ベーステーブル | TransactWriteItems | PK=complaint\_id | SK=metadata, SK=comm\#comm\_date\#comm\_id |  | 
| getAllCommentsByComplaintID | ベーステーブル | クエリ | PK=complaint\_id | SK begins\_with "comm\#" |  | 
| getLatestCommentByComplaintID | ベーステーブル | クエリ | PK=complaint\_id | SK begins\_with "comm\#" | scan\_index\_forward=False, Limit 1 | 
| getAComplaintbyCustomerIDAndComplaintID | Customer\_complaint\_GSI | クエリ | customer\_id=customer\_id | complaint\_id = complaint\_id |  | 
| getAllComplaintsByCustomerID | Customer\_complaint\_GSI | クエリ | customer\_id=customer\_id | 該当なし |  | 
| escalateComplaintByComplaintID | ベーステーブル | UpdateItem | PK=complaint\_id | SK=metadata |  | 
| getAllEscalatedComplaints | Escalations\_GSI | Scan | 該当なし | 該当なし |  | 
| getEscalatedComplaintsByAgentID (新しいものから古いものへの順) | Escalations\_GSI | クエリ | escalated\_to=agent\_id | 該当なし | scan\_index\_forward=False | 
| getCommentsByAgentID (2 つの日付間) | Agents\_Comments\_GSI | クエリ | agent\_id=agent\_id | SK between (date1, date2) |  | 

## 苦情管理システムの最終スキーマ
<a name="data-modeling-schema-complaint-management-final-schema"></a>

最終的なスキーマ設計は次のとおりです。このスキーマ設計を JSON ファイルとしてダウンロードするには、GitHub の [DynamoDB の例](https://github.com/aws-samples/aws-dynamodb-examples/blob/master/schema_design/SchemaExamples/ComplainManagement/ComplaintManagementSchema.json)を参照してください。

**ベーステーブル**

![苦情メタデータを含むベーステーブル設計。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-20-Complaint_management_system.png)


**Customer\_Complaint\_GSI**

![特定の顧客からの苦情を示す GSI 設計。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-21-Customer_Complaint_GSI.png)


**Escalations\_GSI**

![エスカレーション関連の属性を示す GSI 設計。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-22-Escalations_GSI.png)


**Agents\_Comments\_GSI**

![特定のエージェントからのコメントを示す GSI 設計。](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-23-Comments_GSI.png)


## このスキーマ設計での NoSQL Workbench の使用
<a name="data-modeling-schema-complaint-management-nosql"></a>

この最終スキーマを、DynamoDB のデータモデリング、データ視覚化、クエリ開発機能を提供するビジュアルツールである [NoSQL Workbench](workbench.md) にインポートして、新しいプロジェクトを詳しく調べたり編集したりできます。使用を開始するには、次の手順に従います。

1. NoSQL Workbench をダウンロードします。詳細については、「[DynamoDB 用の NoSQL Workbench のダウンロード](workbench.settingup.md)」を参照してください。

1. 上記の JSON スキーマファイルをダウンロードします。このファイルは既に NoSQL Workbench モデル形式になっています。

1. JSON スキーマファイルを NoSQL Workbench にインポートします。詳細については、「[既存のデータモデルのインポート](workbench.Modeler.ImportExisting.md)」を参照してください。

1. NOSQL Workbench にインポートしたら、データモデルを編集できます。詳細については、「[既存のデータモデルの編集](workbench.Modeler.Edit.md)」を参照してください。