API GatewayからPubSubバックエンドLambaに接続する。

## はじめに
前回までは以下の記事の内容で、Publisher Lambda⇒SNS⇒SQS⇒Subscriber Lambda⇒DynamoDBまでの流れを構築しました。

https://qiita.com/kennyQiita/items/e6d3ad182c3d32242c79

今回はこちらの構成に手を加えて、以下のようにUpdateしていきたいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/552710fc-e926-3807-99cf-8e71dc531009.png)
処理の流れを簡単に説明すると以下のようになります。
1. Postmanを使ってAPIリクエストを送信し、API Gatewayを介してバックエンドLambdaが起動する。
1. LambdaからSNSトピックが配信され、SQSキューに渡った後、Subscriber Lambdaが起動してDynamoDBに新規Itemを追加（Put）する。
1. DynamoDB Streamに新規Itemの追加情報が流れ、次のLambdaがトリガーされる。このLambdaで更にSNSトピックを配信する。
1. SNSにトピックが追加されたら、Userに処理完了通知をEmailで送信する。

## 前提条件
* Administrator権限を持つAWSアカウント
* LambdaはPython3.8で記述
* 上記別記事の部分は構築済

以下の順序で構築していきたいと思います。
1. API Gatewayの設定
1. メールを配信するSNSの設定
1. DynamoDB Streamにトリガーされ、SNSトピックを配信する追加Lambdaの構築
1. DynamoDB Streamの設定

## API Gatewayの設定
### API作成
今回はREST APIを作成していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/34dafd80-fb0b-b196-0cac-4ccc3d92e12d.png)
### Resource作成
APIが作成されたら、Resourceを作成していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/569c5b97-2ddd-d7fd-4895-91c67d84b54c.png)
### Method作成
作成したResourceに対してMethodを作成していきます。下図の`/test`は`/publishtopic`に読み替えてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/3ffb8200-7e1a-cda5-6bb3-018b57a54e25.png)
今回はLambdaを呼び出すので`Integration type`にLambdaを選択し、リクエストボディに後続処理で利用するメッセージを格納したいので`Use Lambda Proxy Integration`を`ON`にします。Lambda Functionでは作成済のSNSトピックを配信するLambdaを選択します。Lambda側の画面を見てみると、トリガーにAPI Gatewayが反映されていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/32fd48cf-0c47-d6df-404b-8f6e017d7bb3.png)

`Use Lambda Proxy Integration`を`ON`にすると、設定項目を大幅に減らすことが可能です。今回のようにリクエスト、レスポンスの変換を必要としない場合に有効です。
また、上記をONにした場合、LambdaにInputされるデータ（Lambda関数のパラメータeventに入る値）が決まった形式になり、LambdaからOutputするデータも決まった形式する必要があります。

* Lambdaの入力形式：https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format

今回は以下のような最低限のレスポンスをセットするようにLambda関数を変更しました。
```python
return {
        'statusCode': 200,
        'body': json.dumps('A new message has been published')
}
```
最後に作成したAPIをデプロイします。この手順は忘れがちです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/1ad1553b-cb80-e7c2-3826-3e46ab595b72.png)
デプロイが完了すると、以下のようにAPIのエンドポイントが作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/ce4887d4-4c42-33fc-c92e-44c1d4f854fa.png)

## メール配信を行うSNSトピックの作成
以下のようなSNSトピックとサブスクリプションを作成します。サブスクリプションにEmailを選択しており、指定したEmail宛にサブスクリプションの確認メールが届くのでAcceptします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/40f3062a-695e-89bd-ae50-751911140916.png)

## 追加Lambdaの作成
上記で作成したトピックを配信するLambdaを実装していきます。
```python

import json
import boto3

client = boto3.client('sns')

def lambda_handler(event, context):
    # Logging
    print('Lambda triggered by DynamoDB Strem.')
    
    # Get new item data
    item_data = event['Records'][0]['dynamodb']['NewImage']
    message = item_data['message']['S']
    print(item_data)
    
    # Set SNS Topic parameter
    params = {
        'TopicArn': 'arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:newItemOnDynamodb',
        'Subject' : 'Published From: getDataFromDynamodbStream',
        'Message' : message
    }
    
    # Send to SNS topic
    try:
        response = client.publish(
            TopicArn = params['TopicArn'],
            Subject = params['Subject'],
            Message = params['Message']
        )
    
        print(response)
        return {
            'statusCode': 200,
            'body': json.dumps(item_data)    
        }
    
    except Exception as e:
        print(e)
        raise e
```
DynamoDBストリームから渡される中身から、メッセージを取得しています。取得したメッセージをTopicの`Message`にセットし、Emailで確認できるようにしています。また、メールの件名も`Subject`でセットしています。

