Lambda + DynamoDB を利用したサーバレス構築ハンズオン

## はじめに
普段DynamoDBをあまり使用しないので、CFnの練習がてら構築していきたいと思います。

学習に利用した教本は、[AWS Lambda実践ガイド 第2版 (impress top gear) 著:大澤文孝](https://www.amazon.co.jp/AWS-Lambda%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89-%E7%AC%AC2%E7%89%88-impress-gear/dp/4295013307/ref=asc_df_4295013307/?tag=jpgo-22&linkCode=df0&hvadid=553954433678&hvpos=&hvnetw=g&hvrand=2164190274868650654&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009299&hvtargid=pla-1599229625688&psc=1&th=1&psc=1)
を利用しています。
GUI及び、SAMによる構築はありましたが、CFnでの構築はなかったので、書籍のエッセンスを使いつつ、自分なりに少しアーキテクトを変更して構築をしてみました。

------------

## 構成図
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-15-15.59.50-640x423.png)


------------

## ハンズオン
### 構築のながれ
#### 1.S3作成：SNSメール雛形保存
#### 2.SNS作成：メール通知のため構築
#### 3.SQS作成:LambdaとLambdaを疎結合で構築
#### 4.Lambda(sendmail)作成:メール未送信ユーザにメール通知の構築
#### 5.DynamoDB作成:ユーザ名/アドレス/二重配信チェック DBの構築
#### 6.Lambda(sendqueue)作成:DynamoDBに該当カラムを持つ場合　SQSを発行する構築
#### 7.EventBridge作成:CronでLambda(sendqueue)とトリガーを構築

-----------

### 1.S3作成：SNSメール雛形保存
#### 1.1.CFn で S3 構築
・`SNS`で送信する通知内容を保存するためのバケットを作成
```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation to create S3 Bucket
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "S3 Configuration"
        Parameters:
        - S3BucketName
        - AccessControl
        - BlockPublicAcls
        - BlockPublicPolicy
        - IgnorePublicAcls
        - RestrictPublicBuckets
        - ExpirationInDays
        - EventBridgeConfiguration
        - Prefix
        - TagsName

# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  S3BucketName:
    Type: String
    Default: "cfn-s3-20230102-inamura"
    Description: Type of this BacketName.
  VersioningConfiguration:
    Type: String
    Default: "Enabled"
    Description: VersioningConfiguration.
  AccessControl:
    Type: String
    Description: AccessControl.
    Default: "Private"
    AllowedValues: [ "Private", "PublicRead", "PublicReadWrite", "AuthenticatedRead", "LogDeliveryWrite", "BucketOwnerRead", "BucketOwnerFullControl", "AwsExecRead" ]
  BlockPublicAcls: 
    Type: String
    Description: BlockPublicAcls.
    Default: "True"
    AllowedValues: [ "True", "False" ]
  BlockPublicPolicy:
    Type: String
    Description: BlockPublicPolicy.
    Default: "True"
    AllowedValues: [ "True", "False" ]
  IgnorePublicAcls:
    Type: String
    Description: IgnorePublicAcls.
    Default: "True"
    AllowedValues: [ "True", "False" ]
  RestrictPublicBuckets:
    Type: String
    Description: RestrictPublicBuckets.
    Default: "True"
    AllowedValues: [ "True", "False" ]
  ExpirationInDays:
    Type: String
    Description: Lifecycle Days.
    Default: "7"
  TagsName:
    Type: String
    Description: UserName
    Default: "inamura"
  
# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
#  S3
# ------------------------------------------------------------#
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref S3BucketName      
      VersioningConfiguration:
        Status: !Ref VersioningConfiguration
      AccessControl: !Ref AccessControl
      PublicAccessBlockConfiguration:
        BlockPublicAcls: !Ref BlockPublicAcls
        BlockPublicPolicy: !Ref BlockPublicPolicy
        IgnorePublicAcls: !Ref IgnorePublicAcls
        RestrictPublicBuckets: !Ref RestrictPublicBuckets
      LifecycleConfiguration:
        Rules:
          - Id: LifeCycleRule
            Status: Enabled
            ExpirationInDays: !Ref ExpirationInDays
    # #イベント通知 Lambda構築後コメントアウト解除
    #  NotificationConfiguration:
    #    LambdaConfigurations:
    #      - Event: "s3:ObjectCreated:*"
    #        Function: !ImportValue cfn-lmd-sendqueue-inamura-arn
    # #ここまで
      Tags:
        - Key: "User"
          Value: !Ref TagsName
# ------------------------------------------------------------#
#  Outputs
# ------------------------------------------------------------#
Outputs:
  S3BucketName:
    Value: !Ref S3Bucket
    Export:
      Name: cfn-s3-BucketName
```
<br>

