@function ディレクティブを使用して、Lambda リゾルバを構築してみよう

記事タイトルとURLをコピーする

こんにちは。アプリケーションサービス部 河野です。

今回は、 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 リゾルバについては、以下のブログをご参照ください。

blog.serverworks.co.jp

実践

以下のドキュメントを参考に実際に @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 を実行してください)

docs.powertools.aws.dev

github.com

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 (執筆記事の一覧)