AWS

AppSync – データソース:RDS(Aurora Serverless)

スポンサーリンク
AppSync - データソース:RDS(Aurora Serverless) AWS
スポンサーリンク
スポンサーリンク

AppSyncのデータソースにRDS(Aurora Serverless)を設定する

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

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

今回はRDSをデータソースにする構成を確認します。
RDSとして指定できるリソースはAurora Serverlessです。

なおAppSyncの基本的な解説と、DynamoDBをデータソースとする構成については、以下のページをご確認ください。

構築する環境

Diagram of AppSync - Data Source: RDS(Aurora Serverless)

データソースとして動作するAurora Serverlessを作成します。

AppSyncからAurora Serverlessを操作するために、スキーマ・リゾルバを定義します。
本ページは、以下のAWS公式ページを参考に進めます。

チュートリアル: Aurora Serverless - AWS AppSync
AWS AppSync の Aurora Serverless チュートリアル。

2つのLambda関数を作成します。
関数のランタイム環境はPython3.8とします。

1つ目の関数はCloudFormationカスタムリソースに関連付けて、スタック作成時に実行されるように設定します。
この関数の働きは、Aurora Serverless DBを初期化することです。

2つ目の関数はGraphQL APIを実行するクライアントとして設定します。
Function URLを有効化し、URLクエリパラメータで実行する操作を指定できるようにします。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

awstut-fa/061 at main · awstut-an-r/awstut-fa
Contribute to awstut-an-r/awstut-fa development by creating an account on GitHub.

テンプレートファイルのポイント解説

本ページはAppSyncのデータソースにAurora Serverlessを指定する方法を中心に取り上げます。

Aurora Serverless作成に関しては、以下のページをご確認ください。

Aurora ServerlessのData APIを有効化する方法については、以下のページをご確認ください。

データソース

Resources: DataSource: Type: AWS::AppSync::DataSource Properties: ApiId: !GetAtt GraphQLApi.ApiId Name: DataSource RelationalDatabaseConfig: RdsHttpEndpointConfig: AwsRegion: !Ref AWS::Region AwsSecretStoreArn: !Ref SecretArn DatabaseName: !Ref DBName DbClusterIdentifier: !Ref DBClusterArn RelationalDatabaseSourceType: RDS_HTTP_ENDPOINT ServiceRoleArn: !GetAtt DataSourceRole.Arn Type: RELATIONAL_DATABASE
Code language: YAML (yaml)

ポイントはTypeプロパティです。
Aurora Serverlessをデータソースとする場合は、「RDS_HTTP_ENDPOINT」を指定します。

またRelationalDatabaseConfigプロパティで詳細を設定します。
Aurora ServerlessのクラスターのARNや、クラスターに接続するために使用するSecrets Managerのシークレット等を指定します。

データソース用IAMロール

Resources: DataSourceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: sts:AssumeRole Principal: Service: appsync.amazonaws.com Policies: - PolicyName: !Sub "${Prefix}-DataSourcePolicy" PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - rds-data:DeleteItems - rds-data:ExecuteSql - rds-data:ExecuteStatement - rds-data:GetItems - rds-data:InsertItems - rds-data:UpdateItems Resource: - !Ref DBClusterArn - !Sub "${DBClusterArn}:*" - Effect: Allow Action: - secretsmanager:GetSecretValue Resource: - !Ref SecretArn - !Sub "${SecretArn}:*"
Code language: YAML (yaml)

AppSyncがAurora Serverlessに接続して、SQL文を実行するためのIAMロールです。
AWS公式サイトで紹介されているポリシーを参考に作成しました。

スキーマ

Resources: GraphQLSchema: Type: AWS::AppSync::GraphQLSchema Properties: ApiId: !GetAtt GraphQLApi.ApiId Definition: | type Mutation { createPet(input: CreatePetInput!): Pet updatePet(input: UpdatePetInput!): Pet deletePet(input: DeletePetInput!): Pet } input CreatePetInput { type: PetType price: Float! } input UpdatePetInput { id: ID! type: PetType price: Float! } input DeletePetInput { id: ID! } type Pet { id: ID! type: PetType price: Float } enum PetType { dog cat fish bird gecko } type Query { getPet(id: ID!): Pet listPets: [Pet] listPetsByPriceRange(min: Float, max: Float): [Pet] } schema { query: Query mutation: Mutation }
Code language: YAML (yaml)

AWS公式ページを参考に、スキーマを定義します。

リゾルバ

リゾルバもAWS公式ページを参考に作成します。

参考としてcreatePetミューテーション用リゾルバを取り上げます。

