サーバーレスアーキテクチャで翻訳 Web API を構築してみた【AWS Hands-on for Beginners Serverless #1】

# はじめに
AWS認定試験の勉強している時にサーバレスアーキテクチャに関する問題がたくさん出てきました。
なので復習もかねて、今回は[AWS Hands-on for Beginners Serverless #1](https://pages.awscloud.com/JAPAN-event-OE-Hands-on-for-Beginners-Serverless-1-2022-reg-event.html?trk=aws_introduction_page)を参考に、サーバレスアーキテクチャを実際に構築していきたいと思います。
# 学習するサービス
- AWS Lambda 
- Amazon API Gateway
- Amazon DynamoDB
- Amazon Translate

# AWS Hands-on for Beginners Serverless #1の概要
このハンズオンでは、"こんにちは" と入力すると "Hello" と返すWebAPIを作成します。

構築する構成図は以下になります。
![Hands-On-for-Beginners_2022_Serverless1_0819_v1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820434/0ae9f9c3-ffc9-19fa-ef76-8163bd0ea703.png)
引用:[AWS Hands-on for Beginners Serverless #1](https://pages.awscloud.com/JAPAN-event-OE-Hands-on-for-Beginners-Serverless-1-2022-reg-event.html?trk=aws_introduction_page)

### 作成するWebAPIリクエストの流れ
1. API Gatewayがリクエストを受け取けつけるとLambdaを呼び出す。
2. LambdaはTranslateを呼び出し翻訳結果を取得する。
3. 翻訳結果をAPIGatewayに返しAPIレスポンスを返す。
4. リクエストのinput、outputの履歴をDynamoDBに保存する。


このハンズオンは以下の構成になっております。
|  | Session | 分:秒 |
| ---- | ---- |---- |
|1|Serverless アーキテクチャの概要|09:54|
|2|AWS Lambda の概要|06:40|
|3|AWS Lambda ハンズオン① Lambda を単体で使ってみる|10:14|
|4|AWS Lambda ハンズオン② 他のサービスを呼び出してみる|07:41|
|5|Amazon API Gateway の概要|08:12|
|6|Amazon API Gateway ハンズオン① API Gateway を単体で使ってみる|06:14|
|7|Amazon API Gateway ハンズオン② API Gateway と Lambda を組み合わせる|10:20|
|8|Amazon DynamoDB の概要|07:44|
|9|Amazon DynamoDB ハンズオン①テーブルを作ってみる|04:41|
|10|Amaozn DynamoDB ハンズオン② API Gateway と Lambda と DynamoDB を組み合わせる|08:03|


座学動画についてはこの記事では触れませんが、とても分かりやすいと思うので
気になった方はぜひ[AWS Hands-on for Beginners Serverless #1](https://pages.awscloud.com/JAPAN-event-OE-Hands-on-for-Beginners-Serverless-1-2022-reg-event.html?trk=aws_introduction_page)をご覧ください。

# AWS Hands-on for Beginnersとは
[AWS Hands-on for Beginners](https://aws.amazon.com/jp/events/aws-event-resource/hands-on/)とは、動画を見ながら実際にサービスを触って学習できるAWS公式提供の動画形式コンテンツです。

下図の右側にあるアンケートに答えると無償で視聴できます。
![Screenshot 2023_08_04 23_26_46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820434/572aa046-4c48-2f7c-0f93-80de3c7da752.png)

# ハンズオン実践
細かいやり方や設定については省略させていただきます。
詳細については、ハンズオン動画をご覧いただければと思います。
## 3. AWS Lambda ハンズオン① Lambda を単体で使ってみる

イベントの中身をログとして出力できるLambda関数を実装します。

1.　Lambda関数「translate-function」を作成する。
2.　イベント「Test」を作成し、テストを実行する。
3.　イベントの中身をログとして出力するように、「translate-function」を実装する。
4.　CloudWatchでログを確認し、きちんと出力されているか確認する。

以下のように実装しました。
```python:lambda_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 from Lambda!')
    }
```



以下のテストイベントを実行します。

```json:Test
{
  "key1": "value1",
  "key2": "value2",
  "key3": "value3"
}
```

無事にイベントの中身をログとして出力できています。

![translate-function - Lambda - Google Chrome 2023_08_05 14_19_49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820434/f3566471-0d14-e1bd-b298-afc20287db44.png)
```:Function Log
START RequestId: b0a4fea6-e5dd-4fcc-9538-d576e1fcafd4 Version: $LATEST
[INFO]	2023-08-05T04:27:26.667Z	b0a4fea6-e5dd-4fcc-9538-d576e1fcafd4	{'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
END RequestId: b0a4fea6-e5dd-4fcc-9538-d576e1fcafd4
REPORT RequestId: b0a4fea6-e5dd-4fcc-9538-d576e1fcafd4	Duration: 2.08 ms	Billed Duration: 3 ms	Memory Size: 128 MB	Max Memory Used: 36 MB	Init Duration: 118.60 ms
```

CloudWatchでも、テストイベントの中身をログとして出力できました。
![translate-function - Lambda - Google Chrome 2023_08_05 14_35_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820434/427b0859-93e0-2eba-8cbe-41e4422cd23c.png)

```
2023-08-05T13:27:26.544+09:00	INIT_START Runtime Version: python:3.7.v30 Runtime Version ARN: arn:aws:lambda:ap-northeast-1::runtime:898e3ea3957b56a1999758f7a44db46ffe42dc979b385666bf4950b8611ec475
2023-08-05T13:27:26.667+09:00	START RequestId: b0a4fea6-e5dd-4fcc-9538-d576e1fcafd4 Version: $LATEST
2023-08-05T13:27:26.668+09:00	[INFO] 2023-08-05T04:27:26.667Z b0a4fea6-e5dd-4fcc-9538-d576e1fcafd4 {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
2023-08-05T13:27:26.670+09:00	END RequestId: b0a4fea6-e5dd-4fcc-9538-d576e1fcafd4
2023-08-05T13:27:26.670+09:00	REPORT RequestId: b0a4fea6-e5dd-4fcc-9538-d576e1fcafd4 Duration: 2.08 ms Billed Duration: 3 ms Memory Size: 128 MB Max Memory Used: 36 MB Init Duration: 118.60 ms
```
## 4. AWS Lambda ハンズオン② 他のサービスを呼び出してみる
Translateを呼び出す、Lambda関数を実装します。

1.　Translateを呼び出せるようにLambda関数「translate-function」を実装する。
2.　LambdaにアタッチされているIAMロールにポリシー「TranslateFullAccess」を追加する。
3.　正しく動作するか確認するため、テストを実行する。



以下のように実装しました。
```python:lambda_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
        })
    }
```

続いて、Lambda関数がTranslateにアクセスできるようIAMロールにポリシーを追加していきます。

Lambda関数の「設定」から「ロール名」を選択します。
![許可を追加 _ IAM _ Global - Google Chrome 2023_08_05 16_29_52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820434/09ce8ce5-abfb-31cb-37f8-e02d452b8ebd.png)

「許可を追加」から「ポリシーをアタッチ」を選択します。
![translate-function-role-wg9ienoq _ IAM _ Global - Google Chrome 2023_08_05 16_16_31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820434/edfc5962-ec95-cb6c-7c9b-147864623213.png)

検索バーで「Translate」と検索します。
「TranslateFullAccess」を選択して「許可を追加」をクリックします。
![AWS Hands-on for Beginners Serverless #1 サーバーレスアーキテクチャで翻訳 Web API を構築する _ AWS Webinar - Google Chrome 2023_08_05 16_20_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820434/e0b57751-cc4f-257a-192b-aa97518b3819.png)

無事、ロールにポリシーをアタッチできました。
![許可を追加 _ IAM _ Global - Google Chrome 2023_08_05 16_34_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820434/088bebb7-8ff7-eb1b-409b-7609ab32bf0a.png)
## 6. Amazon API Gateway ハンズオン① API Gateway を単体で使ってみる
このセクションではまだLambda関数との連携は行いません。
Mockデータを返すAPIを作成します。

1. 「translate-api」として、APIを作成する。
2. 「sample」として、リソースを作成する。
3. 統合タイプを「Mock」としてGETメソッドを作成する。
4. GETメソッドのテストを行う。
5. 新しく作成したステージ「dev」にAPIのデプロイを行う。 
6. デプロイしたAPIをURLで呼び出す。

統合レスポンスのマッピングテンプレート「application/json」を作成します。
```json:application/json
{
    "StatusCode": 200,
    "body": {
        {
            "report_id": 5,
            "report_title": "Hello, World"
            
        },
        {
            "report_id": 7,
            "report_title": "こんにちは"  
        }
    }
}
```

テストを実行すると、無事にレスポンスが返ってきました。
![i2ho5luaf3.execute-api.us-east-1.amazonaws.com_dev_sample - Google Chrome 2023_08_05 21_12_26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820434/e6536f13-4c41-ac40-ca53-99bcd0d86ccc.png)

ブラウザでのURL呼び出しも無事にできました。
![キャプチャ 2023_08_05 21_05_19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820434/3305b64c-0337-f6c6-38c5-9018d36c2c8b.png)


## 7. Amazon API Gateway ハンズオン② API Gateway と Lambda を組み合わせる
API GatewayとLambdaを連携して、入力した日本語を英語に翻訳するAPIを作成します。

1. すでに作成済みのAPI「translate-api」で「translate」リソースを作成する。
2. 統合タイプを「Lambda関数」とし、作成済みのLambda関数「translate-function」を選択してGETメソッドを作成する。
3. GETメソッドのメソッドリクエストで、URL クエリ文字列パラメータ「input_text」を追加する。
4. APIGatewayのルールに従ってレスポンスを返すようにLambda関数「translate-function」を修正する。
5. イベントテンプレート「Amazon API Gateway AWS Proxy」を修正し、イベント「APICall」を作成する。
6. クエリパラメータの文字列を受け取れるように「translate-function」を修正する。
7.GETメソッドのURLの呼び出しでクエリパラメータを「こんばんは」としてAPIを叩いてみる。

以下のように実装しました。
```python:lambda_function.py
import json
import boto3

translate = boto3.client('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': {}
    }

```

クエリパラメータ(input_text)として「こんにちは」を渡すテスト「APICall」を実行します。

翻訳結果として「Hi」が無事に返ってきました。
また、今回レスポンスとして追加した「isBase64Encoded」と「headers」も無事に確認できました。
```json:Response
{
  "statusCode": 200,
  "body": "{\"output_text\": \"Hi\"}",
  "isBase64Encoded": false,
  "headers": {}
}
```
ブラウザでのURL呼び出しを行います。クエリパラメータとして「こんばんは」をURLに入力します。
無事にレスポンスが返ってきました。`※GETメソッドを作成するとき、「Lambdaプロキシ統合の使用」にチェックしていないとここで、エラーが発生します。`
```json
{"output_text": "Good evening"}
```

## 9. Amazon DynamoDB ハンズオン①テーブルを作ってみる
DynamoDBのテーブルを作っていきます。
1. 「translate-history」テーブルを作成する。
2. 手動で項目を作成する。


「timestamp」をパーティションキーとした「translate-history」テーブルを作成しました。
また、項目を作成しました。
![項目 _ Amazon DynamoDB Management Console - Google Chrome 2023_08_06 1_25_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820434/5a3e07aa-12a8-8645-52c8-6032620b1cdc.png)


 
## 10. Amaozn DynamoDB ハンズオン② API Gateway と Lambda と DynamoDB を組み合わせる
翻訳履歴をDynamoDBにPUTするよう、Lambda関数を実装していきます。

1. 翻訳APIを叩くたびに、「timestamp」、「input_text」、「output_text」をDynamoDBにPUTするようLambda関数を修正する。
2. Lambda関数にアタッチされているIAMロールにポリシー「AmazonDynamoDBFullAccess」をアタッチする。
3. 作成済みのテスト「APICall」を実行し、実際にDynamoDBにPUTできているか確認する。
4. GETメソッドのURLの呼び出しでURLにアクセスし、APIを叩いてDynamoDBにPUTできているか確認する。

以下のように実装しました。
```python:lambda_function.py
import json
import boto3
import datetime

translate = boto3.client('translate')

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


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_text': input_text,
            'output_text': output_text
            
        }
    )
    
    return {
        'statusCode': 200,
        'body': json.dumps({
            'output_text': output_text
        }),
        'isBase64Encoded': False,
        'headers': {}
    }
```
クエリパラメータ(input_text)として「こんにちは」を渡すテスト「APICall」を実行します。

無事に「translate-history」テーブルにputされています。
![項目 _ Amazon DynamoDB Management Console - Google Chrome 2023_08_06 1_49_34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820434/7e6a47af-0f27-0b05-d9e9-dd7c41ac3f41.png)

続いて、ブラウザでのURL呼び出しを行います。クエリパラメータとして「ハンズオン完走しました」をURLに入力します。
無事に、レスポンスが返ってきました。

![項目 _ Amazon DynamoDB Management Console - Google Chrome 2023_08_06 2_01_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820434/c2d79c28-a113-660a-9b07-9a57002eabdd.png)

こちらも、無事に「translate-history」テーブルにputされています。
![項目 _ Amazon DynamoDB Management Console - Google Chrome 2023_08_06 2_02_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820434/8e22f341-1ad8-6069-c7c5-061d5d94742c.png)

### 落ち穂拾い
- AWS Lambdaで、コンテナが再利用されるとグローバルスコープ(ハンドラ外)に記述されている部分はウォームスタートになり、ローカルスコープ(ハンドラ内)に記述されている部分は再利用されない。
- 今後どのようにサーバーレスを学んでいくかのラーニングパスの紹介

# まとめ
無事にハンズオン完走することができました。
ハンズオンでの学習はアウトプットとして、とてもためになると感じたので、気になるサービス・学びたいサービスがあれば積極的に触ってみて学習していきたいと思います。