ちなみにDynamoDBストリームから渡ってくる中身は、以下のようなイメージです。
```json
{
  "Records": [
    {
      "eventID": "1",
      "eventVersion": "1.0",
      "dynamodb": {
        "Keys": {
          "Id": {
            "N": "101"
          }
        },
        "NewImage": {
          "Message": {
            "S": "New item!"
          },
          "Id": {
            "N": "101"
          }
        },
        "StreamViewType": "NEW_AND_OLD_IMAGES",
        "SequenceNumber": "111",
        "SizeBytes": 26
      },
      "awsRegion": "us-west-2",
      "eventName": "INSERT",
      "eventSourceARN": "arn:aws:dynamodb:us-west-2:111122223333:table/TestTable/stream/2015-05-11T21:21:33.291",
      "eventSource": "aws:dynamodb"
    }
  ]}
```
`Records[].dynamodb`の中に入っている`NewImage`から`Message`を取り出せばよいカタチです。

## DynamoDB Streamの設定
DynamoDBに新規Itemが追加された際に、上記で実装したLambdaが呼び出されるようにDynamoDB Streamを設定していきます。
Streamを有効化し、Streamにデータが流れるタイミングは新規Itemが追加された場合で、`New Image`が渡されればその内容（メッセージ）が取得できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/096954de-ad11-a819-77c8-2fe15b018294.png)
また、Triggerとして先ほど作成したLambdaを設定します。これで一通り構築できました。

## 動作確認テスト
動作確認をしてみます。まずはAPI GatewayからTest実行してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/836c3785-db62-a96a-96c8-2fa82d7994fd.png)
リクエストボディに以下のように文言をセットしてTest実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/20e070e0-15ff-3365-4d64-5426c68c0f3f.png)
最初のLambdaから`200`のレスポンスが返却されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/91b080a7-159d-9c46-70a4-aa66451db98c.png)
メールも届いています。動作していそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/897c78bb-fc79-b4c8-869e-9243146a1acb.png)

続いてAPIのエンドポイントを実際に叩いてテストしてみます。APIエンドポイントはAPI Gateway作成手順でAPIをDeployした際に確認しました。
こちらのエンドポイントをブラウザで叩いてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/bb690470-38dc-e8c1-b906-e93ea15d5d31.png)
エラーとなりました。最初は「え？？」となったのですが、ブラウザからのAPIコールはGETとなるので、今回はPOSTリクエストを送信しないといけません。
PostmanからPOSTリクエストを送信します。Bodyに任意の文字列を入れています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/00755c64-00a7-caf6-2ee9-9b8f9acb68a7.png)
ステータス`200`が返ってきています。
メールも届きました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/0dd90a43-eb20-1078-dae5-591ba2761198.png)
先ほどの実行と合わせて、DynamoDBにも新規Itemが追加されていることも確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/4aee5684-ca39-3618-f1b5-f62d19a23bac.png)

## おわりに
無事にAPI GatewayとDynamoDB Streamを追加したPubSubの仕組みを構築できました。ユーザーからのリクエスト処理と、バックエンドのロジックを切り離しできるこのような構成は、AWSのサービスを組み合わせて実装可能なことを確認できました。ユーザーからのAPIリクエストを受け取る際に、ユーザー側には処理時間を待たせることなく、処理完了したら通知したいような構成としたい場合は有効だと思います。

## 参考記事
以下参考にさせて頂いた記事です。

https://qiita.com/takmot/items/89d6ad6d4ddecaa1556c

https://qiita.com/minsu/items/c9e983f109b1cf5a516e

https://blog.serverworks.co.jp/tech/2020/02/07/apigw-lambda-response/

https://qiita.com/segawa/items/5c6d418019e0ca931dff