AWS Lambdaで翻訳APIを作成するハンズオンをやってみた

# はじめに

SAAの勉強で知ったサービスを実際に触って学習内容の定着を図りたいという目的でハンズオンをやってみました。

参考にしたハンズオンの動画、資料は大変わかりやすかったのですが、動画が作成された段階でのコンソール画面と2024年8月時点でのコンソールが若干違っていたため少しわかりづらかったです…。
作業メモとしてこちらの記事を作成してみました。

以下のリンクから日本語でのAWSハンズオンや資料一覧を確認できます。
https://aws-samples.github.io/jp-contents-hub/

# AWS Lambda　ハンズオン

今回参考にしたハンズオンはこちらです。
https://pages.awscloud.com/JAPAN-event-OE-Hands-on-for-Beginners-Serverless-1-2022-reg-event.html?trk=aws_introduction_page

# 使用するAWSのサービス

今回使用するAWSのサービスは以下の通りです
- [AWS Lambda](https://aws.amazon.com/jp/lambda/)
- [AWS APIGateway](https://aws.amazon.com/jp/api-gateway/)
- [AWS DynamoDB](https://aws.amazon.com/jp/dynamodb/)
- [Amazon Translate](https://aws.amazon.com/jp/translate/)

# AWS Lambda ハンズオン① Lambda を単体で使ってみる

こちらのフェーズでは、Lambdaを実際に作成して単体で実行できるようになります。

1. AWSのコンソールからLambdaを開き「関数の作成」をクリックします
2. 動画に従い以下のように設定項目に入力し「関数の作成」をクリックします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817138/aa757569-c60f-ad53-e4f2-0a75ddc6458e.png)
3. Lambda>関数 から、先ほど作成したLambdaを開きます
4. 「コード」のタブからLambdaで実行する関数を以下のように書き換え「デプロイ」をクリックしLambdaを更新します
<details><summary>書き換えるコード</summary>

```python
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!')
    }
```
</details>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817138/36436605-8a8d-fdf8-803d-5596ea73018c.png)
5. テストを実行すると正しく書き換えたLambdaが動作していることが確認できます。

# AWS Lambda ハンズオン② 他のサービスを呼び出してみる

こちらのフェーズでは、Lambdaを使ってAWSの翻訳を実行するサービスを呼び出すことができるようになります。

1.　翻訳サービスを呼び出すためにLambdaの関数を以下のように書き換えて保存します
<details><summary>書き換えるコード</summary>

```python
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
        })
    }
```
</details>

2.　Lambdaを実行するIAMロールを修正してAmazon Translate にアクセスできるように設定します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817138/952b42cd-fc19-2645-e56b-aa5a433f6d52.png)
「許可を追加」をクリックします

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817138/bd2106aa-228f-82b8-9648-47772d0986d7.png)
TranslateFullAccess　を選択し許可を追加します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817138/adcb80c2-6ff0-6adc-9e85-c9f1e82ed254.png)
以下のように許可ポリシーが表示されていれば成功です
3. テストを実行すると正しく書き換えたLambdaが動作していることが確認できます。

# Amazon API Gateway ハンズオン① API Gateway を単体で使ってみる

こちらのフェーズでは、APIを作成しテスト実行ができるようになります。
モックとしてAPIを作成し固定レスポンスのjsonを返すAPIを作成します。

1. API Gateway から新たにAPIを作成します
2. 作成したAPIにメソッドを追加します
3. 作成したメソッドの統合レスポンスに以下のJSONを定義します
4. APIをデプロイします

# Amazon API Gateway ハンズオン② API Gateway と Lambda を組み合わせる

こちらのフェーズでは、作成したAPIとLambdaを組み合わせて実行することができるようになります。

1. /translate リソースを作成し、GETメソッドを作成します
2. メソッドリクエストでクエリパラメータを設定し翻訳してほしい文字列を`input_text`として渡せるようにします

# Amazon DynamoDB ハンズオン①テーブルを作ってみる

こちらのフェーズでは、APIの実行履歴を残すためのDBを作成します。

1. DynamoDB >「テーブル」から「テーブルの作成」をクリックする
2. パーティションキーに「timestamp」を設定します(今回はソートキーを特に設定しません)
3. テーブル設定はデフォルト設定を選択し、「テーブルの作成」をクリックします 
4. 3で作成したDBを選択し、アクションから「項目を作成」をクリックします。手動でレコードを作成できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817138/d8eda73d-8378-9db6-373c-20063f212fb3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817138/147769ed-d2bd-c41b-5355-d141d74c03a3.png)

# Amaozn DynamoDB ハンズオン② API Gateway と Lambda と DynamoDB を組み合わせる

こちらのフェーズでは、これまでに作成したLambda, API Gateway, DynamoDBを組み合わせて翻訳APIを実行->DBにAPIの実行履歴を保存という機能を実装します。

1. Lambdaの関数を以下のように修正し保存します

<details><summary>書き換えるコード</summary>

```python
import json
import boto3
import datetime

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

2.　LambdaがDynamoDBにアクセスできるようにIAMロールを設定します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817138/8dc1f202-6947-ac5d-2355-ac7dfd66a905.png)
3.　テストを実行するとDynamoDBに実行した履歴が記録されていることを確認できます(★がついているのは手動で追加したレコード)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817138/80fff6ee-22c7-5e32-8e61-f4cad22685b6.png)


# 落ち穂拾い　エラーハンドリング

APIでの翻訳機能はこれまでの実装で問題なく動きますが、実際のLambdaではエラーのハンドリングも行うべきなので、Lambdaの実行関数を以下のようにエラーハンドリングができるように修正する必要があります。

<details><summary>書き換えるコード</summary>

```python
import json
import boto3
import datetime

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

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

def lambda_handler(event, context):

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

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

    except Exception as e:
        logging.error(e.response['Error']['Message'])
        raise Exception("[ErrorMessage]: " + str(e))

    output_text = response.get('TranslatedText')

    try:
        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
          }
        )
    except Exception as e:
        logging.error(e.response['Error']['Message'])
        raise Exception("[ErrorMessage]: " + str(e))

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

# ハンズオンをやってみての感想など

LambdaなどのAWSのサーバーレスなサービスを今まで実際に使ったことがなかったため、具体的な利用シーンをあまり想像できていませんでした。
今回参考にしたハンズオン通り作業を進めると1時間弱、長くても半日ぐらいで簡単なAPIを実装することができました。

単に外部サービスを呼び出したりするようなAPIはLambdaとAPI Gateway +α　で十分かもしれないなと感じました。

次は[SAMを使ったサーバレス環境の自動構築についてのハンズオン](https://pages.awscloud.com/JAPAN-event-OE-Hands-on-for-Beginners-Serverless-2-2022-confirmation_009.html)をやってみようかと思います。
そちらも作業メモを記事にまとめる予定です。