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

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

チュートリアル: Aurora Serverless

AWS AppSync は、データ API で有効化されている Amazon Aurora Serverless クラスターに対して SQL コマンドを実行するためのデータソースを提供します。AppSync リゾルバーで GraphQL クエリ、ミューテーション、サブスクリプションを使用して、Data API に対して SQL ステートメントを実行できます。

クラスターを作成する

RDS データソースを AppSync に追加する前に、まず Aurora Serverless クラスターでデータ API を有効にし、AWS Secrets Manager を使用してシークレットを設定する必要があります。最初に AWS CLI を使用して Aurora Serverless クラスターを作成できます。

aws rds create-db-cluster --db-cluster-identifier http-endpoint-test --master-username USERNAME \ --master-user-password COMPLEX_PASSWORD --engine aurora --engine-mode serverless \ --region us-east-1

これにより、クラスターの ARN が返されます。

シークレットを AWS Secrets Manager コンソールから作成します。あるいは、以下のような入力ファイルで前の手順の USERNAME および COMPLEX_PASSWORD を使用して、CLI から作成します。

{ "username": "USERNAME", "password": "COMPLEX_PASSWORD" }

このシークレットを AWS CLI にパラメータとして渡します。

aws secretsmanager create-secret --name HttpRDSSecret --secret-string file://creds.json --region us-east-1

これにより、シークレットの ARN が返されます。

Aurora Serverless クラスターとシークレットの ARN をメモしておいてください。これらの ARN は後でデータソースを作成するときに AppSync コンソールで使用します。

Data API を有効にする

RDS のドキュメントの指示に従うことで、クラスターで Data API を有効にできます。Data API は AppSync データソースとして追加する前に有効にする必要があります。

データベースとテーブルを作成する

Data API を有効にしたら、AWS CLI の aws rds-data execute-statement コマンドで使用できるようになります。これにより、Aurora Serverless クラスターが正しく設定されていることを AppSync API への追加前に確認できます。まず、--sql のようなパラメータで TESTDB というデータベースを作成します。

aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:123456789000:cluster:http-endpoint-test" \ --schema "mysql" --secret-arn "arn:aws:secretsmanager:us-east-1:123456789000:secret:testHttp2-AmNvc1" \ --region us-east-1 --sql "create DATABASE TESTDB"

これがエラーなしで実行されたら、create table コマンドを使用してテーブルを追加します。

aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:123456789000:cluster:http-endpoint-test" \ --schema "mysql" --secret-arn "arn:aws:secretsmanager:us-east-1:123456789000:secret:testHttp2-AmNvc1" \ --region us-east-1 \ --sql "create table Pets(id varchar(200), type varchar(200), price float)" --database "TESTDB"

すべてが問題なく実行されたら、AppSync API のデータソースとしてクラスターを追加する手順に進むことができます。

GraphQl スキーマ

Aurora Serverless Data API がテーブルで起動されて実行中になったところで、次は GraphQL スキーマを作成し、ミューテーションとサブスクリプションを実行するためのリゾルバーをアタッチします。AWS AppSync コンソールで新しい API を作成し、[スキーマ] ページに移動して、以下のように入力します。

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 }

スキーマを保存し、[データソース] ページに移動して、新しいデータソースを作成します。データソースタイプとして [Relational database (リレーショナルデータベース)] を選択し、データソース名としてわかりやすい名前を入力します。前回の手順で作成したデータベースの名前と、そのデータベースを作成したクラスターのクラスター ARN を使用します。[Role (ロール)] では、AppSync により新しいロールを作成するか、以下のようなポリシーによりロールを作成できます。

{ "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": [ "arn:aws:rds:us-east-1:123456789012:cluster:mydbcluster", "arn:aws:rds:us-east-1:123456789012:cluster:mydbcluster:*" ] }, { "Effect": "Allow", "Action": [ "secretsmanager:GetSecretValue" ], "Resource": [ "arn:aws:secretsmanager:us-east-1:123456789012:secret:mysecret", "arn:aws:secretsmanager:us-east-1:123456789012:secret:mysecret:*" ] } ] }

このポリシーには、ロールにアクセス許可を付与する 2 つのステートメントがあります。リソースとして、最初のものは Aurora Serverless クラスターであり、2 番目のものは AWS Secrets Manager ARN です。作成をクリックする前に AppSync データソース構成内の両方の ARN を指定する必要があります。

リゾルバーの設定

有効な GraphQL スキーマと RDS データソースを用意できたところで、スキーマでリゾルバーを GraphQL フィールドにアタッチできます。この API は以下の機能を提供します。

  1. Mutation.createPet フィールドでペットを作成する

  2. Mutation.updatePet フィールドでペットを更新する

  3. Mutation.deletePet フィールドでペットを削除する

  4. Query.getPet フィールドで 1 つのペットを取得する

  5. Query.listPets フィールドですべてのペットを一覧表示する

  6. Query.listPetsByPriceRange フィールドでペットを価格帯別に一覧表示する

