Docker + DynamoDB local + C++ の環境構築と実践まで

#環境構築
今回使うものはこちら

- Docker
- Dynamodb local
- AWS CLI バージョン 2
- vcpkg
- AWS SDK for C++

また、C++ のビルド環境には VC++ を使用します。

##Docker の導入
>Docker は、コンテナを用いてアプリケーションをすばやく構築、テスト、デプロイできるソフトウェアプラットフォームです。

https://www.docker.com/

###ダウンロード
Docker のサイトからインストーラをダウンロードしましょう。
※この記事では、Docker Desktop for Windows を使用します。

###インストール
`Docker Desktop requires Windows 10 Pro/Enterprise (16299+) or Windows 10 Home (18362.1040+).`
インストール中に、上のようなエラーメッセージが出る場合は、Windows Update を確認してみてください。アップデート後に、もう一度インストールを試してみましょう。

`WSL 2 installation is imcomplete.`

上のダイアログメッセージが出る場合は、Linux カーネル更新プログラムのインストールが必要です。以下からパッケージをダウンロードしましょう。

https://aka.ms/wsl2kernel

###バージョン確認
コマンドラインからDocker のバージョンを確認してみましょう。インストールに成功していれば、以下のような出力が得られるはずです。

```shell
> docker --version
Docker version 20.10.0, build 7287ab3
```
##DynamoDB local の導入

> ダウンロード可能なバージョンの Amazon DynamoDB (DynamoDB local)では、DynamoDB ウェブサービスにアクセスせずに、アプリケーションを開発してテストすることができます。代わりに、データベースはコンピュータ上で自己完結型となります。

###イメージを取得する
Docker を使って、DynamoDB local イメージを取得しましょう。

Amazon が公開している DynamoDB local のDocker イメージがありますので、
`docker pull` コマンドを使って取得します。

```shell
> docker pull amazon/dynamodb-local
```
###コンテナを起動する
Docker を使って、DynamoDB local を起動しましょう。
`docker run` コマンドを使って起動します。

```shell
> docker run -p 8000:8000 amazon/dynamodb-local
```
これで DynamoDB local のコンテナが起動しました。わかりやすくポート番号は8000を使用しました。

###コンテナを確認する
`docker ps` コマンドを使うと、現在起動しているコンテナのリストが出力されます。

```shell
> docker ps
CONTAINER ID   IMAGE                   COMMAND                  CREATED         STATUS         PORTS                    NAMES
607c8a202aac   amazon/dynamodb-local   "java -jar DynamoDBL…"   2 seconds ago   Up 7 seconds   0.0.0.0:8000->8000/tcp   frosty_goldstine
```
###コンテナを終了する
`docker kill` コマンドを使うと、起動しているコンテナを終了させることができます。このとき、コンテナIDを指定します。

```shell
> docker kill 607c8a202aac
```

##AWS CLI バージョン 2 の導入
AWS CLI には、DynamoDB local を操作するためのコマンドラインツールが含まれています。
※今回はテーブル情報の読み出しのためだけに利用したいと思います。（C++ からのリクエストが正しく通っているかを確認するため。）

事前にインストールしておきましょう。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv2-windows.html

###初回設定

１度だけこちらの４項目を設定しておきましょう。

それぞれ、設定内容はダミーでもよいですが、忘れないように覚えておく必要があります。もし忘れてしまったら設定しなおしましょう。

```shell
> aws configure
AWS Access Key ID [****************XXXX]: （ローカルで使用するのでダミーのキーIDでよい）
AWS Secret Access Key [****************XXXX]: （ローカルで使用するのでダミーのアクセスキーでよい）
Default region name [us-west-2]:（ローカルで使用するのでダミーのリージョンでよい）
Default output format [json]:
```

###動作確認（DynamoDB local のテーブルリストを取得する）

DynamoDB local にアクセスして、動作確認をしてみましょう。
まだテーブルを作っていないので、以下のような JSON が取得できるはずです。