#### 1.2.S3 に SNSメール雛形保存
`template-mail.txt`として`SNS`で送信する通知内容を、S3へ保存する

```txt
メール配信:テスト

【テスト内容】新規ユーザが登録されました
```
コンソール画面からだと下記のような状態となる
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-15-11.29.45-640x135.png)
<br>
### 2.SNS作成：メール通知のため構築
##### 2.1 SNSを構築する
・`EventBridge`をトリガーにして、`DynamoDB`に登録したユーザにメール通知がされていない場合に、こちらの`SNS`を利用してメールが通知される
・`TopicPolicy`部分は、検証のため制限していません。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: SNS Create

# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "SNS Configuration"
        Parameters:
        - TopicName
        - Endpoint
    
    ParameterLabels:
      TopicName:
        default: "TopicName"
      Endpoint:
        default: "MailAddress"

# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  TopicName:
    Type: String
    Default: "cfn-sns-topic-inamura"
  Endpoint:
    Type: String
    Default: "XXXXXXXXXX@gmail.com"
  TagsValueUserName:
    Type: String
    Default: "inamura"

# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
  SNSTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: !Ref TopicName
      Subscription:
        - Endpoint: !Ref Endpoint
          Protocol: email
      Tags:
        - Key: "User"
          Value: !Ref TagsValueUserName    

  TopicPolicy:
    Type: AWS::SNS::TopicPolicy
    Properties:
      Topics:
        - !Ref SNSTopic
      PolicyDocument:
        Id: !Ref SNSTopic
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS: "*"
            Action: SNS:Publish
            Resource: !Ref SNSTopic

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#                
Outputs:
  SNSTopic:
    Value: !GetAtt SNSTopic.Arn
    Export:

      Name: !Sub "${TopicName}-arn"
# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#                
Outputs:
  SNSArn:
    Value: !Ref SNSTopic
    Export:
      Name: !Sub "${TopicName}-arn"
  SNSTopicName:
    Value: !Ref TopicName
    Export:
      Name: !Ref TopicName
