【Serverless Framework】S3イベントでLambda起動をローカルで実行したい

[前記事](https://qiita.com/odayushin/items/3418bb06f16248b414d2)でserverless frameworkでのローカル環境構築方法を勉強してみたのですが、その続きみたいな記事になります。
今回はS3イベントをトリガーにしてLambdaを起動し、LambdaがS3やDynamoDBにアクセスするような構成を想定してローカルに動作確認用の環境を構築していきます。

## ローカルに構築するインフラ構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/360735/c73ab332-a831-ea58-fbf9-c579f4d26841.png)



## 必要な物
- Nodejs
- npm
- Java
- awscli

## 手順
やっていきます。

### プロジェクト作成

```shell:
$ npx serverless create -t aws-nodejs -p local-s3-lambda-app
$ cd local-s3-lambda-app
$ npm init -y
```

### 必要なモジュール類をインストール

```
$ npm i serverless serverless-dynamodb-local serverless-s3-local aws-sdk
```

### Lambdaとして動かす関数を用意
2つLambdaを作成します。

1. S3にオブジェクトをPutするLambda(trigger)
1. S3イベントでトリガーされるLambda(handler)

```javascript:s3_trigger.js
"use strict";
const AWS = require("aws-sdk");
const fs = require("fs");
const s3Client = new AWS.S3({
  s3ForcePathStyle: true,
  accessKeyId: "S3RVER",
  secretAccessKey: "S3RVER",
  endpoint: new AWS.Endpoint("http://localhost:4569"),
});

module.exports.trigger = async (event, context, callback) => {
  const uploadFile = fs.readFileSync("./upload_data/upload_data.json");
  const params = {
    Bucket: "local-bucket",
    Key: "upload_data.json",
    Body: uploadFile,
  };
  const response = await s3Client
    .putObject(params)
    .promise()
    .catch((err) => {
      console.log(err, err.stack);
    });
  console.log("OK");
  callback();
};

```


```javascript:handler.js

"use strict";
const AWS = require("aws-sdk");
const dynamodbClient = new AWS.DynamoDB({
  endpoint: "http://localhost:8000",
});

module.exports.handler = async (event) => {
  const params = {
    TableName: "SomeTable",
  };
  const response = await dynamodbClient
    .scan(params)
    .promise()
    .catch((err) => console.log(err));
  console.log(JSON.stringify(response, null, 1));
};

```

### serverless.ymlを修正
serverless.ymlにローカルで動かすS3とDynamoDBとLambdaを記述します。

```yml:serverless.yml
service: local-s3-lambda-app
configValidationMode: error
frameworkVersion: "2"

plugins:
  - serverless-s3-local
  - serverless-dynamodb-local

provider:
  name: aws
  runtime: nodejs12.x
  stage: dev
  region: ap-northeast-1
  lambdaHashingVersion: 20201221

custom:
  s3:
    host: localhost
    directory: ./local_s3
  dynamodb:
    stages:
      - dev
    start:
      port: 8000
      inMemory: true
      migrate: true
      seed: true
    seed:
      development:
        sources:
          - table: SomeTable
            sources: [./seed/records.json]

resources:
  Resources:
    NewResource:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: local-bucket
      Resources:
    SomeTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: SomeTable
        AttributeDefinitions:
          - AttributeName: attr1
            AttributeType: S
        KeySchema:
          - AttributeName: attr1
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1

functions:
  trigger:
    handler: s3_trigger.trigger
  handler:
    handler: handler.handler
    events:
      - s3:
          bucket: local-bucket
          event: s3:Put

```

### 必要なファイルを追加
S3にPutするファイルとDynamoDBに初期投入するデータを用意します。

S3用

```shell
$ mkdir upload_data
$ touch upload_data/upload_data.json
$ echo '{}' > upload_data/upload_data.json
```

DynamoDB用

```shell
$ mkdir seed
$ touch seed/records.json
```

```json:records.json
[
  {
    "attr1": "value1-1",
    "attr2": "value2-1",
    "attr3": "value3-1"
  },
  {
    "attr1": "value1-2",
    "attr2": "value2-2",
    "attr3": "value3-2"
  }
]
```

### localDynamoDBをインストール

```
$ $(npm bin)/sls dynamodb install
```

### ローカルで起動してみる
順番はこんな感じです

1. S3を起動する
1. DynamoDBを起動する
1. Lambda(s3_trigger)をinvoke localする

S3イベントがhandlerをinvokeしてDynamoDBのレコードがコンソールに出力されていればOKです。

```shell
$ $(npm bin)/sls s3 start
$ $(npm bin)/sls dynamodb start
$ $(npm bin)/sls invoke local -f trigger

{
 "Items": [
  {
   "attr2": {
    "S": "value2-2"
   },
   "attr1": {
    "S": "value1-2"
   },
   "attr3": {
    "S": "value3-2"
   }
  },
  {
   "attr2": {
    "S": "value2-1"
   },
   "attr1": {
    "S": "value1-1"
   },
   "attr3": {
    "S": "value3-1"
   }
  }
 ],
 "Count": 2,
 "ScannedCount": 2
}
```

無事出力されました！

## 終わりに
ローカルでS3イベントからLambda起動してDynamoDBの読み取りまでできちゃうのはすごいですね。
他にもapi-gatewayも動かせたりするので、まだまだローカルでの実行環境構築はいろんなことができそうです。
以上です。