AWS Lambda

【ハンズオン】「コードで学ぶAWS入門」をやってみました!

はじめに

はじめまして!新入社員のタナミです。
今回は人生初の技術ブログということで真野 智之様の「コードで学ぶAWS入門」の第13章をやってみました。

本来はCDKを利用して行うのですが、私はAWSのマネージドコンソールを使用して作成したので
同じくマネージドコンソールを使用して作成したい方に向けてに少しでも参考になればと思います。

「コードで学ぶAWS入門」はこちらをご覧ください!

構成図を確認しよう

今回作成する俳句SNS「Bashoutter」の構成図は以下の通りです。

Mano, T. (2021, June 14). コードで学ぶAWS入門.https://tomomano.github.io/learn-aws-by-coding

Lambdaを作ろう

まずは4つのLambda関数GET、POST、DELETE、PATCHを作成します。
ランタイムはPython3.14を選択し、それぞれのLambdaのリソース名は以下のようにしました。

tanami-haiku-get
tanami-haiku-post
tanami-haiku-delete
tanami-haiku-patch

共通の設定

次にロールを作成し4つのLambda全てに同一のロールを割り当てます。
ロールにはAmazonDynamoDBFullAccessのポリシーをアタッチしておきましょう。
これによりLambda→DynamoDBへとアクセスすることが出来ます!

また、タイムアウト秒を30秒に変更しておきます。

Lambdaのコード

さて、Lambdaのガワを用意することが出来たので次に中身の実装です。
元サイト様のGitHubを参考にコードを作成します。
それぞれのLambdaに設定するコードは以下の通りです。

tanami-haiku-get

import json
import os
import decimal
import boto3

# DynamoDB設定
ddb = boto3.resource("dynamodb")
table = ddb.Table("tanami-haiku-table")

# CORS設定
HEADERS = {
    "Access-Control-Allow-Origin": "*",
}

# Decimal対応のJSONエンコーダー
class DecimalEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, decimal.Decimal):
            return float(o)
        return super(DecimalEncoder, self).default(o)

def lambda_handler(event, context):
    """
    handler for GET /haiku
    """
    try:
        response = table.scan()

        status_code = 200
        resp = response.get("Items")
    except Exception as e:
        status_code = 500
        resp = {"description": f"Internal server error. {str(e)}"}
    return {
        "statusCode": status_code,
        "headers": HEADERS,
        "body": json.dumps(resp, cls=DecimalEncoder)
    }

tanami-haiku-post

import json
import os
import uuid
from datetime import datetime, timezone
import boto3

# DynamoDB設定
ddb = boto3.resource("dynamodb")
table = ddb.Table("tanami-haiku-table")

# CORS設定
HEADERS = {
    "Access-Control-Allow-Origin": "*",
}

def lambda_handler(event, context):
    """
    handler for POST /haiku
    """
    try:
        body = event.get("body")
        if not body:
            raise ValueError("Invalid request. The request body is missing!")
        body = json.loads(body)

        for key in ["username", "first", "second", "third"]:
            if not body.get(key):
                raise ValueError(f"{key} is empty")

        item = {
            "item_id": uuid.uuid4().hex,
            "username": body["username"],
            "first": body["first"],
            "second": body["second"],
            "third": body["third"],
            "likes": 0,
            "created_at": datetime.now(timezone.utc).isoformat(timespec="seconds")
        }
        response = table.put_item(Item=item)

        status_code = 201
        resp = {"description": "Successfully added a new haiku"}
    except ValueError as e:
        status_code = 400
        resp = {"description": f"Bad request. {str(e)}"}
    except Exception as e:
        status_code = 500
        resp = {"description": str(e)}
    return {
        "statusCode": status_code,
        "headers": HEADERS,
        "body": json.dumps(resp)
    }

tanami-haiku-delete

import json
import os
import boto3

# DynamoDB設定
ddb = boto3.resource("dynamodb")
table = ddb.Table("tanami-haiku-table")

# CORS設定
HEADERS = {
    "Access-Control-Allow-Origin": "*",
}

def lambda_handler(event, context):
    """
    handler for DELETE /haiku/{item_id}
    """
    try:
        path_params = event.get("pathParameters", {})
        item_id = path_params.get("item_id", "")
        if not item_id:
            raise ValueError("Invalid request. The path parameter 'item_id' is missing")

        response = table.delete_item(
            Key={"item_id": item_id}
        )

        status_code = 204
        resp = {"description": "Successfully deleted."}
    except ValueError as e:
        status_code = 400
        resp = {"description": f"Bad request. {str(e)}"}
    except Exception as e:
        status_code = 500
        resp = {"description": str(e)}
    return {
        "statusCode": status_code,
        "headers": HEADERS,
        "body": json.dumps(resp)
    }

tanami-haiku-patch

import json
import os
import boto3

# DynamoDB設定
ddb = boto3.resource("dynamodb")
table = ddb.Table("tanami-haiku-table")

# CORS設定
HEADERS = {
    "Access-Control-Allow-Origin": "*",
}