```
<br>

#### 2.2 上記設定後に、SNSから送られてきたメールのサブスクリプションを押下する
①送られてきたメールの赤枠部分をクリックする
![](https://cloud5.jp/wp-content/uploads/2022/11/スクリーンショット-2022-11-26-14.13.42-640x157.png)

②画面が遷移して下記画面が表示されると、サブスクリプションが開始される
![](https://cloud5.jp/wp-content/uploads/2022/11/スクリーンショット-2022-11-26-14.15.17.png)
※上記青枠部分をクリックすると、サブスクリプションが解除される

<br>

### 3.SQS作成:LambdaとLambdaを疎結合で構築

・2つの処理をする`Lambda`の間を疎結合するために構築
・今回はDynamoDBとLambda側で二重配信チェックを含めたため、標準キューを利用
・`可視性タイムアウト`を`Lambda`のタイムアウトを10秒としているため30秒と設定
・10分ごとにCronされるため保持期間を9分と設定
・2回通知を失敗すると、`DLQ`へ移行し、`DLQ`のメッセージ保持期間は1時間と設定

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: SQS Create
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "SQS Configuration"
        Parameters:
        - QueueName
        - DeadLetterQueueName
        - TagsValue

    ParameterLabels:
      QueueName:
        default: "QueueName"
      DeadLetterQueueName:
        default: "DeadLetterQueueName"
      TagsValue:
        default: "TagsValue"

# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  QueueName:
    Type: String
    Default: "cfn-sqs-inamura"
  DeadLetterQueueName:
    Type: String
    Default: "cfn-dlq-inamura"
  TagsValue:
    Type: String
    Default: "inamura"

# ------------------------------------------------------------#
#  SQS
# ------------------------------------------------------------#
Resources:
  NotifySQS:
    Type: AWS::SQS::Queue
    Properties: 
      QueueName: !Ref QueueName
      VisibilityTimeout: 30
      MessageRetentionPeriod: 540
      RedrivePolicy:
        deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn
        maxReceiveCount: 2
      Tags: 
        - Key: "User"
          Value: !Ref TagsValue
    
  DeadLetterQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Ref DeadLetterQueueName
      MessageRetentionPeriod: 3600
      Tags: 
        - Key: "User"
          Value: !Ref TagsValue
# ------------------------------------------------------------#
#  SQS QueuePolicy
# ------------------------------------------------------------#    
  SQSPolicy: 
    Type: AWS::SQS::QueuePolicy
    Properties: 
      Queues: 
        - !Ref NotifySQS
      PolicyDocument: 
        Statement: 
          - 
            Action: 
              - "SQS:*"
            Effect: "Allow"
            Resource: !GetAtt NotifySQS.Arn
            Principal:  
              AWS: 
                - "*"  
          - 
            Action: 
              - "SQS:*"
            Effect: "Allow"
            Resource: !GetAtt DeadLetterQueue.Arn
            Principal:  
              AWS: 
                - "*"  

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#                
Outputs:
  NotifySQS:
    Value: !GetAtt NotifySQS.QueueName
    Export:
      Name: !Sub "${QueueName}-queuename"
  QueueArn:
    Value: !GetAtt NotifySQS.Arn
    Export:
      Name: !Sub "${QueueName}-queuearn"
  DeadLetterQueue:
    Value: !GetAtt DeadLetterQueue.QueueName
    Export:
      Name: !Sub "${DeadLetterQueueName}-queuename"
  DeadLetterQueueArn:
    Value: !GetAtt DeadLetterQueue.Arn
    Export:
      Name: !Sub "${DeadLetterQueueName}-queuearn"
  
```
<br />

#### 4.Lambda(sendmail)作成:メール未送信ユーザにメール通知の構築
・`SQS`からメッセージを取得
・`S3`に保存している`template-mail.txt`より`0行目`と`2行目`を取得してメールで必要な変数を取得する
・`DynamoDB`の`send`を`1`に`update`する。ただしReturnする値はアップデート前のものとすることで、既に送信したのか？していないのか？を判定
・上記判定をして`update`する前の値が`0`であれば送っていないことになるので`SNS`でメール通知する
・メール通知したのでカラム`notsend`を`1`にすることで、次回の判定から除かれるように更新
※ IAM部分のアカウトIDは `XXXXXXXXXXXX`となっていますので、自身のアカウントIDを利用ください

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description:
  Lambda Create
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Lambda Configuration"
        Parameters:
        - FunctionName
        - Description
        - Handler
        - MemorySize
        - Runtime
        - Timeout
        - TagsName

# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  FunctionName:
    Type: String
    Default: "cfn-lmd-sendmail-inamura"
  Description:
    Type: String
    Default: "cfn-lmd-sendmail-inamura"
  Handler:
    Type: String
    Default: "index.lambda_handler"
  MemorySize:
    Type: String
    Default: "128"
  Runtime:
    Type: String
    Default: "python3.9"
  Timeout:
    Type: String
    Default: "10"
  TagsName:
    Type: String
    Description: UserName
    Default: "inamura"
# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
#  Lambda
# ------------------------------------------------------------#
  Lambda:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code:
        ZipFile: |
          import boto3
          import json
          import os
          import datetime

          sqs = boto3.resource('sqs')
          s3 = boto3.resource('s3')
          dynamodb = boto3.resource('dynamodb')
          table = dynamodb.Table('mailaddress')
          sns = boto3.client('sns')
          snsarn = os.environ['SNSARN']

          def lambda_handler(event, context):
            #SQSからメッセージ取得
            for rec in event['Records']:
              email = rec['body']
              bucket = rec['messageAttributes']['bucket']['stringValue']
              template = rec['messageAttributes']['template']['stringValue']
              username = rec['messageAttributes']['username']['stringValue']

              #S3メールテンプレートから本文生成
              obj = s3.Object(bucket, template)
              r = obj.get()
              maildata = r['Body'].read().decode('utf-8')
              data = maildata.split("\n", 3)
              subject =  data[0]
              body = data[2]

              #2重通知防止の仕組み
              #DynamoDB sendするので1をUpdate ただしReturnでUpdate前を返す
              r = table.update_item(
                Key = {
                  'email' : email
                  },
                  UpdateExpression = "set send=:val",
                  ExpressionAttributeValues = {
                    ':val' : 1
                  },
                  ReturnValues = 'UPDATED_OLD'
                  )
              
              #条件分岐 未送信(Updateする前の UPDATED_OLD が 0) の場合SNS送信 
              if r['Attributes']['send'] == 0:
                date = datetime.datetime.now()
                d = date.strftime('%Y%m%d %H:%M:%S')
    
                params = {
                'TopicArn': snsarn,
                'Subject': subject + str(d),
                'Message': body
                }
                sns.publish(**params)
              
                #DynamoDB sendしたので、notsendを1にUpdate
                r =table.update_item(
                  Key = {
                    'email' : email
                    },
                    UpdateExpression = "set notsend=:val",
                    ExpressionAttributeValues = {
                      ':val' : 1
                    }
                  )
              else:
                print("Resend Skip")
              
      Description: !Ref Description
      FunctionName: !Ref FunctionName
      Handler: !Ref Handler 
      MemorySize: !Ref MemorySize
      Runtime: !Ref Runtime
      Timeout: !Ref Timeout
      Role: !GetAtt LambdaRole.Arn
      Environment:
        Variables:
          TZ: "Asia/Tokyo"
          SNSARN: !ImportValue cfn-sns-topic-inamura-arn
      Tags:
        - Key: "User"
          Value: !Ref TagsName
      DeadLetterConfig:
        TargetArn: !ImportValue cfn-dlq-inamura-queuearn

  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${FunctionName}-role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action: "sts:AssumeRole"
            Principal:
              Service: lambda.amazonaws.com
      Policies:
        - PolicyName: !Sub "${FunctionName}-policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                  - "logs:CreateLogGroup"
                Resource: !Sub "arn:${AWS::Partition}:logs:*:*:*"

              - Effect: "Allow"
                Action:
                  - "sqs:*" 
                Resource: "arn:aws:sqs:ap-northeast-1:XXXXXXXXXXXX:*"

              - Effect: "Allow"
                Action:
                  - "s3:*" 
                Resource: "*"

              - Effect: "Allow"
                Action:
                  - "dynamodb:*" 
                Resource: "arn:aws:dynamodb:ap-northeast-1:XXXXXXXXXXXX:*"

              - Effect: "Allow"
                Action:
                  - "sns:Publish" 
                Resource: "*"

  EventSourceMapping:
    Type: AWS::Lambda::EventSourceMapping
    Properties: 
      Enabled: true
      EventSourceArn: !ImportValue cfn-sqs-inamura-queuearn
      FunctionName: !GetAtt Lambda.Arn
      BatchSize: 10

# ------------------------------------------------------------#
# Output Parameters
#------------------------------------------------------------#          
Outputs:
  LambdaArn:
    Value: !GetAtt Lambda.Arn
    Export:
      Name: !Sub "${FunctionName}-arn"
  LambdaName:
    Value: !Ref FunctionName
    Export:
      Name: !Sub "${FunctionName}-name"
```

<br />

### 5.DynamoDB作成:ユーザ名/アドレス/二重配信チェック DBの構築
#### 5.1.CFnでDynamoDBを構築
```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description:
  DynamoDB Create
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "DynamoDB Configuration"
        Parameters:
        - TableName
# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  TableName:
    Type: String
    Default: "mailaddress"

# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
  DynamoDB:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      TableName: !Ref TableName
      AttributeDefinitions:
        - AttributeName: email
          AttributeType: S
        - AttributeName: notsend
          AttributeType: N
      KeySchema:
        - AttributeName: email
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1
      GlobalSecondaryIndexes:
        - IndexName: notsend-index
          KeySchema:
            - AttributeName: notsend
              KeyType: HASH
          Projection:
            ProjectionType: ALL
          ProvisionedThroughput:
            ReadCapacityUnits: 1
            WriteCapacityUnits: 1
      Tags: 
        - Key: "username"
          Value: "inamura"

Outputs:
  DynamoDBArn:
    Value: !GetAtt DynamoDB.Arn
    Export:
      Name: !Sub "cfn-dynamodb-${TableName}"
```
#### 5.2.DynamoDBに値を登録
・`DynamoDB`に値を登録するために`AWS CLI`を利用
##### 5.2.1.jsonファイルを用意
```json
{
  "mailaddress": [
      {
          "PutRequest": {
              "Item": {
                  "email": {"S":"XXXXXXXXXX@gmail.com"},
                  "username": {"S":"InamuraTeppei"},
                  "notsend": {"N":"0"},
                  "send": {"N":"1"}
              }
          }
      }
  ]
}
```
<br />

##### 5.2.2.AWS CLIを利用してデータをインポート
・下記コマンドをターミナルから入力して`DynamoDB`のテーブルへ反映させる
```yaml
aws dynamodb batch-write-item --request-items file://mailaddress.json
```
<br />

##### 5.2.3.マネジメントコンソールから確認
・`DynamoDB` > 左ペイン：テーブル　の　`項目を探索` > 確認したいテーブルを選択
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-15-13.46.30-640x269.png)

<br />

### 6.Lambda(sendqueue)作成:DynamoDBに該当カラムを持つ場合　SQSを発行する構築
・後続手順で`EventBridge`を構築した後にコメントアウトしている`パーミッション(AWS::Lambda::Permission)`部分を修正する
・`DynamoDB`のテーブルから `notsend`が 0 のアイテムを取得
・取得したアイテムの`send`を 0 に更新
→ `notsend`が 0 であれば、一度送っていてもSQSで後続のLambda(sendmail)へ値を渡し、後続Lambdaで`send`しているかどうか？をチェックする仕組み
・`SQS`にメッセージとして登録
※ IAM部分のアカウトIDは `XXXXXXXXXXXX`となっていますので、自身のアカウントIDを利用ください

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description:
  Lambda Create
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Lambda Configuration"
        Parameters:
        - FunctionName
        - Description
        - Handler
        - MemorySize
        - Runtime
        - Timeout
        - TagsName

# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  FunctionName:
    Type: String
    Default: "cfn-lmd-sendqueue-inamura"
  Description:
    Type: String
    Default: "cfn-lmd--sendqueue-inamura"
  Handler:
    Type: String
    Default: "index.lambda_handler"
  MemorySize:
    Type: String
    Default: "128"
  Runtime:
    Type: String
    Default: "python3.9"
  Timeout:
    Type: String
    Default: "10"
  TagsName:
    Type: String
    Default: "inamura"
# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
#  Lambda
# ------------------------------------------------------------#
  Lambda:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code:
        ZipFile: |
          import boto3
          import urllib.parse
          import json
          from boto3.dynamodb.conditions import Key, Attr
          import os

          BUCKET = os.environ['S3']
          TEMPLATE = os.environ['TEMPLATE']

          def lambda_handler(event, context):
            print('Loading function')

            #DynamoDB boto3操作 Tableオブジェクト取得
            dynamodb = boto3.resource('dynamodb')
            table = dynamodb.Table('mailaddress')

            #SQS boto3操作 キュー取得
            sqs = boto3.resource('sqs')
            queue = sqs.get_queue_by_name(QueueName='cfn-sqs-inamura')
            
            #環境変数からバケット名 オブジェクト名取得
            bucket = BUCKET
            template = TEMPLATE
            
            #DynamoDB(table) から notsend 0 を絞り込んで取得
            response = table.query(
              IndexName='notsend-index',
              KeyConditionExpression=Key('notsend').eq(0)
            )

            #絞り込んだテーブル情報 response['Item']から send 0 にプリペーアドクエリ更新
            for item in response['Items']:
              table.update_item(
                Key={'email' : item['email']},
                UpdateExpression="set send=:val",
                ExpressionAttributeValues= {
                  ':val' : 0
                }
              )

              #SQSにメッセージとして登録
              sqsresponse = queue.send_message(
                MessageBody=item['email'],
                MessageAttributes={
                  'username' : {
                    'DataType' : 'String',
                    'StringValue' : item['username']
                  },
                  'bucket' : {
                    'DataType' : 'String',
                    'StringValue' : bucket
                  },
                  'template' : {
                    'DataType' : 'String',
                    'StringValue' : template
                  }
                }
              )
              
              #ログ出力
              print(json.dumps(sqsresponse))

      Description: !Ref Description
      FunctionName: !Ref FunctionName
      Handler: !Ref Handler 
      MemorySize: !Ref MemorySize
      Runtime: !Ref Runtime
      Timeout: !Ref Timeout
      Role: !GetAtt LambdaRole.Arn
      Environment:
        Variables:
          ##削除する際は TriggerLambdaPermission をコメントアウトして Lambda を更新する
          SNS: !ImportValue cfn-sns-topic-inamura-arn
          S3: !ImportValue  cfn-s3-BucketName
          ##ここまで
          TEMPLATE: template-mail.txt 
      Tags:
        - Key: "User"
          Value: !Ref TagsName
      DeadLetterConfig:
        TargetArn: !ImportValue cfn-dlq-inamura-queuearn

  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${FunctionName}-role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action: "sts:AssumeRole"
            Principal:
              Service: lambda.amazonaws.com
      Policies:
        - PolicyName: !Sub "${FunctionName}-policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                  - "logs:CreateLogGroup"
                Resource: !Sub "arn:${AWS::Partition}:logs:*:*:*"

              - Effect: "Allow"
                Action:
                  - "sqs:*" 
                Resource: "arn:aws:sqs:ap-northeast-1:XXXXXXXXXXXX:*"

              - Effect: "Allow"
                Action:
                  - "s3:*" 
                Resource: "arn:aws:s3:::*"

              - Effect: "Allow"
                Action:
                  - "dynamodb:*"  
                Resource: "arn:aws:dynamodb:ap-northeast-1:XXXXXXXXXXXX:*"

  # #削除する際は TriggerLambdaPermission をコメントアウトして Lambda を更新する
  # TriggerLambdaPermission:
  #   Type: AWS::Lambda::Permission
  #   Properties:
  #     FunctionName: !Ref FunctionName
  #     Action: lambda:InvokeFunction
  #     Principal: events.amazonaws.com
  #     SourceArn: !ImportValue cfn-evb-checkdynamodb-inamura-arn
  # #ここまで

# ------------------------------------------------------------#
# Output Parameters
#------------------------------------------------------------#          
Outputs:
  LambdaArn:
    Value: !GetAtt Lambda.Arn
    Export:
      Name: !Sub "${FunctionName}-arn"
  LambdaName:
    Value: !Ref FunctionName
    Export:
      Name: !Sub "${FunctionName}-name"
```
<br />

