AWS Lambdaで作るサーバーサイド

AWS Lambda & DynamoDB

2022.08.28

はじめに

AWS上でAPIを作ろうとしたとき, 気になるのはやはり費用です。
特に趣味の範囲ではリクエストが飛ぶこともそうそうないのでEC2インスタンスを立ち上げっぱなしにするのはもったいない, けれども毎回落とすのは面倒。
そんなときのテンプレート, API Gateway + Lambda + DynamoDBでAPIを作ってみました。


それぞれの役割

Lambda

コードをアップロードすることでイベント発生をトリガーとして任意の処理を実行できます。 今回の場合だとDynamo DBからデータをとってきて、responseとして返す処理を担当します。実行時間分のみ請求されるためEC2インスタンスに比べ安価になるケースが多いです。

API Gateway

APIを作成し、AWSの他のサービスへとつなげる役割を果たします。
今回はGETメソッドのエンドポイントを提供し、HTTPリクエストをトリガーとしてLambdaを呼び出します。

Dynamo DB

key-value形式のNoSQLデータベース。


手順

今回はaccessKeyを指定してユーザーテーブルの指定されたユーザーを返すAPIを作ります。

1. Lambdaの関数を作成

今回はmainという名前で関数を作成します。普通LambdaではNode.jsを使うことが多いですが今回は個人的に最近使っているGoで書きます。

go
package main

import (
	"context"
	"lead-growth-api/user"
	"log"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	echoadapter "github.com/awslabs/aws-lambda-go-api-proxy/echo"
	"github.com/labstack/echo/v4"
)

var echoLambda *echoadapter.EchoLambda

func init() {
	// stdout and stderr are sent to AWS CloudWatch Logs
	log.Printf("echo cold start")
	e := echo.New()
	e.GET("/user", func(c echo.Context) error {
		return user.GetUser(c)
	})
	echoLambda = echoadapter.New(e)
}

func handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	return echoLambda.ProxyWithContext(ctx, req)
}

func main() {
	lambda.Start(handler)
}
go
package user

type UserMaster struct {
	ID    string `json:"id"`
	Name  string `json:"name"`
	Email string `json:"e-mail"`
}
go
package user

import (
	"fmt"
	"net/http"

	"github.com/labstack/echo/v4"
)

// DBからuserを取得
func GetUser(c echo.Context) error {
	return c.JSON(http.StatusOK, map[string]string{"message": "hello"})
}

ポイントはaws-lambda-go-api-proxyのechoを使っている点です。このためにinitにe.GET等を追加するだけで他のエンドポイントを追加することができます。

あとはmain.goをbuildしてアップロードするのですが、lambdaで動くようにするには以下のように環境変数を設定する必要があります。

undefined
build:
	GOARCH=amd64 GOOS=linux CGO_ENABLED=0 go build -o main main.go
	zip main.zip main
	rm main

これで

$ make build

でmain.zipを作りuploadします。

現時点ではDynamoDBと接続しておらず、ただ{message: hello}を返しているだけです。

2. API Gatewayの設定

以下のように/userにGETメソッドを追加し、以下のようにGETメソッドを作成します。

api gateway user get

Lambdaプロキシ統合に使用にチェックを入れるのを忘れないでください。これがないとmainの

go
e.GET("/user", func(c echo.Context) error {
		return user.GetUser(c)
})

が呼ばれません。

API Gatewayのテストで{"message":"hello"}が返るか確かめてください。

3. DynamoDBテーブルの作成

Dynamo DBのテーブルの作成に進み、以下のようなテーブルを作ります。 dynamo db create user table

また、Lambdaで取得するためにレコードを追加しておきます。
user row

4. LambdaとDynamoDBをつなぐ

GetUserを以下のように書き換えます。

go
func GetUser(c echo.Context, db *dynamodb.DynamoDB) error {
	params := &dynamodb.GetItemInput{
		TableName: aws.String("users"),
		Key: map[string]*dynamodb.AttributeValue{
			"id": {
				S: aws.String("1"),
			},
		},
	}
	result, err := db.GetItem(params)
	if err != nil {
		return err
	}
	usr := UserMaster{}
	err = dynamodbattribute.UnmarshalMap(result.Item, &usr)

	return c.JSON(http.StatusOK, usr)
}

さいごに

今回は省略しましたがDynamoDBへのアクセス権をLambdaに付与する必要があります。
こちらを参考にポリシーを作成し、Lambdaのroleに付与してください。


参考サイト

チュートリアル: Lambda と DynamoDB を使用した CRUD API の構築
API Gateway(HTTP API)のHTTPプロキシ統合とGoを利用したLambda Functionの実装方法
How to Create an AWS IAM Policy to Grant AWS Lambda Access to an Amazon DynamoDB Table