データソースなしのAppSyncを構築する
AppSyncは以下のサービスの中からデータソースを選択することができます。
- Lambda
- DynamoDB
- OpenSearch
- None
- HTTPエンドポイント
- RDS
今回はデータソースなし(None)を構築する方法を確認します。
なおAppSyncの基本的な解説と、DynamoDBをデータソースとする構成については、以下のページをご確認ください。
Lambdaをデータソースとする構成については、以下のページをご確認ください。
構築する環境
AppSync内でスキーマを定義し、以下の2つの操作が実現できるよう設定を行います。
- 扱うデータは2つのフィールド(keyとdatetime)を持つ。
- 保存されている全データを取得するクエリ。
- データを追加するミューテーション。
先述の通り、今回の構成では、AppSyncにデータソースは作成しません。
ですから疑似的に上記の動作を実現するために、リゾルバに仮データを定義します。
AppSyncによるGraphQL APIを実行するクライアントとして、Lambda関数を作成します。
Function URLを有効化し、URLクエリパラメータで実行する操作を指定できるようにします。
関数のランタイム環境はPython3.8とします。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-fa/tree/main/054
テンプレートファイルのポイント解説
データソース
Resources:
DataSource:
Type: AWS::AppSync::DataSource
Properties:
ApiId: !GetAtt GraphQLApi.ApiId
Name: DataSource
Type: NONE
Code language: YAML (yaml)
ポイントはTypeプロパティです。
データソースを持たない場合は、「NONE」を指定します。
スキーマ
Resources:
GraphQLSchema:
Type: AWS::AppSync::GraphQLSchema
Properties:
ApiId: !GetAtt GraphQLApi.ApiId
Definition: |
schema {
query: Query
mutation: Mutation
}
type Query {
listSampleDatas: [SampleData]
}
type Mutation {
addSampleData(key: String!): SampleData
}
type SampleData {
key: String!
datetime: String
}
Code language: YAML (yaml)
データソースは作成しませんが、スキーマレベルでは特別な対応は不要です。
通常通りスキーマを定義します。
先述の通り、今回は先述の操作を実装しますので、それに準じたスキーマを定義します。
リゾルバ
まずデータを追加するミューテーション用のリゾルバを確認します。
Resources:
addSampleDataResolver:
Type: AWS::AppSync::Resolver
DependsOn:
- GraphQLSchema
Properties:
ApiId: !GetAtt GraphQLApi.ApiId
DataSourceName: !GetAtt DataSource.Name
FieldName: addSampleData
Kind: UNIT
RequestMappingTemplate: |
{
"version": "2018-05-29",
"payload": {
"key": $util.toJson($context.arguments.key)
}
}
ResponseMappingTemplate: |
{
"key": $util.toJson($context.result.key),
"datetime": $util.toJson($util.time.nowFormatted("yyyy-MM-dd HH:mm:ssZ"))
}
TypeName: Mutation
Code language: YAML (yaml)
ポイントはリゾルバのリクエストマッピングとレスポンスマッピングの設定です。
データソースを持たない場合のマッピングの設定は以下のページに詳しいです。
データソースのないAppSyncにおいて、マッピング設定は特徴的です。
リモートのデータソースを呼び出すのではなく、ローカルリゾルバーはリクエストマッピングテンプレートの結果をレスポンスマッピングテンプレートに転送します。
チュートリアル: ローカルリゾルバー
つまりデータソースがないため、リクエストマッピングで生じた値が、そのままレスポンスマッピングに渡されるという意味です。
まずリクエストマッピング(RequestMappingTemplateプロパティ)を確認します。
payloadを使用すると、リクエストマッピングからレスポンスマッピングに値を渡すことができます。
また$context.argumentsでミューテーションの引数を取得できます。
まとめますと、ミューテーションを実行する上で渡された文字列を、keyの値としてレスポンスマッピングに渡すという挙動となります。
次にレスポンスマッピング(ResponseMappingTemplateプロパティ)を確認します。
$context.resultでレスポンスマッピングから渡された値を取得することができます。
$util.time.nowFormattedで現在日時を取得することができます。
まとめますと、レスポンスマッピングから渡された引数をkeyの値としてセットし、現在日時をdatetimeの値としてセットした上で、ミューテーション結果として返すという挙動となります。
次に全データを取得するクエリ用のリゾルバを確認します。
Resources:
ListSampleDatasResolver:
Type: AWS::AppSync::Resolver
DependsOn:
- GraphQLSchema
Properties:
ApiId: !GetAtt GraphQLApi.ApiId
DataSourceName: !GetAtt DataSource.Name
FieldName: listSampleDatas
Kind: UNIT
RequestMappingTemplate: |
{
"version": "2018-05-29",
}
ResponseMappingTemplate: |
[
{
"key": "hoge",
"datetime": "2022-05-15 12:34:56.7890"
},
{
"key": "foo",
"datetime": "2022-05-16 12:34:56.7890"
},
{
"key": "bar",
"datetime": "2022-05-17 12:34:56.7890"
}
]
TypeName: Query
Code language: YAML (yaml)
まずリクエストマッピングですが、先ほどとは異なり、payload設定は行いません。
つまりリクエストマッピングからレスポンスマッピングには、何にも渡さないという挙動となります。
次にレスポンスマッピングですが、ハードコーディングした値を返すように設定します。
つまり常に検索結果をイメージした同じデータを返すという挙動となります。
(参考)GraphQLクライアントLambda関数
import json
import os
import time
from gql import Client, gql
from gql.transport.aiohttp import AIOHTTPTransport
api_key = os.environ['API_KEY']
graphql_url = os.environ['GRAPHQL_URL']
transport = AIOHTTPTransport(
url=graphql_url,
headers={
'x-api-key': api_key
})
client = Client(transport=transport, fetch_schema_from_transport=True)
ADD = 'Put'
LIST = 'List'
def lambda_handler(event, context):
operation = ''
document = None
result = None
if not 'queryStringParameters' in event or (
not 'operation' in event['queryStringParameters']):
operation = LIST
else:
operation = event['queryStringParameters']['operation']
if operation == ADD:
key = event['queryStringParameters']['key']
document = gql(
"""
mutation AddSampleData($key: String!) {
addSampleData(key: $key) {
key
datetime
}
}
"""
)
params = {
'key': key
}
result = client.execute(document, variable_values=params)
elif operation == LIST:
document = gql(
"""
query ListSampleDatas {
listSampleDatas {
key
datetime
}
}
"""
)
result = client.execute(document)
return {
'statusCode': 200,
'body': json.dumps(result, indent=2)
}
Code language: Python (python)
GraphQLクエリを実行するLambda関数です。
今回はPython用GraphQLクライアントライブラリとして、GQLを使用します。
https://github.com/graphql-python/gql
GQLを使って、スキーマで定義したクエリ・ミューテーションを実行します。
Pythonの場合、event[‘queryStringParameters’]でURLクエリパラメータを取得することがきます。
URLクエリパラメータで必要なパラメータを渡します。
- operation:実行する操作。
- key:操作がADDの場合に必要。追加するデータのkey名。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
Lambda関数用のデプロイパッケージを用意する
Lambda関数を作成する場合、3つの方法があります。
今回はデプロイパッケージをS3バケットにアップロードする方法を選択します。
詳細につきましては、以下のページをご確認ください。
Lambdaレイヤー用のデプロイパッケージを用意する
先述のGQLをLambdaレイヤーとして用意します。
Lambdaレイヤーに関する詳細は、以下のページをご確認ください。
なおLambdaレイヤー用パッケージを作成ためのコマンドは以下となります。
$ sudo pip3 install --pre gql[all] -t python
$ zip -r layer.zip python
Code language: Bash (bash)
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- AppSync API:fa-054-GraphQLApi
- GraphQLクライアントLambda関数のFunction URL:https://dkaky6lcgzcthyow5su5q3gsyi0tmyqt.lambda-url.ap-northeast-1.on.aws/
AWS Management Consoleから、AppSyncを確認します。
データソースをみると、Typeが「NONE」とあります。
つまり今回作成したAppSync APIには、データソースの実体は存在しないということです。
スキーマ・リゾルバの設定を確認します。
CloudFormationテンプレートで定義した通りです。
動作確認
準備が整いましたので、GraphQLクライアントLambda関数のFunction URLにアクセスします。
まずデータの追加(Add)をイメージした操作です。
URLクエリのoperationの値に「Add」、keyの値に「spam」を指定します。
addSampleDataミューテーションが実行されました。
keyの値に先ほどクエリのパラメータにした「spam」が、datetimeの値に現在日時がセットされています。
このようにデータソースがなくとも、疑似的にデータを登録した時と同様の結果を得ることができました。
次にデータの一覧(List)をイメージした操作です。
URLクエリのoperationの値に「List」を指定します。
listSampleDatasクエリが実行されました。
このクエリに対応するレスポンスマッピングでハードコーディングしたデータがそのまま返ってきました。
このようにデータソースがなくとも、疑似的にデータ一覧を取得したときと同様の結果を得ることができました。
まとめ
データソースがないAppSyncを設定する構成をご紹介しました。