Mutation.createPet

右側のAWS AppSync コンソールのスキーマエディタで、createPet(input: CreatePetInput!): Pet の右側にある [Attach Resolver (リゾルバーをアタッチ)] を選択します。RDS データソースを選択します。[request mapping template (リクエストマッピングテンプレート)] セクションで、以下のテンプレートを追加します。

#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'" ] }

SQL ステートメントは、statements 配列内での順序に基づいて実行されます。結果はその同じ順序で返されます。これはミューテーションなので、挿入の後に選択ステートメントを実行して、GraphQL レスポンスマッピングテンプレートに入力するための、コミットされた値を取得します。

[response mapping template (レスポンスマッピングテンプレート)] セクションで、以下のテンプレートを追加します。

$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])

ステートメントには 2 つの SQL クエリがあるため、データベースから返される行列の 2 番目の結果を $utils.rds.toJsonString($ctx.result))[1][0]) で指定する必要があります。

Mutation.updatePet

右側のAWS AppSync コンソールのスキーマエディタで、updatePet(input: UpdatePetInput!): Pet の右側にある [Attach Resolver (リゾルバーをアタッチ)] を選択します。RDS データソースを選択します。[request mapping template (リクエストマッピングテンプレート)] セクションで、以下のテンプレートを追加します。

{ "version": "2018-05-29", "statements": [ $util.toJson("update Pets set type='$ctx.args.input.type', price=$ctx.args.input.price WHERE id='$ctx.args.input.id'"), $util.toJson("select * from Pets WHERE id = '$ctx.args.input.id'") ] }

[response mapping template (レスポンスマッピングテンプレート)] セクションで、以下のテンプレートを追加します。

$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])

Mutation.deletePet

右側のAWS AppSync コンソールのスキーマエディタで、deletePet(input: DeletePetInput!): Pet の右側にある [Attach Resolver (リゾルバーをアタッチ)] を選択します。RDS データソースを選択します。[request mapping template (リクエストマッピングテンプレート)] セクションで、以下のテンプレートを追加します。

{ "version": "2018-05-29", "statements": [ $util.toJson("select * from Pets WHERE id='$ctx.args.input.id'"), $util.toJson("delete from Pets WHERE id='$ctx.args.input.id'") ] }

[response mapping template (レスポンスマッピングテンプレート)] セクションで、以下のテンプレートを追加します。

$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])

Query.getPet

スキーマに対してミューテーションが作成されたところで、次は 3 つのクエリを接続して、個々の項目、リストを取得し、SQL フィルタを適用する方法を紹介します。右側のAWS AppSync コンソールのスキーマエディタで、getPet(id: ID!): Pet の右側にある [Attach Resolver (リゾルバーをアタッチ)] を選択します。RDS データソースを選択します。[request mapping template (リクエストマッピングテンプレート)] セクションで、以下のテンプレートを追加します。

{ "version": "2018-05-29", "statements": [ $util.toJson("select * from Pets WHERE id='$ctx.args.id'") ] }

[response mapping template (レスポンスマッピングテンプレート)] セクションで、以下のテンプレートを追加します。

$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])

Query.listPets

右側のAWS AppSync コンソールのスキーマエディタで、getPet(id: ID!): Pet の右側にある [Attach Resolver (リゾルバーをアタッチ)] を選択します。RDS データソースを選択します。[request mapping template (リクエストマッピングテンプレート)] セクションで、以下のテンプレートを追加します。

{ "version": "2018-05-29", "statements": [ "select * from Pets" ] }

[response mapping template (レスポンスマッピングテンプレート)] セクションで、以下のテンプレートを追加します。

$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])

Query.listPetsByPriceRange

右側のAWS AppSync コンソールのスキーマエディタで、getPet(id: ID!): Pet の右側にある [Attach Resolver (リゾルバーをアタッチ)] を選択します。RDS データソースを選択します。[request mapping template (リクエストマッピングテンプレート)] セクションで、以下のテンプレートを追加します。

{ "version": "2018-05-29", "statements": [ "select * from Pets where price > :MIN and price < :MAX" ], "variableMap": { ":MAX": $util.toJson($ctx.args.max), ":MIN": $util.toJson($ctx.args.min) } }

[response mapping template (レスポンスマッピングテンプレート)] セクションで、以下のテンプレートを追加します。

$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])

ミューテーションを実行する

すべてのリゾルバーを SQL ステートメントで設定し、GraphQL API を Serverless Aurora Data API に接続したところで、ミューテーションとクエリの実行を開始できます。AWS AppSync コンソールで、[クエリ] タブを選択し、以下のように入力してペットを作成します。

mutation add { createPet(input : { type:fish, price:10.0 }){ id type price } }

