AppSync – データソース:None

目次

データソースなしのAppSyncを構築する

AppSyncは以下のサービスの中からデータソースを選択することができます。

  • Lambda
  • DynamoDB
  • OpenSearch
  • None
  • HTTPエンドポイント
  • RDS

今回はデータソースなし(None)を構築する方法を確認します。
なおAppSyncの基本的な解説と、DynamoDBをデータソースとする構成については、以下のページをご確認ください。

あわせて読みたい
CFNでAppSync入門 – データソース:DynamoDB 【CloudFormationを使用してAppSync環境を構築】 AppSyncはAWSが提供するマネージドサービスの1つで、容易にGraphQL APIを構築することができます。 本ページはAppSync...

Lambdaをデータソースとする構成については、以下のページをご確認ください。

あわせて読みたい
AppSync – データソース:Lambda 【AppSyncの データソースにLambdaを設定する構成】 AppSyncは以下のサービスの中からデータソースを選択することができます。 Lambda DynamoDB OpenSearch None HTTPエ...

構築する環境

Diagram of AppSync - Data Source: None

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)

ポイントはリゾルバのリクエストマッピングとレスポンスマッピングの設定です。
データソースを持たない場合のマッピングの設定は以下のページに詳しいです。

https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/resolver-mapping-template-reference-none.html

データソースのない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バケットにアップロードする方法を選択します。
詳細につきましては、以下のページをご確認ください。

あわせて読みたい
CloudFormationでLambdaを作成する3パータン(S3/インライン/コンテナ) 【CloudFormationでLambdaを作成する】 CloudFormationでLambdaを作成する場合、大別すると以下の3パターンあります。 S3バケットにコードをアップロードする インライ...

Lambdaレイヤー用のデプロイパッケージを用意する

先述のGQLをLambdaレイヤーとして用意します。
Lambdaレイヤーに関する詳細は、以下のページをご確認ください。

あわせて読みたい
CFNでLambdaレイヤー作成 【CloudFormationでLambdaレイヤー作成】 本ページでは、CloudFormationでLambdaレイヤーを作成する方法を確認します。 Lambda レイヤーは、Lambda 関数で使用できるラ...

なおLambdaレイヤー用パッケージを作成ためのコマンドは以下となります。

$ sudo pip3 install --pre gql[all] -t python

$ zip -r layer.zip python
Code language: Bash (bash)

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 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 of AppSync Data Source.

データソースをみると、Typeが「NONE」とあります。
つまり今回作成したAppSync APIには、データソースの実体は存在しないということです。

スキーマ・リゾルバの設定を確認します。

Schema and Resolvers of AppSync.
Detail of Resolver 1.
Detail of Resolver 2.

CloudFormationテンプレートで定義した通りです。

動作確認

準備が整いましたので、GraphQLクライアントLambda関数のFunction URLにアクセスします。

まずデータの追加(Add)をイメージした操作です。
URLクエリのoperationの値に「Add」、keyの値に「spam」を指定します。

Result of addSampleData Mutation.

addSampleDataミューテーションが実行されました。
keyの値に先ほどクエリのパラメータにした「spam」が、datetimeの値に現在日時がセットされています。
このようにデータソースがなくとも、疑似的にデータを登録した時と同様の結果を得ることができました。

次にデータの一覧(List)をイメージした操作です。
URLクエリのoperationの値に「List」を指定します。

Result of listSampleDatas Query.

listSampleDatasクエリが実行されました。
このクエリに対応するレスポンスマッピングでハードコーディングしたデータがそのまま返ってきました。
このようにデータソースがなくとも、疑似的にデータ一覧を取得したときと同様の結果を得ることができました。

まとめ

データソースがないAppSyncを設定する構成をご紹介しました。

目次