def lambda_handler(event, context):
    """
    handler for PATCH /haiku/{item_id}
    """
    try:
        path_params = event.get("pathParameters", {})
        item_id = path_params.get("item_id", "")
        if not item_id:
            raise ValueError("Invalid request. The path parameter 'item_id' is missing")

        response = table.update_item(
            Key={"item_id": item_id},
            UpdateExpression=f"SET likes = likes + :inc",
            ExpressionAttributeValues={
                ':inc': 1,
            }
        )

        status_code = 200
        resp = {"description": "OK"}
    except ValueError as e:
        status_code = 400
        resp = {"description": f"Bad request. {str(e)}"}
    except Exception as e:
        status_code = 500
        resp = {"description": str(e)}
    return {
        "statusCode": status_code,
        "headers": HEADERS,
        "body": json.dumps(resp)
    }

DynamoDBのテーブルを作ろう

次にDynamoDBのテーブルを作成していきます。
テーブル名を設定し、パーティションキーをitem_idと設定します。

また、Auto ScalingをOFFにして
書き込みキャパシティーと読み込みキャパシティーをどちらも1に設定することで万が一の料金の跳ね上がりを防止出来ます!

動作確認テスト~その1~

さて、LambdaとDynamoDBを作成することが出来たので
GETとPOSTの動作確認を行いましょう!

Lambdaではテストイベントを作成しテストを実行することで簡単に動作を確認することが出来ます。

それでは早速やってみましょう!

tanami-haiku-postで俳句の作成

Lambdaに戻りテストイベントを作成します。
イベントJSONにコードを記入してテストを実行します

イベントJSONのコードは下記の通りです。

{
  "body": "{\"username\": \"tanaka_taro\", \"first\": \"春風に\", \"second\": \"桜散りゆく\", \"third\": \"静寂かな\"}"
}

ログを確認しましょう。
以下のようなログが出力されていれば成功です!

tanami-haiku-getで俳句の確認

先ほどと同様にテストを実行します。
イベントJSONのコードは下記の通りです。

{
  "httpMethod": "GET",
  "path": "/haiku",
  "headers": {
    "Content-Type": "application/json"
  }
}

こちらも同様にログの確認をしましょう。

DynamoDBでも俳句の確認をしよう

さてLambda上では俳句の確認を行えましたが
念のためDynamoDBでも作成された俳句の確認を行いましょう。

項目スキャンまたはクエリを実行することで作成された俳句が確認出来ます。

これでLambdaからDynamoDBまでの連携を確認することが出来ました!

S3を作ろう

さて、LambdaとDynamoDBを作成することが出来たので
次にS3を使ってWebページ部分を作成します。

バケット名を設定しパブリックアクセスを全て許可します。

ウェブサイトをホスティングさせよう

S3が作成出来たら静的ウェブサイトホスティングを許可し
インデックスドキュメントにindex.htmlを指定します。

Lambdaのコードと同様に元サイト様のGitHubからbashoutter/gui/distのディレクトリに含まれるファイルを全てアップロードします。

バケットポリシーを設定しよう

次にバケットポリシーを設定します。
バケットポリシーを適用することでWebサイト上から先ほどアップロードしたindex.htmlにアクセス出来るようになります。

バケットポリシーは以下の通りです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicFullAccess",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::作成したバケット名/*"
        }
    ]
}

APIGatewayを作ろう

最後にAPIGatewayを作成しましょう
APIGatewayを作成することでWebサイト上から俳句の閲覧及び投稿が可能になります!

APIを作成からRESTAPIを選択し作成します。

APIGatewayとLambdaを連携しよう

APIGatewayを作成完了後にリソースを作成から/haikuと/haiku/{item_id}を作成します。

メソッドを作成から/haikuにGETとPOSTのメソッドを作成し
/haiku/{item_id}にDELETEとPATCHのメソッドを作成します。
それぞれに各Lambdaを割り当てLambdaプロキシ統合をONにします。

最後にCORSを有効にするから/haikuと/haiku/{item_id}のどちらもCORSを有効にしておきましょう。

APIGatewayをデプロイしよう

最後にAPIをデプロイから作成したAPIGatewayを有効化させましょう。
ステージ名はprodとしておきます。

またデプロイ後のURLは忘れずにコピーしておきましょう!

動作確認テスト~その2~

いよいよ最後の動作確認を行います!

Webサイト上からS3のindex.htmlにアクセスしAPI EndPoint URLに先ほどコピーしたAPIGatewayのURLを貼り付けREFRESHをクリックします。

動作確認テスト~その1~で作成した俳句が表示されているはずです。
更に俳句を入力しPOSTしてREFRESHを押すことでWebサイト上で俳句を投稿することが出来ます!

最後に

いかがだったでしょうか。

今回ハンズオンとして扱った「コードで学ぶAWS入門」はこの俳句SNS以外にも、AWSの基本からAWSサービスを使ったチャットボットの作成まで幅広くAWSの基礎を学ぶことが出来るので非常にオススメです。

また、さらにLambdaのハンズオンに触れたい場合はAWS公式のハンズオンもあり、こちらは動画付きで丁寧に解説してあるのでオススメです。

ではまた次のブログでお会いしましょう!

返信を残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA