AWSハンズオン実践メモ  〜AWS SAM を使ってテンプレートからサーバーレスな環境を構築する〜

# はじめに
前回、AWS公式の[ハンズオンシリーズ](https://aws.amazon.com/jp/aws-jp-introduction/aws-jp-webinar-hands-on/?trk=aws_blog)の中から、翻訳 Web API の構築を通して、サーバーレスアーキテクチャの基本を学ぶ[ハンズオン](https://pages.awscloud.com/event_JAPAN_Hands-on-for-Beginners-Serverless-2019_LP.html?trk=aws_introduction_page)を実施しました。
**参考**：[AWSハンズオン実践メモ 〜サーバーレスアーキテクチャで翻訳 Web API を構築する〜](https://qiita.com/kuwadev/items/52133e5a6cec9358ca3c)

今回は、前回のハンズオンで構築した翻訳 Web API を**AWS Serverless Application Model (AWS SAM)を用いてテンプレートから構築**します。

AWS Serverless Application Model (AWS SAM)とは、**サーバーレスアプリケーション構築用のオープンソースフレームワーク**です。簡単に言うと、**サーバレスな構成を作ることに特化した Cloudformation のテンプレート**になります。

本記事は自身のハンズオン学習メモとして投稿します。
# 目次
- [ハンズオンの目的](#ハンズオンの目的)
- [本編](#本編)
- [おわりに](#おわりに)

# ハンズオンの目的

- AWS Serverless Application Model (AWS SAM)を用いて、サーバーレス構成を自動構築する

>前回の Serverless #1 で構築した翻訳 Web API を AWS Serverless Application Model (AWS SAM) を用いてテンプレートから構築します。前回のハンズオンでは全ての工程を手作業で（マネージメントコンソール上で）構築していきました。今回のハンズオンではこの構築作業をテンプレート化し、作成したテンプレートから AWS リソースを構築していきます。テンプレート化することで、そのテンプレートをバージョン管理できるようになる、チーム内でレビューがしやすくなる、といった利点があり、プロダクト開発をより効率的に進めることができます。

![20200709.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/646352/0a9f26a1-786a-6156-e749-63915da4efd9.png)
(https://aws.amazon.com/jp/aws-jp-introduction/aws-jp-webinar-hands-on/ より引用)

# 本編

### Cloud9 のセットアップ+Cloud9 で簡単な Lambda 関数を作成する  
- Cloud9で環境を作成
- Cloud9からLambdaを作成＆編集

### SAM で Lambda 関数を作成する①  

AM を利用するための事前準備を行い、Lambda 関数の設定情報を SAM テンプレートに記載していく。

- translate-function.pyを作成

```translate-function.py
import json
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):

    logger.info(event)

    return {
        'statusCode': 200,
        'body': json.dumps('Hello Hands on world!')
    }
```

- template.yamlを作成  

```template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AWS Hands-on for Beginners - Serverless 2
Resources:
  TranslateLambda:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: translate-function-2
      CodeUri: ./translate-function
      Handler: translate-function.lambda_handler
      Runtime: python3.7
      Timeout: 5
      MemorySize: 256
```

- 作成したファイルをパッケージング

```
aws cloudformation package \
     --template-file template.yaml \
     --s3-bucket 20200714handsonserverless \
     --output-template-file packaged-template.yaml
```

このコマンドで、S3 にファイルがアップロードされ、そのファイルのUri が --template-file で指定したテンプレートに書き足され、--output-template-file で指定したファイル名で出力される。

- デプロイ

```
aws cloudformation deploy \
     --template-file ./packaged-template.yaml \
     --stack-name hands-on-serverless-2 \
     --capabilities CAPABILITY_IAM
```

パッケージで書き出したテンプレートファイルを元に、AWS CLI の CloudFormation の deployコマンドを叩く。これで、テンプレートに記述されている通りのリソースが構築される。

### SAM で Lambda 関数を作成する②

SAM テンプレートファイルをアップデートし、Lambda 関数から別の AWS サービスを呼び出せるよう IAM 設定を修正。

- translate-function.pyを編集

```translate-function.py
import json
import boto3

translate = boto3.client('translate')

def lambda_handler(event, context):

    input_text = "こんにちは"

    response = translate.translate_text(
        Text=input_text,
        SourceLanguageCode='ja',
        TargetLanguageCode='en'
    )

    output_text = response.get('TranslatedText')

    return {
        'statusCode': 200,
        'body': json.dumps({
            'output_text': output_text
        })
    }
```

- template.yamlを編集(Policies: - TranslateFullAccessを追加)
- 作成したファイルをパッケージング(先程と同様)
- デプロイ(先程と同様)


### SAM で API Gateway のリソースを作成し、Lambda 関数と連携させる

API Gateway リソースを作成する際の設定を SAM テンプレートに記載し、外部からリクエストを受け付けられる Web API を構築する。

- translate-function.pyを編集

```translate-function.py
import json
import boto3

translate = boto3.client(service_name='translate')

def lambda_handler(event, context):

    input_text = event['queryStringParameters']['input_text']

    response = translate.translate_text(
        Text=input_text,
        SourceLanguageCode="ja",
        TargetLanguageCode="en"
    )

    output_text = response.get('TranslatedText')

    return {
        'statusCode': 200,
        'body': json.dumps({
            'output_text': output_text
        }),
        'isBase64Encoded': False,
        'headers': {}
    }
```

- template.yamlを編集  

```template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AWS Hands-on for Beginners - Serverless 2
Resources:
  TranslateLambda:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: translate-function-2
      CodeUri: ./translate-function
      Handler: translate-function.lambda_handler
      Runtime: python3.7
      Timeout: 5
      MemorySize: 256
      Policies:
        - TranslateFullAccess
      Events:
        GetApi:
          Type: Api
          Properties:
            Path: /translate
            Method: get
            RestApiId: !Ref TranslateAPI
  TranslateAPI:
    Type: AWS::Serverless::Api
    Properties:
      Name: translate-api-2
      StageName: dev
      EndpointConfiguration: REGIONAL
```

- 作成したファイルをパッケージング(先程と同様)
- デプロイ(先程と同様)

以下のようにクエリ部分にリクエストを入力し送信すると、翻訳結果が返却される。

![20200714_translate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/646352/9a3468db-b445-130d-53f6-d781f0aea939.png)



### SAM で DynamoDB TBL を作成し、Lambda 関数を連携させる

DynamoDB テーブルを作成する際の設定情報を SAM テンプレートに記載する。また、Lambda 関数側の記載も修正し、DynamoDB に翻訳リクエストの履歴情報を書き込めるようにする。

- translate-function.pyを編集

```translate-function.py
import json
import boto3
import datetime

translate = boto3.client(service_name='translate')

dynamodb_translate_history_tbl = boto3.resource('dynamodb').Table('translate-history-2')

def lambda_handler(event, context):

    input_text = event['queryStringParameters']['input_text']

    response = translate.translate_text(
        Text=input_text,
        SourceLanguageCode="ja",
        TargetLanguageCode="en"
    )

    output_text = response.get('TranslatedText')

    dynamodb_translate_history_tbl.put_item(
      Item = {
        "timestamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S"),
        "input": input_text,
        "output": output_text
      }
    )

    return {
        'statusCode': 200,
        'body': json.dumps({
            'output_text': output_text
        }),
        'isBase64Encoded': False,
        'headers': {}
    }
```

- template.yamlを編集  

```template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AWS Hands-on for Beginners - Serverless 2
Resources:
  TranslateLambda:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: translate-function-2
      CodeUri: ./translate-function
      Handler: translate-function.lambda_handler
      Runtime: python3.7
      Timeout: 5
      MemorySize: 256
      Policies:
        - TranslateFullAccess
        - AmazonDynamoDBFullAccess
      Events:
        GetApi:
          Type: Api
          Properties:
            Path: /translate
            Method: get
            RestApiId: !Ref TranslateAPI
  TranslateAPI:
    Type: AWS::Serverless::Api
    Properties:
      Name: translate-api-2
      StageName: dev
      EndpointConfiguration: REGIONAL
  TranslateDynamoDbTbl:
    Type: AWS::Serverless::SimpleTable
    Properties:
      TableName: translate-history-2
      PrimaryKey:
        Name: timestamp
        Type: String
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1
```

- 作成したファイルをパッケージング(先程と同様)
- デプロイ(先程と同様)

以下のように翻訳結果がdynamoDBに蓄積される。

![20200714_dynamodb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/646352/4f2579d7-e114-bb5e-2f27-d87a5e5778f5.png)


### 作成した AWS リソースの削除

CloudFormationのスタックを削除すると関連するリソースが全て削除される。


#おわりに

今回はSAMを利用し、IaaCでサーバーレスアーキテクチャを構築した。
プロジェクトにおいてAWSで設計・構築する際はCloudFormationを使っていく事になると思うので、記法や管理方法についても学習したい。


#参考

[[新ツール] AWS Serverless Application Model (AWS SAM) を使ってサーバーレスアプリケーションを構築する](https://dev.classmethod.jp/articles/aws-serverless-application-model/)