DynamoDB localを使ってDynamoDBをローカルでデプロイする

## 概要

DynamoDBはウェブサービスとして利用できるほか、別に提供されている**DynamoDB Local**を使えばDynamoDBを**開発用ローカルDBサーバ**としてデプロイすることができます。
DynamoDB Localに関する公式ドキュメントは以下の通り。

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DynamoDBLocal.html

上記の記述の通り、DynamoDB Localは実行可能な.jarファイルとして提供されているほか、Dockerイメージとしても提供されており、`docker-compose`で簡単にデプロイすることができます。

今回は公式の記述を参考にしつつ、`docker-compose`でDynamoDB Localをデプロイし、Node.js上から読み書きするまでを行ってみたいと思います。

### dynamodb-admin

また、**dynamodb-admin**というDynamoDB Local用のGUIを提供してくれるnpmモジュールがあります。
CLIよりもGUIで確認できたほうが便利だと思うので、こちらも一緒にデプロイしてみます。

https://www.npmjs.com/package/dynamodb-admin

## 環境

|ソフトウェア|バージョン|
|:--|:--|
|OS|Windows 10|
|Docker|Docker version 20.10.7, build f0df350|
|docker-compose|docker-compose version 1.29.2, build 5becea4c|
|Node.js|v14.15.0
|yarn|1.22.10
|@aws-sdk/client-dynamodb|3.22.0


## ディレクトリ構成

ディレクトリ構成は以下になります。

```
- docker-compose.yml
- dynamodb-admin/
  |- node_modules
  |- Dockerfile
  |- package.json
  |- yarn.lock
- dynamodb-client/
  |- node_modules
  |- index.js
  |- package.json
  |- yarn.lock
```
`dynamodb-admin/`ではdynamodb-admin用のDockerコンテナを作るために用意します。
`dynamodb-client/`ではDynamoDB Localへデータを読み書きするスクリプトを用意します。

## docker-compose.yml

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

services:
  dynamodb-local:
    command: "-jar DynamoDBLocal.jar -sharedDb -optimizeDbBeforeStartup -dbPath ./data"
    image: "amazon/dynamodb-local:latest"
    ports:
    - "8000:8000"
    volumes:
    - "./dynamodb:/home/dynamodblocal/data"
    working_dir: /home/dynamodblocal

  dynamodb-admin:
    build:
      context: ./dynamodb-admin
      dockerfile: Dockerfile
    working_dir: /usr/src/dynamodbadmin
    volumes:
    - dynamodbadmin-node_modules:/usr/src/dynamodbadmin/node_modules
    ports:
    - "8001:8001"

volumes:
  dynamodbadmin-node_modules:
```

## dynamodb-admin

### dynamodb-adminをインストール

```bash
cd dynamodb-admin
npm init -y
yarn add dynamodb-admin
```

### Dockerfileを作成

```Dockerfile:dynamodb-admin/Dockerfile
FROM node:14-slim

ENV DYNAMO_ENDPOINT=http://dynamodb-local:8000

WORKDIR /usr/src/dynamodbadmin

ADD package.json package.json
ADD yarn.lock yarn.lock

RUN yarn install

EXPOSE 8001

CMD ["yarn", "start"]
```

### `package.json`の`scripts`に追加する

以下のスクリプトを追加してDockerから実行できるようにします。

```json:dynamodb-admin/package.json
  "scripts": {
    "start": "dynamodb-admin",
  }
```

## コンテナを起動する

```bash
docker-compose up
```

正常に動作していれば以下のようなログが出力されます。

```
dynamodb-local_1  | Initializing DynamoDB Local with the following configuration: 
dynamodb-local_1  | Port:       8000
dynamodb-local_1  | InMemory:   false
dynamodb-local_1  | DbPath:     ./data
dynamodb-local_1  | SharedDb:   true
dynamodb-local_1  | shouldDelayTransientStatuses:       false
dynamodb-local_1  | CorsParams: *
dynamodb-local_1  |
dynamodb-admin_1  | yarn run v1.22.5
dynamodb-admin_1  | $ dynamodb-admin
dynamodb-admin_1  |   database endpoint:        http://dynamodb-local:8000
dynamodb-admin_1  |   region:           us-east-1
dynamodb-admin_1  |   accessKey:                key
dynamodb-admin_1  | 
dynamodb-admin_1  |   dynamodb-admin listening on http://localhost:8001 (alternatively http://0.0.0.0:8001)
```

上記のログに書いてある`http://localhost:8001`にアクセスすると以下のようなページが表示されます。
この画面から、DynamoDB Localのテーブルを作成したり、項目の追加・削除などをしたりすることができます。

![FireShot Capture 019 - DynamoDB Admin - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/519262/83730dd8-62e2-3c73-332b-14c6633b352f.png)

## AWS SDK for JavaScript v3を使って項目を読み書きする

dynamodb-adminのGUIからDynamoDB Localに対する操作はできるものの、通常の開発では普通プログラムから操作することになると思われるので、デプロイしたDynamoDB Localに対してNode.jsから読み書きを行ってみたいと思います。
以下のドキュメントを参考に、簡単なスクリプトを作成します。

https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/index.html#aws-sdk-for-javascript-v3

### AWS SDKをインストール

```bash
cd dynamodb-client
npm init -y
yarn add @aws-sdk/client-dynamodb
```

### スクリプトを作成

このスクリプトではDynamoDB Localに対して以下の操作を行っています。

- テーブル`Books`を作成
- 項目を一つ作成する
- 作成した項目を読み込む

```javascript:dynamodb-client/index.js
const { DynamoDBClient, PutItemCommand, CreateTableCommand, GetItemCommand } = require("@aws-sdk/client-dynamodb");

(async () => {
  const client = new DynamoDBClient({ endpoint: 'http://localhost:8000' });
  const TableName = 'Books'

  try {
    await client.send(new CreateTableCommand({
      TableName,
      KeySchema: [
        { AttributeName: 'Writer', KeyType: 'HASH' },
        { AttributeName: 'BookTitle', KeyType: 'RANGE' },
      ],
      AttributeDefinitions: [
        { AttributeName: 'Writer', AttributeType: 'S' },
        { AttributeName: 'BookTitle', AttributeType: 'S' },
      ],
      BillingMode: 'PAY_PER_REQUEST'
    }))

    await client.send(new PutItemCommand({
      TableName,
      Item: {
        'Writer': {
          'S': '太宰治'
        },
        'BookTitle': {
          'S': '人間失格'
        }
      }
    }))

    const result = await client.send(new GetItemCommand({
      TableName,
      Key: {
        'Writer': {
          'S': '太宰治'
        },
        'BookTitle': {
          'S': '人間失格'
        }
      }
    }))

    console.log(result)
  } catch (err) {
    console.error(err)
  }
})()
```

ポイントは以下の一行です。

```javascript
  const client = new DynamoDBClient({ endpoint: 'http://localhost:8000' });
```

DynamoDBClientを作成する際に、`endpoint`にコンテナ起動したDynamoDB LocalのURLを指定します。

### スクリプトを実行する

```bash
node index.js
```

コンソール上には以下のように出力されます。

```javascript
{
  '$metadata': {
    httpStatusCode: 200,
    requestId: '53cdcec1-7c53-4a4c-a73b-89b61b5f2238',
    extendedRequestId: undefined,
    cfId: undefined,
    attempts: 1,
    totalRetryDelay: 0
  },
  ConsumedCapacity: undefined,
  Item: { BookTitle: { S: '人間失格' }, Writer: { S: '太宰治' } }
}
```

dynamodb-adminを表示すると、以下の画面のようにちゃんと項目が入っていることが確認できます。

![FireShot Capture 020 - DynamoDB Admin - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/519262/c3e172d8-1072-2af8-bb7a-73fbb86353c1.png)

## 参考

- [DynamoDB ローカル (ダウンロード可能バージョン) のセットアップ](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DynamoDBLocal.html)
- [dynamodb-admin - npm](https://www.npmjs.com/package/dynamodb-admin)
- [DynamoDB Client - AWS SDK for JavaScript v3](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/globals.html)