僕は何としてもEnter連打でAWS Lambdaの開発環境を作りたい

## はじめに

この記事は[Hamee Advent Calendar 2018](https://qiita.com/advent-calendar/2018/hamee) 16日目の記事です。
最近業務内でAWS Lambdaを使うため、勉強のために業務でもありそうなS3を使って並列で画像を並列アップロードする機能をGo言語 + AWS Lambdaを使って実装していたのですが、

 - 割とこの処理って似たような処理を書くことありそうだな...
 - 次同じようなもの作る時に**リポジトリから探してcloneして作り直すのは流石にめんどくさい**
 - 一回雛形作っちゃえば、割と後々楽になるかも？

と思い、雛形ジェネレーターツールのyeomanを使ってAWS Lambdaの開発環境を作ってみました。

## これだけは譲れないポイント

Enter連打でよしなにAWS Lambdaの開発環境を作成してくれるツールを作る。
サボれるところはとことんサボってやりたい。

## そこでyeomanですよ

[The web's scaffolding tool for modern webapps | Yeoman](https://yeoman.io/)

yeomanはコマンドラインからの対話形式で雛形を作成することが出来るツールです。
オワコンだとか囁かれることが多いけど、言語だけに特化しないで汎用的に使える雛形生成ツールとしては、割とすんなり自分は腑に落ちたので、yeomanを使いました。

## yeomanで雛形の開発あれこれ

ただ、自分のこれからのこのツールの展望としては今回の雛形に止まらず、他の雛形が欲しくなったときに追加しやすくしておきたい。

そこで、雛形の質問やフォルダを取得する部分は分けて、新しい雛形を追加する時はclass追加とテンプレート用のディレクトリを追加することで、新しい雛形をなるべく簡単に作れるようにしました。

例えば、質問で雛形を選択したタイミングで渡すオブジェクトは

```js:index.js
const boilerplates = {
  helloWorld: new HelloWorldBoilerPlate({
    name: 'helloWorld',
    needSetup: false
  }),
  s3Upload: new S3UploadBoilerPlate({
    name: 's3Upload',
    needSetup: true,
    awsRegions: awsRegions.list().map(awsRegion => {
      return { name: awsRegion.name, value: awsRegion.code };
    })
  })
};
```

で予め各実行雛形毎にオブジェクトを定義しておいて

```js:index.js
const prompts = [
  //...
  {
    type: 'list',
    name: 'boilerplate',
    message: 'What is the name to use boilerplate?',
    choices: [
      { name: 'Hello World', value: boilerplates.helloWorld },
      { name: 'S3 Upload', value: boilerplates.s3Upload }
    ],
    default: boilerplates.helloWorld
  }
];

this.props = await this.prompt(prompts);
this.props.boilerplateOptions = await this.prompt(
  this.props.boilerplate.getPrompts()
);
```

質問の回答を扱う値に各雛形オブジェクトを受け渡しています。
こうすれば後から雛形を追加する時も、クラスとオブジェクトを追加すれば雛形を追加しやすいです。
それと、最後の質問ごとの条件を次の質問でも使う、のような質問が多段的になったときに``this.prompt()``の処理がネストが深くて可読性が低くなるのが気持ち悪かったので、``async/await``に書き直しています。記述が簡素でいい感じ。
[Interacting With The User | Yeoman](https://yeoman.io/authoring/user-interactions.html)

地味にハマったのが、項目を入力している途中に、入力した時点よりも前に入力した値を使う必要がある分岐のやり方がわからず時間を浪費...
yeomanは要素の選択を[Inquirer.js](https://github.com/SBoudrias/Inquirer.js)を使っていて

```js:index.js
const prompts = [
  //...
  {
    type: 'input',
    name: 'fileCounts',
    message: 'How many images will you create?',
    default: '10',
    when: answer => {
      return answer.isCreateImages;
    }
  }
];
```

のようにwhenに関数を定義してあげて、true, falseをreturnで返せばこの質問の実行を制御できます。
しかも今まで選択、入力した質問も参照ができる。こりゃ便利。

## S3の並列画像アップロードの開発環境

S3を使って並列で画像を並列アップロードする機能の開発環境内で使うツールは

 - S3互換のオブジェクトストレージ
   - [minio](https://docs.minio.io)
 - AWS Lambdaの実行環境
  - [lambci/docker-lambda](https://github.com/lambci/docker-lambda)

を選択しています。

AWS Lambdaを使うために開発環境を汚したくはなかったので、dockerでAWS Lambdaを実行出来るlambci/docker-lambda、S3互換のオブジェクトストレージはLocalStackと比較してminioの方が超速だったのと、画面UIがminioは本当にリッチで、簡単な操作だったら画面上でも出来るのでminio使うかーといった感じです。
この二つのコンテナを

```sh
$ docker network create --driver bridge minio_network
```

のようにbridgeネットワークを定義しそのネットワーク内でコンテナ間の通信を行っています。
minioのコンテナは以下のように定義しています。regionは選択した値から取得できるようにしています。

```yml:minio/docker-compose.yml
version: "3"
services:
  minio:
    image: minio/minio
    ports:
      - "9000:9000"
    command: [server, /data]
    environment:
      - "MINIO_ACCESS_KEY=dummydummydummy"
      - "MINIO_SECRET_KEY=dummydummydummy"
      - "MINIO_REGION=<%= props.boilerplateOptions.region %>"
    restart: always
networks:
  default:
    external:
      name: minio_network
```

S3の並列画像アップロードは、S3へzipファイルが上がったことをトリガーとして、AWS Lambdaを実行し内部でgorutineを使って画像ファイルを保存したいS3に並列に画像をアップロードする実装を記述。実装内容は割とよくある実装だと思うので割愛しますが、テンプレートは[ここ](https://github.com/gtongy/generator-golang-lambda/tree/master/generators/app/templates/s3Upload)を踏んでいただけると見られます。

## こうしてgeneratorの完成

出来上がったものはこちら。

[gtongy/generator-golang-lambda](https://github.com/gtongy/generator-golang-lambda)

## それではいざ実行!

以下コマンドでgeneratorをインストールした後に起動して、テンプレートを選択しEnterを連打!

```sh
$ npm install -g yo
$ npm install -g generator-golang-lambda
$ yo golang-lambda
```

![generator-golang-lambda.gif](https://qiita-image-store.s3.amazonaws.com/0/194069/6bc4838d-80ce-dda3-48be-344a087d9257.gif)


minio管理画面上からbucketとEnter連打で作ったディレクトリに置かれたsample.zipをアップロードして

<img width="1437" alt="minio-management-view.png" src="https://qiita-image-store.s3.amazonaws.com/0/194069/3d7758e8-167b-b8bb-0117-86360094ff2b.png">  

``make docker-build``で、lambci/docker-lambdaをビルドしたのちに下のコマンドを実行すると...

```sh
$ make docker-run-minio
```

![golang-lambda-run.gif](https://qiita-image-store.s3.amazonaws.com/0/194069/d47ed98a-95a9-a2ef-b7a1-fe897e1a4d82.gif)

うまく実行されてますね!

minioの管理画面でもファイルがアップロードされているか確認してみると

<img width="1437" alt="スクリーンショット 2018-12-15 18.47.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/194069/cd0d29ad-c84e-f9c9-a61e-3120f15e0224.png">


画像も正常にアップロードされています。
ちなみに、dockerでコンテナを立ち上げるまでに、yeomanで事前にセットアップコマンドが実行されていて、Makefileに定義されたコマンドが実行されます。

```sh:Makefile
JSON_FILE=`node -p "JSON.stringify(require('./event.json'))"`

# create minio network
create-network-minio:
	docker network create --driver bridge minio_network

# minio container docker-compose up
fig-up-minio:
	docker-compose -f ./minio/docker-compose.yml up -d

# build lambci/lambda:build-go1.x container
docker-build:
	docker run --rm -v "$(PWD)":/go/src/handler \
	lambci/lambda:build-go1.x \
	sh -c 'dep ensure && go build -o handler *.go'

# lambci/lambda:build-go1.x run in container to use minio
docker-run-minio:
	docker run \
	--net="minio_network" \
	-e "AWS_LAMBDA_FUNCTION_MEMORY_SIZE=256" \
	-e AWS_ACCESS_KEY_ID="dummydummydummy" \
	-e AWS_SECRET_ACCESS_KEY="dummydummydummy" \
	-e AWS_DEFAULT_REGION="us-east-1" \
	--rm -v "$(PWD)":/var/task lambci/lambda:go1.x handler "$(JSON_FILE)"
```

Enterを連打すると、内部で以下コマンドをyeoman経由で実行します。

```sh
$ make create-network-minio
$ make fig-up-minio
$ make docker-build
```

なので、実際にAWS Lambdaを実行するコマンドも少なく済んで、爆速快適ああ楽チン!

## 終わりに

yeoman、界隈ではオワコンだとか書かれているのもあって、使うのを少しためらいましたが、汎用的な雛形作成ツールとして初めて書くときには**なんだ全然便利じゃないか**と感じるツールでした。
ただ初めてyeomanで雛形を作ったのもあって、これから拡張しやすいかこれから実装してみないとなんともといった感じなので、これから拡張していくときに使い勝手が分かっていくかなと。

AWS Lambdaは今回初めて触りましたが、このサーバーレスのサクッと環境を作れてしまうのと、コード実行時間単位での課金で低価格で試して即デプロイ出来るのはかなりシビれました。
ちょっとした機能を高速で作って試してみたい時に使いやすそうだなーと感じました。
サーバーレスを気軽に試せるので、一度AWS Lambda試してみてください！

## 参考文献, 書籍
 - [Goで学ぶAWS Lambda（PDF、ePubセット版） #技術書典 - カエルと空 - BOOTH](https://booth.pm/ja/items/1034858)
 - [GitHub - SBoudrias/Inquirer.js: A collection of common interactive command line user interfaces.](https://github.com/SBoudrias/Inquirer.js)
 - [The web's scaffolding tool for modern webapps | Yeoman](https://yeoman.io/)
 - [docker-lambdaでAWS Lambda環境をお手軽に動かす - Qiita](https://qiita.com/hihats/items/56005088f0f2018143cf)