### 7.EventBridge作成:CronでLambda(sendqueue)とトリガーを構築
####　7.1.CFnでEventBridge構築
```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description:
  EventBridge gets s3 events and sends them to lambda
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Eventbridge Configuration"
        Parameters:
          - RuleName
          - EventBusName
          - State
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------# 
Parameters:
  EventBusName:
    Type: String
    Default: "default"
  RuleName:
    Type: String
    Default: "cfn-evb-checkdynamodb-inamura"
  State:
    Type: String
    Default: "ENABLED"

# ------------------------------------------------------------#
#  EventBridge
# ------------------------------------------------------------#
Resources:
  EventsRuleCron:
    Type: AWS::Events::Rule
    Properties:
      Description: "Check DynamoDB and send to Lambda"
      EventBusName: !Ref EventBusName
      Name: !Ref RuleName
      State: !Ref State
      ScheduleExpression: cron(0/10 * * * ? *)
      Targets:
        - Arn: !ImportValue cfn-lmd-sendqueue-inamura-arn
          Id: cfn-lmd-inamura

# ------------------------------------------------------------#
# Output Parameters
#------------------------------------------------------------#          
Outputs:
  EventBridgeArn:
    Value: !GetAtt EventsRuleCron.Arn
    Export:
      Name: !Sub "${RuleName}-arn"
```
<br />