```shell
> aws dynamodb list-tables --endpoint-url http://localhost:8000
{
    "TableNames": []
}
```

##AWS SDK for C++ の導入
今回は、C++ からDynamoDB local にアクセスするので、**AWS SDK for C++** というライブラリを使用します。


###vcpkg をインストールする
> vcpkg は、C++ のコマンド ライン パッケージ マネージャーです。 これにより、Windows、Linux、および macOS でのサードパーティ ライブラリの取得とインストール作業を大幅に簡素化できます。

https://github.com/Microsoft/vcpkg

github から取得し、ブートストラップを実行しましょう。

```shell
> git clone https://github.com/microsoft/vcpkg
> ./vcpkg/bootstrap-vcpkg.bat
```

###AWS SDK for C++ を取得する

vcpkg を使って、ライブラリを取得しましょう。

```shell
> ./vcpkg/vcpkg install aws-sdk-cpp:x64-windows
```

###VC++ にインテグレートする

vcpkg を VC++ にインテグレートします。
これによって、先ほどダウンロードしたAWS SDK for C++ のためのインクルードパスの設定や、.lib のリンクを自動で行ってくれるようになります。

```shell
> ./vcpkg/vcpkg integrate install
```

※ただし、コーディング中のインテリセンスを働かせるためには、手動でインクルードパスを通す必要がありました。

また、インテグレートを解除したい場合は、以下のコマンドを使います。

```shell
> ./vcpkg/vcpkg integrate remove
```

#コーディング

主要なデータベース操作と、対応する AWS SDK for C++ の関数名を挙げておきます。
AWS のサイトにもサンプルコードがあるので、参考にしてみてください。

| 用途 | 関数名 |
|:-:|---|
| テーブル追加  | Aws::DynamoDB::DynamoDBClient::CreateTable |
| テーブル削除  | Aws::DynamoDB::DynamoDBClient::DeleteTable |
| アイテム取得  | Aws::DynamoDB::DynamoDBClient::GetItem |
| アイテム追加  | Aws::DynamoDB::DynamoDBClient::PutItem  |
| アイテム更新  | Aws::DynamoDB::DynamoDBClient::UpdateItem |
| アイテム削除  | Aws::DynamoDB::DynamoDBClient::DeleteItem |

**C++ Code Samples for Amazon DynamoDB**
https://docs.aws.amazon.com/code-samples/latest/catalog/code-catalog-cpp-example_code-dynamodb.html

##テーブル作成（create-table）
```cpp
#include <aws/core/Aws.h>
#include <aws/core/utils/Outcome.h> 
#include <aws/dynamodb/DynamoDBClient.h>
#include <aws/dynamodb/model/AttributeDefinition.h>
#include <aws/dynamodb/model/CreateTableRequest.h>
#include <aws/dynamodb/model/KeySchemaElement.h>
#include <aws/dynamodb/model/ProvisionedThroughput.h>
#include <aws/dynamodb/model/ScalarAttributeType.h>
#include <iostream>

// ここで、予め設定しておいたアクセスキーを使います
const Aws::String AWS_ACCESS_KEY_ID     = "XXXXXXXXXXXXXXXXXXXX";
const Aws::String AWS_SECRET_ACCESS_KEY = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

Aws::SDKOptions options;
Aws::InitAPI(options);

// 作成したいテーブルの名前を決める
const Aws::String table("Game");
// ここで、予め設定しておいたリージョン名を使います
const Aws::String region("us-west-2");

// エンドポイントを指定する
Aws::Client::ClientConfiguration clientConfig;
clientConfig.requestTimeoutMs = 1000;
clientConfig.region = region;
clientConfig.endpointOverride = "http://localhost:8000";

const Aws::DynamoDB::DynamoDBClient dynamoClient(Aws::Auth::AWSCredentials(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY), clientConfig);

// テーブル作成リクエスト
Aws::DynamoDB::Model::CreateTableRequest req;

Aws::DynamoDB::Model::AttributeDefinition haskKey;
// "Name"という名前の属性を定義する、"Name"は文字列型である
haskKey.SetAttributeName("Name");
haskKey.SetAttributeType(Aws::DynamoDB::Model::ScalarAttributeType::S);
req.AddAttributeDefinitions(haskKey);

// "Name" をハッシュキー（パーティションキー）として扱うようにスキーマを定義する
Aws::DynamoDB::Model::KeySchemaElement keyscelt;
keyscelt.WithAttributeName("Name").WithKeyType(Aws::DynamoDB::Model::KeyType::HASH);
req.AddKeySchema(keyscelt);

// 適当なキャパシティユニットを設定する
Aws::DynamoDB::Model::ProvisionedThroughput thruput;
thruput.WithReadCapacityUnits(5).WithWriteCapacityUnits(5);
req.SetProvisionedThroughput(thruput);

// テーブル名を指定する
req.SetTableName(table);

// テーブル作成リクエストをDynamoDB local に送信する（この関数はブロックするので注意！）
const Aws::DynamoDB::Model::CreateTableOutcome& result = dynamoClient.CreateTable(req);
if (result.IsSuccess())
{
    std::cout << "Table \"" << result.GetResult().GetTableDescription().GetTableName() <<
        " created!" << std::endl;
}
else
{
    std::cout << "Failed to create table: " << result.GetError().GetMessage();
}

Aws::ShutdownAPI(options);
```

