dynamodb-localのローカル環境での利用方法

# この記事で書くこと
ローカル環境でdynamodb-localを利用する方法をなるべくシンプルに記載します。
手順は以下の通りです。

1. dynamodb-localコンテナの起動とデータの永続化
2. サンプルデータの投入
3. データの永続化確認
4. アプリケーションからの接続とリクエスト実行

# 背景
個人的に、RDBも含めてDBをDocker化することで以下のような利点を感じています。
1. ローカル環境に各種DBの様々なバージョンを入れないことで管理しやすくなった
2. アプリケーションの実行時には毎回dockerコマンドを打たなくてよくなった

同じように感じている、もしくはそのようにしたいという方の参考になればと思っています。

# 実行環境
OS:macOS Big Sur(version 11.6)
docker:20.10.0
aws-cli:1.20.53
python:3.9.1
boto3:1.18.48

# dynamodb-localコンテナの起動とデータの永続化

下記公式サイトでは`docker run`コマンドでの起動方法が書いてあります。
しかし、起動と終了が頻発することを想定し、今回は`docker-compose`を利用することにします。

https://hub.docker.com/r/amazon/dynamodb-local


ディレクトリ構成とyamlファイルは以下の通りです。

```
├ docker-compose.yaml
├ dynamodb_data   // データ保存用のディレクトリ
├ api
│ └ create_table.json  // テーブル作成用
│ └ input_data.json  // input用データ
```

```yaml:docker-compose.yaml
version: '3'

services:
  dynamodb:
    image: amazon/dynamodb-local
    container_name: dynamodb_local
    user: root
    command: -jar DynamoDBLocal.jar -sharedDb -dbPath /data
    volumes:
      - ./dynamodb_data:/data
    ports:
      - 8000:8000
```
永続化にあたってのポイントは以下2点です。
1. `user`で`rootユーザー`を指定することで名前付きボリュームを利用可能にしている
2. `command`で`-dbPath`を指定することでデータを永続化している
詳細な内容については下記の記事をご参照ください。
https://developers.freee.co.jp/entry/dynamodb-local


`docker-compose`コマンドでイメージのpullからコンテナを起動まで一気に実施します。

```
$ docker-compose up -d
$ docker container ls
```

```
CONTAINER ID   IMAGE                   COMMAND                  CREATED        STATUS        PORTS                    NAMES
08c276a5b062   amazon/dynamodb-local   "java -jar DynamoDBL…"   1 hours ago   Up 23 hours   0.0.0.0:8000->8000/tcp   dynamodb_local
```

# サンプルデータの投入

用意しておいた`create_table.json`を元にテーブル作成。

```settings/create_table.json
{
        "AttributeDefinitions": [
            {
                "AttributeName": "name",
                "AttributeType": "S"
            }
        ],
        "TableName": "students",
        "KeySchema": [
            {
                "AttributeName": "name",
                "KeyType": "HASH"
            }
        ],
        "ProvisionedThroughput": {
            "ReadCapacityUnits": 5,
            "WriteCapacityUnits": 5
        }
}
```

```
$ aws dynamodb create-table --cli-input-json file://settings/create_table.json  --endpoint-url http://localhost:8000
```

`--endpoint-url`を指定しないとローカルでないAWS上にリソースが作成されてしまう可能性があるのでご注意ください。

続いて`input_data.json`を元にデータも投入します。

```input_data.json
{
    "students": [
        {
            "PutRequest": {
                "Item": {
                    "name": { "S": "ichiro" },
                    "hometown": { "S": "Tokyo" }
                }
            }
        },
        {
            "PutRequest": {
                "Item": {
                    "name": { "S": "jiro" },
                    "hometown": { "S": "Chiba" },
                    "hobby": {"S": "table tennis"}
                }
            }
        },
        {
            "PutRequest": {
                "Item": {
                    "name": { "S": "saburo" },
                    "hometown": { "S": "Kanagawa" }
                }
            }
        }
    ]
}
```

```
$ aws dynamodb batch-write-item --request-items file://settings/input_data.json --endpoint-url http://localhost:8000
```

`scan`(全表スキャン)して確認し、無事データが登録されているのを確認します。

```
$ aws dynamodb scan --table-name students --endpoint-url http://localhost:8000
```

```
{
    "Items": [
        {
            "name": {
                "S": "ichiro"
            },
            "hometown": {
                "S": "Tokyo"
            }
        },
        {
            "name": {
                "S": "saburo"
            },
            "hometown": {
                "S": "Kanagawa"
            }
        },
        {
            "name": {
                "S": "jiro"
            },
            "hometown": {
                "S": "Chiba"
            },
            "hobby": {
                "S": "table tennis"
            }
        }
    ],
    "Count": 3,
    "ScannedCount": 3,
    "ConsumedCapacity": null
}
```

# データの永続化確認
永続化の確認のために`docker-compose down`と`docker-compose-up`を再度実行します。

```
$ docker-compose down
$ docker-compose up -d
```

先程と全く同じように`scan`してみるとそのまま3件数のデータが残っていることが確認できました。

```
{
    "Items": [
        {
            "name": {
                "S": "ichiro"
            },
            "age": {
                "N": "22"
            },
            "hometown": {
                "S": "Tokyo"
            }
        },
        {
            "name": {
                "S": "saburo"
            },
            "age": {
                "N": "20"
            },
            "hometown": {
                "S": "Kanagawa"
            }
        },
        {
            "name": {
                "S": "jiro"
            },
            "hometown": {
                "S": "Chiba"
            },
            "age": {
                "N": "21"
            },
            "hobby": {
                "S": "table tennis"
            }
        }
    ],
    "Count": 3,
    "ScannedCount": 3,
    "ConsumedCapacity": null
}
```

この状態を作成できればとりあえず安心して手元から操作できそうです。


# アプリケーションからの接続
せっかくなのでアプリケーションからも`scan`操作を実行してみます。

```scan.py
import boto3

def scan():
    dynamodb = boto3.resource('dynamodb',
    endpoint_url="http://localhost:8000",
    region_name="us-west2", # ダミー情報です
    aws_access_key_id="access_key_id", # ダミー情報です
    aws_secret_access_key="secret_access_key" # ダミー情報です
    )

    table = dynamodb.Table("students")
    res = table.scan()
    for item in res['Items']:
        print(item)


if __name__ == '__main__':
    scan()
```

無事に接続してデータを取得することができました。

```
{'name': 'ichiro', 'hometown': 'Tokyo'}
{'name': 'saburo', 'hometown': 'Kanagawa'}
{'name': 'jiro', 'hometown': 'Chiba', 'hobby': 'table tennis'}
```

# おわりに
普段はsam-localからdynamodb-localへ接続することが多いのですが、そこでもハマりポイントがあったので今度記事にしようと思います。