こんにちは。アプリケーションサービス部 河野です。
今回は、 GraphQL の @function ディレクティブを使用して Lambda リゾルバを構築してみました。
概要
Amplify CLI は いくつかの GraphQL ディレクティブを提供しています。
デフォルトスキーマにある @model もその内の一つです。
API (GraphQL) - Directives reference - AWS Amplify Docs
今回は、Lambda リゾルバを簡単に構築できる @function ディレクティブの使い方について紹介します。
Lambda リゾルバ
Lambda リゾルバは、AppSyncの Query または Mutation をAWS Lambda 関数にマッピングし、AppSync 経由で Lambda を実行することができる機能です。
https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/tutorial-lambda-resolvers.html
vtl を介さず直接 Lambda に処理を移譲できるダイレクト Lambda リゾルバもありますが、@function で作成されるものは、通常の Lambda リゾルバになります。
Lambda と ダイレクト Lambda リゾルバについては、以下のブログをご参照ください。
実践
以下のドキュメントを参考に実際に @function を試していきます。
API (GraphQL) - Custom business logic (Lambda function, HTTP, and VTL resolvers) - AWS Amplify Docs
※ amplify init は実行済みであるとします
1. 文字列を返す Lambda を呼び出す
GraphQL の Query から、文字列だけを返すシンプルな Lambda 関数を実行します。
1-1. GraphQL API を追加する
amplify add api で GraphQL API を追加します。
amplify add api
? Select from one of the below mentioned services: GraphQL
? Here is the GraphQL API that we will create. Select a setting to edit or continue Continue
? Choose a schema template: Blank Schema
schema.graphql を Query を追加します。
type Query { echo(msg: String): String @function(name: "echofunction-${env}") }
@function ディレクティブの後に、関数名をを加えます。
CLI から function(Lambda) を追加した場合は、"-{環境名}" が付与されますので、事前に設定しています。
1-2. Lambda を追加する
Lambda 関数を追加します。 python を使用することが多いので、今回は python の Lambda を追加します。
amplify add function ? Select which capability you want to add: Lambda function (serverless function) ? Provide an AWS Lambda function name: echofunction // スキーマで設定した関数名 ? Choose the runtime that you want to use: Python
index.py を編集します。
import json from aws_lambda_powertools.utilities.data_classes.appsync_resolver_event import ( AppSyncResolverEvent, ) def handler(event, context): print("received event:") event: AppSyncResolverEvent = AppSyncResolverEvent(event) print(event) return event.arguments.get('msg')
event にどのような情報が格納されているかは、aws-lambda-powertools を確認しました。
data class が提供されていたので、せっかくなので使ってみました。(使用する場合は、pipenv install aws-lambda-powertools を実行してください)
1-3. デプロイ
バックエンドをデプロイします。
amplify push --y
...
✔ Generated GraphQL operations successfully and saved at ../../../../src/graphql
Deployment state saved successfully.
ちなみに、ダイレクト Lambad リゾルバーではないことは、GraphQL の resolvers に Lambda を呼び出すためのリゾルバーが追加されていることから確認できます。
1-4. 動作確認
AppSync のコンソールから Query の動作確認を行います。
問題なく実行できました。
Lambda の Cloudwatch Logs も確認してみます。 実行されていますね。
2. パイプラインリゾルバー
複数の Lambda を連続的に呼び出すことができます。
また後続の Lambad は前回の Lambda の実行結果を利用することができます。
先ほど作成した Lambda の実行結果に文字列を連結する関数を試してみます。
2-1. スキーマを修正する
呼び出したい順に @function ディレクティブを定義するだけでOKです。
echofunction → concatenateEcho の順で実行されます。
type Query { echo(msg: String): String @function(name: "echofunction-${env}") concatenateEcho(msg: String): String @function(name: "echofunction-${env}") @function(name: "concatenateEcho-${env}") }
2-2. Lambda を追加する
先ほどのスキーマで定義した、concatenateEcho Lambda を追加します。
amplify add function ? Select which capability you want to add: Lambda function (serverless function) ? Provide an AWS Lambda function name: concatenateEcho ? Choose the runtime that you want to use: Python Only one template found - using Hello World by default.
index.py を編集します。
import json from aws_lambda_powertools.utilities.data_classes.appsync_resolver_event import ( AppSyncResolverEvent, ) def handler(event, context): print("received event:") event: AppSyncResolverEvent = AppSyncResolverEvent(event) print(event) # 指定した文字列と echo 関数の結果を連結する return event.arguments.get('msg') + event.prev_result
Lambda が別なので、ライブラリ等は再度インストールする必要があります。 共通ライブラリなどは、Lambda Layer を追加した方が良さそうです。
2-3. デプロイ
先ほどと同じようにデプロイします。
amplify push --y
...
✔ Generated GraphQL operations successfully and saved at ../../../../src/graphql
Deployment state saved successfully.
2-4. 動作確認
AppSync のコンソールから Query の動作確認を行います。
文字列が連結されていますね!
3. 既存の DynanmoDB にアクセスする
以下の通り、既存の DynamoDB にアクセスしてデータを取得してみます。
3-1. スキーマを修正する
type SampleDb{ id: ID! name: String! description: String! } type Query { echo(msg: String): String @function(name: "echofunction-${env}") concatenateEcho(msg: String): String @function(name: "echofunction-${env}") @function(name: "concatenateEcho-${env}") getExternalDdbItem(id: String): SampleDb @function(name: "getExternalDdbItem-${env}") }
今までと同様に、@function ディレクティブを付与したクエリを追加しました。
また、既存の テーブルに合わせた型を用意し、返り値として定義しています。
3-2. dynamodb をインポートする
以下のコマンドを実行して、DynamoDB を Storage として Amplify に取り込みます。
amplify import storage 2023-09-05 12:25 ? Select from one of the below mentioned services: DynamoDB table - NoSQL Database ✔ Select the DynamoDB Table you want to import: · sample-ddb ✅ DynamoDB Table 'sample-ddb' was successfully imported. Next steps: - This resource can now be accessed from REST APIs (`amplify add api`) and Functions (`amplify add function`)
Storage - Use an existing S3 bucket or DynamoDB table - AWS Amplify Docs
Amplify に取り込むことで、環境変数からテーブル名を取得できたり、DynamoDB にアクセスするための権限付与がCLI から実施できるようになります。
3-3. Lambda を追加する
同様にスキーマで定義した、getExternalDdbItem Lambda を追加します。
amplify add function 12.4s 2023-09-05 12:39 ? Select which capability you want to add: Lambda function (serverless function) ? Provide an AWS Lambda function name: getExternalDdbItem ? Choose the runtime that you want to use: Python Only one template found - using Hello World by default. Available advanced settings: - Resource access permissions - Scheduled recurring invocation - Lambda layers configuration - Environment variables configuration - Secret values configuration ? Do you want to configure advanced settings? Yes ? Do you want to access other resources in this project from your Lambda function? Yes # ← import した DynamoDB のアクセスするための設定 ? Select the categories you want this function to have access to. storage ? Select the operations you want to permit on sampleddb create, read, update, delete You can access the following resource attributes as environment variables from your Lambda function ENV REGION STORAGE_SAMPLEDDB_ARN STORAGE_SAMPLEDDB_NAME STORAGE_SAMPLEDDB_STREAMARN ? Do you want to invoke this function on a recurring schedule? No ? Do you want to enable Lambda layers for this function? No ? Do you want to configure environment variables for this function? No ? Do you want to configure secret values this function can access? No ? Do you want to edit the local lambda function now? Yes Edit the file in your editor: develop/amplify-studio-sandbox/05_function_directive/amplify/backend/function/getExternalDdbItem/src/index.py ? Press enter to continue Successfully added resource getExternalDdbItem locally.
? Do you want to access other resources in this project from your Lambda function? Yes
ここで、import した テーブルのアクセス設定を実施しています。
Lambda の権限設定(IAM Role の付与)及びテーブル情報(テーブル名、テーブルARN、ストリームARN)を環境変数として設定してくれます。
index.py を修正します。
指定した ID をもとに getItem して返す処理を実装しています。
import os import json import boto3 from aws_lambda_powertools.utilities.data_classes.appsync_resolver_event import ( AppSyncResolverEvent, ) dynaomodb = boto3.resource("dynamodb") def handler(event, context): print("received event:") print(event) table = dynaomodb.Table(os.environ["STORAGE_SAMPLEDDB_NAME"]) event: AppSyncResolverEvent = AppSyncResolverEvent(event) return table.get_item(Key={"id": event.arguments.get("id")}).get("Item")
3-4. デプロイ
同様にデプロイします。
amplify push ... Deploying root stack 05functiondirective [ =================================------- ] 5/6 amplify-05functiondirective-d… AWS::CloudFormation::Stack UPDATE_COMPLETE Tue Sep 05 2023 13:09:04… functiongetExternalDdbItem AWS::CloudFormation::Stack UPDATE_COMPLETE Tue Sep 05 2023 13:08:50… api05functiondirective AWS::CloudFormation::Stack UPDATE_COMPLETE Tue Sep 05 2023 13:08:39… functionconcatenateEcho AWS::CloudFormation::Stack UPDATE_COMPLETE Tue Sep 05 2023 13:08:28… functionechofunction AWS::CloudFormation::Stack UPDATE_COMPLETE Tue Sep 05 2023 13:08:28… Deployed function echofunction [ ======================================== ] 3/3 Deployed function concatenateEcho [ ======================================== ] 3/3 Deployed function getExternalDdbItem [ ======================================== ] 4/4 LambdaFunction AWS::Lambda::Function UPDATE_COMPLETE Tue Sep 05 2023 13:08:39… Deployment state saved successfully.
3-5. 動作確認
id を指定して、クエリを実行すると、テーブルデータを取得することができました。
さいごに
今回は、@function ディレクティブの使い方について簡単に紹介しました。
@functin ディレクティブを使えば、簡単に Lambda リゾルバを実装することができました。
特に、既存の DynamoDB へのアクセスについては import した DynamoDB テーブルと問題なく連携できた(環境変数や、権限付与など)のは良い発見でした。
今後も色々試していきたいと思います。
swx-go-kawano (執筆記事の一覧)