AppSyncのデータソースとしてgolangのLambdaを利用する

AWS AppSyncのデータソースとして、
GO言語のLambdaハンドラーを指定した環境を構築したいと思います。

概要図

今回は、以下の図の通りに構築します。
AppSyncはMutationおよび、Queryメソッドを利用して、
Lambda Function経由でDynamoDBにアクセスし、結果を返却します。
概要図

AppSync API

最初にAppSyncのAPIを作成します。
今回はカスタムAPIのため、「Build from scratch」を選択します。
AppSync APIの作成

DynamoDB

DynamoDBのテーブルを作成します。
今回はString型のpk, skを定義したシンプルなテーブルとします。

AppSync Schema

AppSync APIのSchemaを記述します。
今回はPostの型にfieldという項目を設け、
Lambdaの処理を分岐させるように実装しました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
}

Lambda Handler

Lambdaハンドラーを実装します。
今回は、Serverless Application Model(SAM)を用いました。
golangのswitch構文によりfieldの値で処理が分岐し、
DynamoDBへのputまたは、getを実行します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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("[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("appsync-lambda-go"),
}

_, 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("appsync-lambda-go"),
}

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)
}

AppSync DataSource

実装済みのLambda FunctionをAppSyncのデータソースに指定します。

AppSync データソースの指定

AppSync Schema Resolver

Mutationおよび、QueryのResolverを指定します。
今回は下記のリクエスト・レスポンスマッピングテンプレートを設定しました。

request mapping template.

1
2
3
4
5
{
"version" : "2017-02-28",
"operation": "Invoke",
"payload": $util.toJson($context.arguments)
}

response mapping template.

1
$util.toJson($context.result)

動作確認

動作確認をしていきます。

Mutation

AppSyncのQueries画面で下記クエリを記述し、実行します。

1
2
3
4
5
6
7
mutation PutPost {
putPost(field: "putPost", pk: "hoge", sk: "huga", title: "Hello! AppSync!!") {
pk
sk
title
}
}

実行結果

DynamoDBのデータ内容
Management Consoleの結果画面
DynamoDBにデータが保存され、レスポンスも期待した値が返却されました!

Query

AppSyncのQueriess画面に下記クエリを記述し、実行します。

1
2
3
4
5
6
7
query GetPost {
singlePost(field: "singlePost", pk: "hoge", sk: "huga") {
pk
sk
title
}
}

実行結果

Management Consoleの結果画面
Mutationで登録したデータが、取得できています!

データソースにLambdaを指定した場合の実装は無限大

Lambdaで処理を記述することで、その実装は無限大です。
例えば、以下の図のようにS3にリクエストパラメータを保存し、
分析用途としても利用することもできるでしょう。
S3に保存する例

改善点

今回はリクエストパラメータにfieldという項目を用意し、
Lambdaの処理を分岐しました。

しかしながら、AppSyncのためのAppSyncResolverTemplateを利用することが
最適解ではないかと思います。
AppSyncResolverTemplateを利用するよう、改善していければと思います。

AppSyncResolverTemplateによる実装例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"context"
"fmt"

"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)

func handler(ctx context.Context, event events.AppSyncResolverTemplate) error {

fmt.Printf("Version: %s\n", event.Version)
fmt.Printf("Operation: %s\n", event.Operation)
fmt.Printf("Payload: %s\n", string(event.Payload))

return nil
}
このエントリーをはてなブックマークに追加