AWS CLI を使って、テーブルが作成できているかの確認をしてみましょう。
うまく作成できていれば、以下のような JSON が取得できるはずです。

```shell
> aws dynamodb list-tables --endpoint-url http://localhost:8000
{
    "TableNames": [
        "Game"
    ]
}
```
## アイテム追加（put-item）
```cpp
#include <aws/core/Aws.h>
#include <aws/core/utils/Outcome.h> 
#include <aws/core/auth/AWSCredentialsProvider.h>
#include <aws/dynamodb/DynamoDBClient.h>
#include <aws/dynamodb/model/AttributeDefinition.h>
//#include <aws/dynamodb/model/CreateTableRequest.h>
#include <aws/dynamodb/model/PutItemRequest.h>
#include <aws/dynamodb/model/PutItemResult.h>
#include <iostream>

//（途中省略...）

Aws::DynamoDB::Model::PutItemRequest req;
// 対象のテーブル名を指定する
req.SetTableName(table);

Aws::DynamoDB::Model::AttributeValue av;

// "Name" が 文字列型"Switch"
av.SetS("Switch");
req.AddItem("Name", av);

// "State" が 文字列型"off"
av.SetS("off");
req.AddItem("State",av);

// アイテム追加リクエストをDynamoDB local に送信する（この関数はブロックするので注意！）
const Aws::DynamoDB::Model::PutItemOutcome result = dynamoClient.PutItem(req);
if (!result.IsSuccess())
{
    std::cout << result.GetError().GetMessage() << std::endl;
    return 1;
}
std::cout << "Done!" << std::endl;

//（途中省略...）
```

AWS CLI を使って、テーブルの内容を出力してみましょう。
`aws dynamodb scan` コマンドにテーブル名を渡すと、以下のようにテーブルの内容を JSON で取得できます。

```shell
> aws dynamodb scan --table-name Game --endpoint-url http://localhost:8000
{
    "Items": [
        {
            "State": {
                "S": "off"
            },
            "Name": {
                "S": "Switch"
            }
        }
    ],
    "Count": 1,
    "ScannedCount": 1,
    "ConsumedCapacity": null
}
```
#おわりに
非常に長い道のりでしたが、DynamoDB local を C++ から操作することができました。AWS SDK for C++ は扱いやすく、DynamoDB を操作するための一通りのAPI が揃っています。これを導入すれば、C++ プログラマにとっては DynamoDB の敷居が一気に下がるのではないでしょうか。

DynamoDB local は 簡単にレコードを追加したり、削除したりできるので、プロトタイピングにもおススメですね。