「AWS無料相談会」をオンラインで開催中

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)