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の処理を分岐させるように実装しました。

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を実行します。

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.

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

response mapping template.

$util.toJson($context.result)

動作確認

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

Mutation

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

mutation PutPost {
  putPost(field: "putPost", pk: "hoge", sk: "huga", title: "Hello! AppSync!!") {
    pk
    sk
    title
  }
}

実行結果

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

Query

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

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による実装例

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
", event.Version)
  fmt.Printf("Operation: %s
", event.Operation)
  fmt.Printf("Payload: %s
", string(event.Payload))

    return nil
}

MMMは、会社としてもAWS Lambdaに力を入れています。ぜひ以下のページもあわせてご覧ください。

サーバーレスアーキテクチャ(AWS Lambda)