####　7.2.手順6.で構築したLambdaのコメントアウト部分を修正し更新する
【修正箇所のみ抜粋】
・`！ImportValue`で`EventBridge`が作成されていないと構築できなかった
```yaml
  TriggerLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref FunctionName
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn: !ImportValue cfn-evb-checkdynamodb-inamura-arn
```
<br />

### 挙動の確認
前提条件：EventBridgeが10分ごとにLambdaをキックするので、10/20・・・分に起動する
#### ①メールが届いたか確認する
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-15-14.46.12.png)
#### ②LambdaのCloudWatchLogsの出力のされ方
【`sendqueue`のCloudWatchLogs】
・`EventBridge`で`Cron`されるたびに`Lambda`が実行している
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-15-14.56.51-640x129.png)

【`sendmail`のCloudWatchLogs】
・`DynamoDB`のカラム`notsend`が 1 のため Queue が発行されず実行されない
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-15-14.56.33-640x90.png)
<br />

#### 【届いていない場合①】Lambda(sendqueue)のCloudWatchLogsを確認する
・前半の処理部分（EventBridge、DynamoDB、SQS系があやしい）のエラーの場合
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-15-14.39.43-640x244.png)
#### 【届いていない場合②】Lambda(sendmail)のCloudWatchLogsを確認する
・後半の処理部分（SQS、DynamoDB、SNS系があやしい）のエラーの場合
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-15-14.44.58-640x75.png)
<br />

### 削除順番
__基本的には構築した順番の逆ですが、`!ImportValue`をしているので削除順がややこしくなっている部分があります。__

1.`Lambda(sendqueue)`の`Environment`部分及び、`TriggerLambdaPermission`部分をコメントアウトして更新をする（`!ImportValue`で引用している部分）
2.`DynamoDB`を削除
3.`EventBridge`を削除
4.`Lambda(sendqueue)`を削除
5.`Lambda(sendmail)`を削除
6.`SQS`を削除
7.`S3`のバケットを空にし、`S3`を削除
8.`SNS`を削除

------------

## さいごに
盛りだくさんの内容になりましたが、個人的にはCFnでDynamoDBを利用するという目的は果たせたかと思います。ただし、まだまだ使えるのかと問われると基本となるクエリなどの本質的な理解が出来ていないとLambdaを書きながら痛感しました。
次回はAPIGateway経由で、こちらで利用した`DynamoDB`に名前やアドレスを CLIからではなくて、実際のフロント画面から送ったりして挙動を確認したいと思います。

------------

## 参考文献
[AWS Lambda実践ガイド 第2版 (impress top gear) 著:大澤文孝](https://www.amazon.co.jp/AWS-Lambda%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89-%E7%AC%AC2%E7%89%88-impress-gear/dp/4295013307/ref=asc_df_4295013307/?tag=jpgo-22&linkCode=df0&hvadid=553954433678&hvpos=&hvnetw=g&hvrand=2164190274868650654&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009299&hvtargid=pla-1599229625688&psc=1&th=1&psc=1)