Resources: CreatePetResolver: Type: AWS::AppSync::Resolver DependsOn: - GraphQLSchema Properties: ApiId: !GetAtt GraphQLApi.ApiId DataSourceName: !GetAtt DataSource.Name FieldName: createPet Kind: UNIT RequestMappingTemplate: | #set($id=$utils.autoId()) { "version": "2018-05-29", "statements": [ "insert into Pets VALUES ('$id', '$ctx.args.input.type', $ctx.args.input.price)", "select * from Pets WHERE id = '$id'" ] } ResponseMappingTemplate: | $utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0]) TypeName: Mutation
Code language: YAML (yaml)

RequestMappingTemplateおよびResponseMappingTemplateプロパティに、公式ページで紹介されているテンプレートマッピングを記述します。

(参考) GraphQLクライアントLambda関数

AppSyncによるGraphQL APIを実行するクライアントとして、Lambda関数を作成します。
この関数のFunction URLを有効化します。
Function URLに関する詳細は、以下のページをご確認ください。

以下に、関数で実行するPythonコードを取り上げます。

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) CREATE_PET = 'createPet' UPDATE_PET = 'updatePet' DELETE_PET = 'deletePet' GET_PET = 'getPet' LIST_PETS = 'listPets' LIST_PETS_BY_PRICE_RANGE = 'listPetsByPriceRange' def lambda_handler(event, context): operation = '' document = None result = None if not 'queryStringParameters' in event or ( not 'operation' in event['queryStringParameters']): operation = LIST_PETS else: operation = event['queryStringParameters']['operation'] if operation == CREATE_PET: document = gql( """ mutation add($type: PetType!, $price: Float!) { createPet(input: { type: $type, price: $price }){ id type price } } """ ) params = { 'type': event['queryStringParameters']['type'], 'price': event['queryStringParameters']['price'] } result = client.execute(document, variable_values=params) elif operation == UPDATE_PET: document = gql( """ mutation update($id: ID!, $type: PetType!, $price: Float!) { updatePet(input: { id: $id, type: $type, price: $price }){ id type price } } """ ) params = { 'id': event['queryStringParameters']['id'], 'type': event['queryStringParameters']['type'], 'price': event['queryStringParameters']['price'] } result = client.execute(document, variable_values=params) elif operation == DELETE_PET: document = gql( """ mutation delete($id: ID!) { deletePet(input: { id: $id }){ id type price } } """ ) params = { 'id': event['queryStringParameters']['id'] } result = client.execute(document, variable_values=params) elif operation == GET_PET: document = gql( """ query get($id: ID!) { getPet(id: $id){ id type price } } """ ) params = { 'id': event['queryStringParameters']['id'] } result = client.execute(document, variable_values=params) elif operation == LIST_PETS: document = gql( """ query allpets { listPets { id type price } } """ ) result = client.execute(document) elif operation == LIST_PETS_BY_PRICE_RANGE: document = gql( """ query list($min: Float!, $max: Float!) { listPetsByPriceRange(min: $min, max: $max) { id type price } } """ ) params = { 'min': event['queryStringParameters']['min'], 'max': event['queryStringParameters']['max'] } result = client.execute(document, variable_values=params) return { 'statusCode': 200, 'body': json.dumps(result, indent=2) }
Code language: Python (python)

GraphQLクエリを実行するLambda関数のコードです。
今回はPython用GraphQLクライアントライブラリとして、GQLを使用します。

GitHub - graphql-python/gql: A GraphQL client in Python
A GraphQL client in Python. Contribute to graphql-python/gql development by creating an account on GitHub.

GQLを使って、スキーマで定義したクエリ・ミューテーションを実行します。

Pythonの場合、event[‘queryStringParameters’]でURLクエリパラメータを取得することがきます。
URLクエリパラメータで必要なパラメータを渡します。
operationパラメータで実行するGraphQLクエリを指定する形となります。

(参考) CloudFormationカスタムリソース

CloudFormationカスタムリソースを使用して、Aurora Serverlessの初期化処理を実行します。
Aurora ServerlessをCloudFormationカスタムリソースを使って初期化する方法については、以下のページをご確認ください。

以下にカスタムリソースとして実行するLambda関数を取り上げます。

Resources: Function2: Type: AWS::Lambda::Function Properties: Environment: Variables: DBCLUSTER_ARN: !Ref DBClusterArn DBNAME: !Ref DBName DBTABLE: !Ref DBTableName REGION: !Ref AWS::Region SECRET_ARN: !Ref SecretArn Code: ZipFile: | import boto3 import cfnresponse import json import os dbcluster_arn = os.environ['DBCLUSTER_ARN'] dbname = os.environ['DBNAME'] dbtable = os.environ['DBTABLE'] region = os.environ['REGION'] secret_arn = os.environ['SECRET_ARN'] sql1 = 'create table {table}(id varchar(200), type varchar(200), price float)'.format(table=dbtable) client = boto3.client('rds-data', region_name=region) schema = 'mysql' CREATE = 'Create' response_data = {} def lambda_handler(event, context): try: if event['RequestType'] == CREATE: response1 = client.execute_statement( database=dbname, resourceArn=dbcluster_arn, schema=schema, secretArn=secret_arn, sql=sql1 ) print(response1) cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data) except Exception as e: print(e) cfnresponse.send(event, context, cfnresponse.FAILED, response_data) FunctionName: !Sub "${Prefix}-function2" Handler: !Ref Handler Runtime: !Ref Runtime Role: !GetAtt FunctionRole2.Arn
Code language: YAML (yaml)

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