レスポンスには、idtypeprice が含まれています。

{ "data": { "createPet": { "id": "c6fedbbe-57ad-4da3-860a-ffe8d039882a", "type": "fish", "price": "10.0" } } }

この項目は updatePet ミューテーションを実行することで変更できます。

mutation update { updatePet(input : { id:"c6fedbbe-57ad-4da3-860a-ffe8d039882a", type:bird, price:50.0 }){ id type price } }

事前のcreatePetオペレーションから返されたidを使用したことに注意してください。リゾルバーが $util.autoId() を利用したため、これがレコードに固有の値になります。同様の方法でレコードを削除できます。

mutation { deletePet(input : {id:ID_PLACEHOLDER}){ id type price } }

最初のミューテーションで price に異なる値を使用してレコードをいくつか作成したら、クエリをいくつか実行します。

クエリを実行する

引き続きコンソールの [クエリ] タブで、以下のステートメントを使用して、作成したすべてのレコードを一覧表示します。

query allpets { listPets { id type price } }

それでもいいですが、SQLを活用しましょう、WHEREは持っていた述語ので次の GraphQL クエリを使用してQuery.listPetsByPriceRangeのためのマッピングテンプレートに where price > :MIN and price < :MAX があったことを予測します。

query { listPetsByPriceRange(min:1, max:11) { id type price } }

price が 1 ドル以上、10 ドル未満のレコードのみが表示されます。最後に、以下のようにクエリを実行して個々のレコードを取得できます。

query { getPet(id:ID_PLACEHOLDER){ id type price } }

入力サニタイズ

開発者には GraphQL オペレーションの引数をサニタイズすることを強くお勧めします。そのための 1 つの方法は、Data API に対して SQL ステートメントを実行する前に、リクエストマッピングテンプレートに入力固有の検証手順を提供することです。listPetsByPriceRange 例のリクエストマッピングテンプレートを変更する方法を見てみましょう。ユーザー入力だけに頼るのではなく、以下のことが可能です。

#set($validMaxPrice = $util.matches("\d{1,3}[,\\.]?(\\d{1,2})?",$ctx.args.maxPrice)) #set($validMinPrice = $util.matches("\d{1,3}[,\\.]?(\\d{1,2})?",$ctx.args.minPrice)) #if (!$validMaxPrice || !$validMinPrice) $util.error("Provided price input is not valid.") #end { "version": "2018-05-29", "statements": [ "select * from Pets where price > :MIN and price < :MAX" ], "variableMap": { ":MAX": $util.toJson($ctx.args.maxPrice), ":MIN": $util.toJson($ctx.args.minPrice) } }

Data API に対してリゾルバーを実行するときに不正な入力から保護するもう 1 つの方法は、プリペアードステートメントをストアドプロシージャおよびパラメータ化された入力と共に使用することです。たとえば、listPets のリゾルバーで、select をプリペアードステートメントとして実行する以下のプロシージャを定義します。

CREATE PROCEDURE listPets (IN type_param VARCHAR(200)) BEGIN PREPARE stmt FROM 'SELECT * FROM Pets where type=?'; SET @type = type_param; EXECUTE stmt USING @type; DEALLOCATE PREPARE stmt; END

これは、以下の execute sql コマンドを使用して、Aurora Serverless インスタンスに作成できます。

aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:xxxxxxxxxxxx:cluster:http-endpoint-test" \ --schema "mysql" --secret-arn "arn:aws:secretsmanager:us-east-1:xxxxxxxxxxxx:secret:httpendpoint-xxxxxx" \ --region us-east-1 --database "DB_NAME" \ --sql "CREATE PROCEDURE listPets (IN type_param VARCHAR(200)) BEGIN PREPARE stmt FROM 'SELECT * FROM Pets where type=?'; SET @type = type_param; EXECUTE stmt USING @type; DEALLOCATE PREPARE stmt; END"

その結果、listPets のリゾルバーコードは、ストアドプロシージャを呼び出すシンプルなものになりました。少なくとも、文字列入力では一重引用符をエスケープする必要があります。

#set ($validType = $util.isString($ctx.args.type) && !$util.isNullOrBlank($ctx.args.type)) #if (!$validType) $util.error("Input for 'type' is not valid.", "ValidationError") #end { "version": "2018-05-29", "statements": [ "CALL listPets(:type)" ] "variableMap": { ":type": $util.toJson($ctx.args.type.replace("'", "''")) } }

文字列のエスケープ

一重引用符は、'some string value' のように、SQL ステートメントの文字列リテラルの開始と終了を表します。1 つ以上の一重引用符 (') を含む文字列値を文字列内で使用するには、それぞれを 2 つの一重引用符 ('') に置き換える必要があります。たとえば、入力文字列が Nadia's dog の場合、SQL ステートメントでは次のようにエスケープします。

update Pets set type='Nadia''s dog' WHERE id='1'