Pythonで、AWS Lambda をData sourceに持つ AWS AppSync API を呼んでみた

前回、Data sourceがDynamoDBである AWS AppSync APIPythonで呼んでみました。
Pythonで、 AWS AppSyncのquery・mutation・subscriptionを試してみた - メモ的な思考的な

 
ただ、AWS AppSync のData sourceでは、DynamoDBの他

などもData sourceとして設定できます。

そこで今回は、AWS LambdaをData source にもつ AWS AppSync APIを作成し、Pythonから呼んでみることにしました。

なお、公式ドキュメントにもチュートリアルはありますが、もう少し単純なもので試してみます。
チュートリアル : AWS Lambda リゾルバー - AWS AppSync

 
また、誤りがありましたらご指摘ください。

 
目次

 

環境

  • Python 3.7.3

    • graphqlclient 0.2.4
  • AWS AppSync

    • データソース:AWS Lambda
      • LambdaはNode.jsで実装
    • API Key 認証
    • データ取得(Query)のみ、APIとして実装

 

Lambdaを作成

AppSync APIを作る前に、Data sourceとなるLambdaを作成します。

AppSync APIから呼ぶLambdaは、

  • event
    • AppSync API から渡している値
  • context
    • Lambdaの実行環境情報
  • callback
    • AppSync APIに値を返すための関数

の3つの引数を伴って呼び出されます。

今回は、eventとcontextの値をAppSync APIに戻すようなLambdaを用意します。

 
Lambdaの設定です。

項目
関数名 AppSyncDatasource
ランタイム Node.js 10.x
アクセス権限 - 実行ロール 基本的なLambdaアクセス権限で新しいロールを作成

 
実装です。

exports.handler = (event, context, callback) => {
    // callback関数を使って、レスポンスを返す
    // 今回は、eventとcontextの値を返してみる
    callback(null, {
        "field": event.field, 
        "event": JSON.stringify(event, 3),
        "context": JSON.stringify(context, 3),
    });
};

 

AppSync APIの作成

新規作成

今回は Build from scratch より作成します。

API nameは、 My AppSync Lambda App とします。

 

Data sourceの作成

設定内容は以下の通りです。

項目
Data source name LambdaSource
Data source type AWS Lambda function
Region 作成したLambdaと同じリージョン
Function ARN 作成したLambda (AppSyncDatasource)
Create or use an existing role New role

 

Schemaの作成

AppSync APIのメニュー Schema より作成します。

今回はデータ取得を試しますので、Queryだけ用意します。

type Query {
    ham(req: String!): ResponseData
    spam(req: String!): ResponseData
}

type ResponseData {
    field: String
    event: String
    context: String
}

schema {
    query: Query
}

ポイントは type ResponseData です。定義した項目

  • field
  • event
  • context

は、いずれもLambdaのcallback関数の第二引数(連想配列)のキーと同じ名称とします。

// 参考:Lambdaの該当箇所
callback(null, {
    "field": event.field, 
    "event": JSON.stringify(event, 3),
    "context": JSON.stringify(context, 3),
});

 
また、

という2つのQueryを用意し、少しだけ実装を変えてみます。

 

SchemaとResolverを紐付ける

Schemaを作成しただけではAppSync APIとLambdaは連携しません。

そこで、SchemaとResolverを紐づけし、連携できるようにします。

Schemaメニューの右にある Resolvers から、Queryの hamAttach をクリックします。

Resolverには

  • request mapping template
  • response mapping template

の2つがあるため、それぞれ設定していきます。

 

request mapping template

request mapping templateでは、AppSync APIに来たリクエストをどのようにLambdaへ流すかを指定します。

まずは Query ham を設定します。

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

 
ポイントは payload です。

payloadに指定した連想配列のキーが、Lambdaの引数 event のプロパティ名となります。

例えば、AppSync APIのpayloadのキー field の値は、Lambda内では event.field で取得できます。

// 参考:Lambdaの該当箇所
exports.handler = (event, context, callback) => {
    callback(null, {
        "field": event.field, 
        ...

 
また、 $context.args は、AppSync API のリクエストパラメータを指します。

 
一方、Query spamの request mapping template では固定値を設定している項目 field の値だけを変えてみます。

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

 

response mapping template

こちらは、Lambdaの戻り値を、AppSync APIへどのように渡すかを指定します。

Query ham/spam とも、Lambdaの戻り値をそのままJSONとして返すため、

$util.toJson($context.result)

とします。

これを元にして、AppSync APIのGraphQLで値を取得できます。

 
以上で、AppSync APIの設定が終わりました。

 

PythonのGraphQLクライアント作成

前回同様、 graphqlclient を使って、AppSync APIへとアクセスしてみます。

API_KEY, API_URLは、AppSync APIのSettingsにあるものを設定します。

from graphqlclient import GraphQLClient

from secret import API_KEY, API_URL


def execute_query_api(gql_client):
    # ham Queryの実行
    ham = """
        query {
          ham(req: "456") {
            field
            event
            context
          }
        }
    """
    ham_result = gql_client.execute(ham)
    print(ham_result)

    # spam Queryの実行
    spam = """
        query {
          spam(req: "789") {
            field
            event
            context
          }
        }
    """
    spam_result = gql_client.execute(spam)
    print(spam_result)


if __name__ == '__main__':
    c = GraphQLClient(API_URL)
    c.inject_token(API_KEY, 'X-Api-Key')
    execute_query_api(c)

 

実行結果

上記のPythonを実行すると以下の結果が得られました*1

Query: ham の結果

{"data":
  {"ham":{
   "field":"from_ham",
   "event":"{\"field\":\"from_ham\",\"args\":{\"req\":\"456\"}}",
   "context":"{\"callbackWaitsForEmptyEventLoop\":true,
               \"functionVersion\":\"$LATEST\",
               \"functionName\":\"AppSyncDatasource\",
               \"memoryLimitInMB\":\"128\",
               \"logGroupName\":\"/aws/lambda/AppSyncDatasource\",
               \"logStreamName\":\"2019/07/xx/[$LATEST]xxx\",
               \"invokedFunctionArn\":\"arn:aws:lambda:region:iam:function:AppSyncDatasource\",
               \"awsRequestId\":\"xx-xx-xx-xx-xx\"}"}}}

 
Query: spamの結果

{"data":
  {"spam":{
   "field":"from_spam",
   "event":"{\"field\":\"from_spam\",\"args\":{\"req\":\"789\"}}",
   "context":"{\"callbackWaitsForEmptyEventLoop\":true,
               \"functionVersion\":\"$LATEST\",
               \"functionName\":\"AppSyncDatasource\",
               \"memoryLimitInMB\":\"128\",
               \"logGroupName\":\"/aws/lambda/AppSyncDatasource\",
               \"logStreamName\":\"2019/07/xx/[$LATEST]xxx\",
               \"invokedFunctionArn\":\"arn:aws:lambda:region:iam:function:AppSyncDatasource\",
               \"awsRequestId\":\"xx-xx-xx-xx-xx\"}"}}}

 

この結果より、

Python -> AWS AppSync API -> AWS Lambda

という経路で、データを取得できているようです。

 

ソースコード

GitHubに上げました。ディレクトlambda_datasource が今回のソースコードです。
https://github.com/thinkAmi-sandbox/AWS_AppSync_python_client-sample

*1:見やすくするよう改行を入れていますが、実際は一行です