事前準備

以下の2つの準備を行います。

  • Lambda関数用のデプロイパッケージを用意してS3バケットにアップロードする
  • Lambdaレイヤー用のデプロイパッケージを用意してS3バケットにアップロードする

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

$ sudo pip3 install --pre gql[all] -t python zip -r layer.zip python
Code language: Bash (bash)

Lambdaレイヤーに関する詳細については、以下のページをご確認ください。

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

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

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • AppSync API:fa-061-GraphQLApi
  • Aurora ServerlessのID:fa-061-dbcluster
  • GraphQLクライアントLambda関数のFunction URL:https://dzmbqhvhyhkzm4pyufkzmketsy0gdoxt.lambda-url.ap-northeast-1.on.aws/

AWS Management ConsoleからAppSyncを確認します。
まずデータソースです。

Detail of AppSync Data Source.

データソースを見ると、Typeが「RELATIONAL_DATABASE」とあります。
正常にAurora Serverlessがデータソースとして設定されています。

次にスキーマ・リゾルバを確認します。

Detail of AppSync Schema.
Detail of AppSync Resolver 1.
Detail of AppSync Resolver 2.
Detail of AppSync Resolver 3.
Detail of AppSync Resolver 4.
Detail of AppSync Resolver 5.
Detail of AppSync Resolver 6.

CloudFormationテンプレートファイルで定義した通りに作成されています。

続いてAurora Serverlessを確認します。

Detail of Aurora Serverless.

こちらも正常に作成されています。

動作確認

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

createPet

まずデータを保存します。
URLクエリのoperationの値に「createPet」を指定し、typeとpriceに任意の値を指定します。
これで以下のGraphQLクエリを実行することになります。

mutation add($type: PetType!, $price: Float!) { createPet(input: { type: $type, price: $price }){ id type price } }
Code language: plaintext (plaintext)

以下が実行結果です。

Result of AppSync 1.

正常にデータが追加されました。

getPet

次にIDを指定してデータを取得します。
URLクエリのoperationの値に「getPet」を、idに先ほど確認したIDを指定します。
これで以下のGraphQLクエリを実行することになります。

query get($id: ID!) { getPet(id: $id){ id type price } }
Code language: plaintext (plaintext)

以下が実行結果です。

Result of AppSync 2.

先ほど保存されたデータが返ってきました。

updatePet

次にIDを指定してデータを更新します。
URLクエリのoperationの値に「updatePet」を、idに先ほど確認したIDを、typeとpriceに任意の値を設定します。
これで以下のGraphQLクエリを実行することになります。

mutation update($id: ID!, $type: PetType!, $price: Float!) { updatePet(input: { id: $id, type: $type, price: $price }){ id type price } }
Code language: plaintext (plaintext)

以下が実行結果です。

Result of AppSync 3.

typeの値が「fish」から「bird」、priceの値が「10.0」から「50.0」に更新されました。

listPets

listPetsを実行する前に、もう一度getPetを実行し、データを追加しておきます。

保存されているデータの一覧を取得します。
URLクエリのoperationの値に「listPets」を指定します。
これで以下のGraphQLクエリを実行することになります。

query allpets { listPets { id type price } }
Code language: plaintext (plaintext)

以下が実行結果です。

Result of AppSync 4.

保存されている2つのデータが返ってきました。

listPetsByPriceRange

priceの値で条件付けした上で、保存されたデータを取得します。
URLクエリのoperationの値に「listPetsByPriceRange」を、minとmaxに任意の値を設定します。
これで以下のGraphQLクエリを実行することになります。

query list($min: Float!, $max: Float!) { listPetsByPriceRange(min: $min, max: $max) { id type price } }
Code language: plaintext (plaintext)

以下が実行結果です。

Result of AppSync 5.

30<=price<=100の条件を満たす「bird」のデータが返ってきました。

deletePet

IDを指定してデータを削除します。
URLクエリのoperationの値に「deletePet」を、idに保存されているデータのIDを設定します。
これで以下のGraphQLクエリを実行することになります。

mutation delete($id: ID!) { deletePet(input: { id: $id }){ id type price } }
Code language: plaintext (plaintext)

以下が実行結果です。

Result of AppSync 6.

「bird」のデータが削除されました。

改めてlistPetsを実行します。

Result of AppSync 7.

「fish」のデータのみが返ってきました。
「bird」が削除されたことがわかります。

まとめ

AppSyncのデータソースにAurora Serverlessを設定する構成をご紹介しました。

タイトルとURLをコピーしました