AWS Appsync Lambdaの練習（チュートリアル）

こんにちは。
SARAH FoodDataBankの沈（シム）です。
弊社のFoodDataBankはAWSのAppsync + Lambdaの組み合わせてサービスを提供しているので、勉強のため、改めてAppsync + Lambdaの組み合わせを実施してみました。

役に立つ対象
---
既存のREST APIまたはAPI Gatewayを用いてAPIを実装していた方
AppsyncでAPIを実装したい方

システム図
---
![スクリーンショット 2022-12-08 14.02.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/9d0e13e1-3ae6-199d-4dae-a5e581347a9e.png)

必要なAWSリソース
---
- Appsync
- Lambda
- DynamoDB
- IAM Role(Lambda用、AppSync用）
- SAM

AppSync作成
---
まず、AppSyncを作成します。
AWS Consoleで「API 作成」ボタンを押下し、「一から構築」を選択し、「開始」ボタンをクリック
![スクリーンショット 2022-12-08 14.16.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/5eaca81a-fea4-d67b-febf-01409c58be48.png)

API 名には適切な名前を入力し、「作成」ボタンをクリク
![スクリーンショット 2022-12-08 14.17.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/f6396a7b-2d69-d04f-5e2a-1e50682d31d7.png)

AppSync APIが作成できると、左メニューの「スキーマ」をクリックし、スキーマの中身を以下のように設定します。
その後、「スキーマ保存」ボタンをクリック
![スクリーンショット 2022-12-08 14.19.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/6110834f-14f2-cef8-5ea6-18770445d06b.png)

#### スキーマ
```
type Mutation {
    putPost(
        field: String!,
        pk: String!,
        sk: String!,
        title: String!
    ): Post
}

type Post {
    field: String!
    pk: String!
    sk: String!
    title: String
}

type Query {
    singlePost(field: String!, pk: String!, sk: String!): Post
}

schema {
    query: Query
    mutation: Mutation
}
```

DynamoDB作成
---
AWS consoleにてDynamoDBへ移動し、テーブル作成ボタンをクリックします。
練習用のため、簡単に作ります。
テーブル名は適切な名で入力し、パーティションキーはpk、ソートキーはsk入力し、「テーブル作成」ボタンをクリック
![スクリーンショット 2022-12-08 14.23.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/08ddd996-0208-907b-2719-bf5c0940dd29.png)

Lambda
---
ディレクトリ構成
![スクリーンショット 2022-12-08 14.35.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/47a4a292-3cea-b45d-9c21-56f764fc4ec0.png)

```go
// src/main.go
package main

import (
	"fmt"

	"github.com/aws/aws-lambda-go/lambda"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)

type Post struct {
	Field string `json:"field"`
	Pk    string `json:"pk"`
	Sk    string `json:"sk"`
	Title string `json:"title"`
}

type Response struct {
	Pk    string `json:"pk"`
	Sk    string `json:"sk"`
	Title string `json:"title"`
}

func hander(arg Post) (Response, error) {

	response := Response{}

	fmt.Println("arg: ", arg)
	fmt.Println("[DEBUG]Start create session.")
	session, err := session.NewSession(
		&aws.Config{Region: aws.String("ap-northeast-1")},
	)
	if err != nil {
		return Response{}, err
	}
	svc := dynamodb.New(session)

	switch arg.Field {
	case "putPost":
		input := &dynamodb.PutItemInput{
			Item: map[string]*dynamodb.AttributeValue{
				"pk": {
					S: aws.String(arg.Pk),
				},
				"sk": {
					S: aws.String(arg.Sk),
				},
				"title": {
					S: aws.String(arg.Title),
				},
			},
			TableName: aws.String("fdb-sim-test-dynamodb"),
		}

		_, err = svc.PutItem(input)
		if err != nil {
			return Response{}, err
		}

		response.Pk = arg.Pk
		response.Sk = arg.Sk
		response.Title = arg.Title

	case "singlePost":
		input := &dynamodb.GetItemInput{
			Key: map[string]*dynamodb.AttributeValue{
				"pk": {
					S: aws.String(arg.Pk),
				},
				"sk": {
					S: aws.String(arg.Sk),
				},
			},
			TableName: aws.String("fdb-sim-test-dynamodb"),
		}

		result, err := svc.GetItem(input)
		if err != nil {
			return Response{}, err
		}

		resultData := &Response{}
		if err := dynamodbattribute.UnmarshalMap(result.Item, resultData); err != nil {
			return Response{}, err
		}
		response.Pk = resultData.Pk
		response.Sk = resultData.Sk
		response.Title = resultData.Title
	}

	return response, nil
}

func main() {
	lambda.Start(hander)
}

```

```go
// src/go.mod
module example.com/main

go 1.18

require (
	github.com/aws/aws-lambda-go v1.36.0 // indirect
	github.com/aws/aws-sdk-go v1.44.154 // indirect
	github.com/jmespath/go-jmespath v0.4.0 // indirect
)

```

```toml
## samconfig.toml
version = 0.1

### Sandbox ###

[default]
[default.deploy]
[default.deploy.parameters]
stack_name = "fdb-sim-test-lambda-go" ## 自分のもので変更してください
s3_prefix = "fdb-sim-test-lambda-go" ## 自分のもので変更してください
region = "ap-northeast-1"
confirm_changeset = false
capabilities = "CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND"

```

```yaml
# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Appsync Lambda test

Resources:
  TestFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: "fdb-sim-test-lambda"
      CodeUri: ./src/
      Handler: main
      Runtime: go1.x
      Timeout: 30
      MemorySize: 128
      Policies:
        - CloudWatchFullAccess
        - AmazonDynamoDBFullAccess

  TestFunctionLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/aws/lambda/fdb-sim-test-lambda"
      RetentionInDays: 30

```

#### 注意点
main.goの中に記入されているDynamoDBの名前は自分のもので変更してください！
```go
TableName: aws.String("fdb-sim-test-dynamodb"),
```

aws cliで以下のコマンドを入力します。
```cl
sam build
sam deploy --resolve-s3
```

Cloudfrontの新しいスタックが生成され、LambdaとCloudwatch Log、IAMロールが作成されます。

AppSync用IAMロール作成
---
AWS consoleにてIAMに移動し、ロール作成をクリックします。
「AWSのサービス」を選択し、「他のAWSのサービスのユースケース」でAppSyncを検索して選択します。
![スクリーンショット 2022-12-08 14.48.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/edaf3ff0-e280-c3dd-897f-65705e7a4aa0.png)

許可ポリシーは後ほど追加するのでとりあえず、次ボタンを押下
![スクリーンショット 2022-12-08 14.49.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/09123301-3fa7-09bd-fa3b-84356e695ab1.png)

ロール名を適切なもので設定してください。
「Service」がappsync.amazonaws.comで設定されているのか確認後、作成ボタンを押下
![スクリーンショット 2022-12-08 14.50.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/cf52c211-39f8-93ad-abe5-157933d7c9a8.png)

新しく作成できたロールにLambdaのポリシーを追加します。
※簡単にするため、Lambda_Fullaccessを追加しました。プロダクトには適切な権限を持ったポリシーを追加してください。
![スクリーンショット 2022-12-08 14.58.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/6661d888-3eb7-bdb6-79e7-73df131d593d.png)

AppSync設定
---
AppSyncに戻り、データセットとリゾルバーを設定します。
左メニューで「データソース」を選択し、新しいデータソースを追加します。
「データソース」名は適切な名を入力し、「データソースタイプ」を「AWS Lambda 関数」で選択します。
リージョンは作成したLambdaのリージョンを選択し、「関数のARN」にて先ほど作成したLambdaのARNを選択します。
ロール選択する時も「既存の役割」を選択して先ほど作成したAppSync用のロールを選択します。
![スクリーンショット 2022-12-08 15.24.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/dfe9170a-92cc-b424-00d3-703c304f90fa.png)

左メニューで「スキーマ」を選択し、右側の「Mutaion」下にあるputPostの「アタッチ」ボタンをクリックします。
![スクリーンショット 2022-12-08 15.28.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/c3295229-a035-d0a5-aac7-7105f754c6f2.png)

「アクション」ボタンで「ランタイムの更新」を選択します。
![スクリーンショット 2022-12-08 15.29.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/0493fe0d-5455-8fe9-b613-b93cf781d4f9.png)

「リゾルバータイプ」を「Unit Resolver(VTL only)」を選択し、Saveボタン押下
![スクリーンショット 2022-12-08 15.30.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/869f6245-6200-d504-b9d7-a8dbbddd20ed.png)

「データソース名」で先ほど設定したデータソースを選択し、「マッピングテンプレートの設定」でrequest, responseのマッピングテンプレートをEnableにします。
各テンプレートは既にサンプルで提供されているものだけで良いので、
リクエストマッピングはInvoke and forward arguments
レスポンスマッピングはReturn Lambda resultで選択し、「リゾルバー保存」ボタンを押下
![スクリーンショット 2022-12-08 15.32.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/b78513a7-103a-0c67-a467-c32e2fa12ba4.png)

テスト
---
AppSyncの左メニューで「クエリ」をクリックして実際にクエリを投げて疎通テストを行います。
クエリの入力欄に以下のクエリを入れて実行ボタンを押します。
```json
mutation PutPost {
  putPost(field: "putPost", pk: "hoge", sk: "huga", title: "Hello! AppSync!!") {
    pk
    sk
    title
  }
}
```

画面の右側に入力したデータが返ってきたら成功です！
![スクリーンショット 2022-12-08 15.36.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/15d330d1-6461-079c-d6cf-8f46cf8d9ee8.png)

実際にDynamoDBにデータが入っているのか確認してみましょう。
データはちゃんと入ってますね。
![スクリーンショット 2022-12-08 15.39.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342115/5ed799a8-fc60-36ab-9454-dfa624326af8.png)

まとめ
---
最近GraphQLでAPIを実装するのが当然ではあり、サーバーレス環境では結構使われているものなので、復習するために改めてAppSync + Lambdaで構築してみました。大体GraphQLのAPIがこんな感じで作られているのを認識できればと思います。
何か不明なものがありましたらコメントつけてください。
今回はAppSyncでリゾルバーを設定したやり方になってますが、AWS側ではダイレクトリゾルバーがあるので、次回はダイレクトリゾルバーを使ってAppSync + Lambdaをやってみようかと思います。

https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/direct-lambda-reference.html

参照サイト
---
https://blog.mmmcorp.co.jp/blog/2019/02/09/appsync-lambda-golang/
- スキーマやLambdaのコードはこちらのサイトから参照

https://zenn.dev/optimisuke/articles/105feac3f8e726830f8c
- go.mod作成する方法

SARAH Tech Blog
---
https://tech.sarah30.com/