AWS SDK for JavaScriptとDockerでAWS（S3、DynamoDB）のローカル開発をやってみる

# はじめに
ITニュースを漁っていると情報が目に入らない日はないくらい急速に拡大を進める__AWS（Amazon Web Services）__ですが、無料枠があるとはいえ中々手を出しにくいものかと思います。

本稿では、AWSの提供するサービスの中でも使用頻度が高そうな__S3（Amazon Simple Storage Service）__と__DynamoDB（Amazon DynamoDB）__をDockerを用いてローカルで開発してみた内容について記載していこうと思います。

# 実行環境
- Windows10 Pro
- Docker Desktop for Windows (WSL2)
- Node.js v14.15.3 (Vue CLI 3)　※必須ではない

# 主要技術概要
- AWS SDK for JavaScript：AWSのサービスを使用するためのJavaScriptライブラリ
- S3：AWSが提供するストレージサービス。
- DynamoDB：AWSが提供するフルマネージドNoSQLデータベース。Key-Value構造。

# 開発準備
Docker、Node.jsのインストールは省略させていただきます。

## S3
今回はS3と互換のある[__Minio__](https://hub.docker.com/r/minio/minio/)を使用します。
アクセスキーやシークレットキーはIAMの使用なども含め工夫されると思いますが、ローカルなので気にせず公式の内容を流用します。

```Bash
docker run -p 9000:9000 \
  -e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
  -e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
  minio/minio server /data
```

上記コマンドが正常に起動している場合は9000番のポートをたたくと、以下の画像のような画面が表示されるはずです。
![minio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/969561/835064ff-aff1-d1f4-ec6e-65b07c7839eb.png)

docker起動時に指定したアクセスキー、シークレットキーを入れると、この時点でバケットの作成やファイルのアップロード・ダウンロードを行うことが可能となります。
UIも格好よく、機能も多いのでAWS以外の利用用途もありそうですね。

## DynamoDB
DynamoDBはAWSの公式がDockerイメージを公開してくれています。
ありがたく使用させていただきましょう。

```Bash
docker run -d --name dynamodb -p 8000:8000 amazon/dynamodb-local
```

S3とDynamoDBの起動が上手くいった場合は起動中のコンテナを確認してみます。

```Bash
$ docker ps
CONTAINER ID   IMAGE                   COMMAND                  CREATED             STATUS             PORTS                    NAMES
5e467706a247   amazon/dynamodb-local   "java -jar DynamoDBL…"   About an hour ago   Up About an hour   0.0.0.0:8000->8000/tcp   dynamodb
113388461d4a   minio/minio             "/usr/bin/docker-ent…"   11 hours ago        Up 11 hours        0.0.0.0:9000->9000/tcp   fervent_chaplygin
```

ステータスなどに異常がなければ問題ないと思います。

## AWS SDK for JavaScript
AWSのSDKはPythonやGo Langなどサーバサイドの言語用のものもありますが、フロントサイドの方が確認しやすいと思うので今回はJS用のものを使用します。

CDN：

```HTML
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.819.0.min.js"></script>
```

Node.js：

```Bash
npm install aws-sdk
```

その他の環境の場合は[こちら](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/index.html)

# コーディング
今回はS3のアップロード・ダウンロードおよびDynamoDBのDB登録・取得を一通り行っていきます。
今回作成した実装画面・実装内容は以下のとおりです。
sample:
https://github.com/kanaria42/aws-local-test

サンプルの実装内容：

- 画面中央のファイル追加ボタンより画像ファイル（画面下部の果物の画像）を追加。
- 画面上部のアップロードボタンより追加したファイルをS3にアップロード。この時登録キーなどをDynamoDBに保存。
- 初期表示・アップロード時にDynamoDBに登録した画像一覧を取得し画面上部の一覧に表示。ない場合は空。
- 一覧の任意の一行を選択し画面右上のDownloadボタンを押下するとS3より画像をダウンロード。

## S3
### 事前準備（バケット作成）
Webアプリケーション内で作成しても良いですが、作成は一度きりなため別途Nodeから実行します。
処理としてはS3のインスタンスを作成してcreateBucketによりバケットを作成するだけですね。
繰り返しになりますが、実際の運用ではアクセスキーを中に書くことはないと思います。

```js:参考：src/app/createBucket.js
const AWS = require("aws-sdk");

const s3 = new AWS.S3({
  accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
  secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
  endpoint: 'http://127.0.0.1:9000',
  s3ForcePathStyle: true,
  signatureVersion: 'v4'
});

s3.createBucket({Bucket: [バケット名]}, function(err, data) {
  if (err) {
    console.error("Unable to create bucket. Error JSON:", JSON.stringify(err, null, 2));
  } else {
    console.log("Created bucket. Bucket description JSON:", JSON.stringify(data, null, 2));
  }
});
```

### アップロード
アップロードは「putObject」などを使用します。
公式のリファレンスではcallbackの形式になっていますがpromiseも使用できます。
サンプルの実装ではファイル名をそのままkeyに設定していますが、同一ファイル名の別ファイルがあると上書きされてしまうのでご注意を。

```js
s3.putObject({Bucket: [バケット名], Key: [S3登録名], Body: [Fileデータ]}).promise();
```

### ダウンロード
ダウンロードは「getObject」などを使用します。
アップロード時に指定したキーで取得可能です。

```js
s3.getObject({Bucket: [バケット名], Key: [S3登録名]}).promise();
```

## DynamoDB
### 事前準備（テーブル作成）
S3と同様にテーブルも一度のみで良いので別途事前に作成してしまいます。
テーブルの定義の与え方はCroudFormationでリソースを作成する時とあまり変わりがありません。
例では「Name」をプライマリキーに設定します。

```js:参考：src/app/createTable.js
const AWS = require("aws-sdk");

const dynamodb = new AWS.DynamoDB({
  endpoint: 'http://127.0.0.1:8000', 
  region: 'ap-northeast-1',
  accessKeyId: 'fakeMyKeyId',
  secretAccessKey: 'fakeSecretAccessKey'
});

const params = {
  AttributeDefinitions: [
    {
      AttributeName: "Name", 
      AttributeType: "S"
    }
  ],
  KeySchema: [
    {
      AttributeName: "Name", 
      KeyType: "HASH"
    }
  ], 
  ProvisionedThroughput: {
    ReadCapacityUnits: 5, 
    WriteCapacityUnits: 5
  },
  TableName: [テーブル名]
};

dynamodb.createTable(params, function(err, data) {
  if (err) {
    console.error("Unable to create table. Error JSON:", JSON.stringify(err, null, 2));
  } else {
    console.log("Created table. Table description JSON:", JSON.stringify(data, null, 2));
  }
});
```

### 登録
登録は「putItem」などを使用します。
テーブル作成時に設定した「Name」の他にいくつか別の項目も登録する例が以下になります。
「Name」などの下にある「S」は文字列をテーブル定義の型を表します。

```js
const item = {Name: {S: [ファイル名]}, Type: {S: [ファイル種別]}, Size: {S: [ファイルサイズ]}};
dynamodb.putItem({Item: item, TableName: this.TABLE_NAME}).promise());
```

### 取得（全件）
登録データの全件取得は「scan」などを使用します。
1件取得の場合はgetItemでキーを指定する形ですね。
実利用ではLimitオプションなどを付加して制限したりするのではないかと思います。

```js
dynamodb.scan({TableName: [テーブル名]}).promise();
```

## 動作確認
4件の画像データの登録などを行った画面です。
DynamoDBのデータ登録から取得、S3のアップロード、ダウンロードが確認できました。
作成している間に楽しくなって本筋でないUIの実装などのほうが時間がかかったのはここだけの話。

Web画面：
![demo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/969561/517d62f7-e22f-40a7-da1f-045a34d0cbfb.png)

Minio：
![s3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/969561/2980ae5b-b05a-7df0-ff7d-b794a894adde.png)

# まとめと所感
以下、簡単なまとめと実装してみての所感になります。

- S3・DynamoDBなどは対応するDockerイメージが公開されておりローカルでも開発可能
- AWSのSDKなども上記環境で使用できる
- 本家AWSは課金される可能性があるため可能であればこちらを使用するほうが良さそう
- ただし本番環境との環境分けで苦労しそうな気がする

Qiitaさんは初投稿かつAWS関連も触り始めて日が浅いため、記述不備・認識間りなどがあればご指摘いただけば幸いです。

# 参考文献
[DynamoDB Localの導入](https://qiita.com/gzock/items/e0225fd71917c234acce)
[S3互換のオブジェクトストレージminioを試す](https://dev.classmethod.jp/articles/s3-compatible-storage-minio/)