翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。
での AWS Lambda リゾルバーの使用 AWS AppSync
AWS Lambda with AWS AppSync を使用して、GraphQL フィールドを解決できます。例えば、GraphQL クエリが Amazon Relational Database Service (Amazon RDS) インスタンスを呼び出し、GraphQL のミューテーションを Amazon Kinesis ストリームに書き込むことができます。このセクションでは、GraphQL フィールド処理の呼び出しに応じてビジネスロジックを実行する Lambda 関数を記述する方法について説明します。
Lambda 関数を作成する
以下の例は、Node.js
(runtime: Node.js 18.x) に記述された、ブログ投稿アプリケーションの一部としてブログ投稿に関するさまざまな処理を実行する Lambda 関数を示しています。コードは、.mis 拡張子の付いたファイル名で保存する必要があることに注意してください。
export const handler = async (event) => { console.log('Received event {}', JSON.stringify(event, 3)) const posts = { 1: { id: '1', title: 'First book', author: 'Author1', url: 'https://amazon.com/', content: 'SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1', ups: '100', downs: '10', }, 2: { id: '2', title: 'Second book', author: 'Author2', url: 'https://amazon.com', content: 'SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT', ups: '100', downs: '10', }, 3: { id: '3', title: 'Third book', author: 'Author3', url: null, content: null, ups: null, downs: null }, 4: { id: '4', title: 'Fourth book', author: 'Author4', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4', ups: '1000', downs: '0', }, 5: { id: '5', title: 'Fifth book', author: 'Author5', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT', ups: '50', downs: '0', }, } const relatedPosts = { 1: [posts['4']], 2: [posts['3'], posts['5']], 3: [posts['2'], posts['1']], 4: [posts['2'], posts['1']], 5: [], } console.log('Got an Invoke Request.') let result switch (event.field) { case 'getPost': return posts[event.arguments.id] case 'allPosts': return Object.values(posts) case 'addPost': // return the arguments back return event.arguments case 'addPostErrorWithData': result = posts[event.arguments.id] // attached additional error information to the post result.errorMessage = 'Error with the mutation, data has changed' result.errorType = 'MUTATION_ERROR' return result case 'relatedPosts': return relatedPosts[event.source.id] default: throw new Error('Unknown field, unable to resolve ' + event.field) } }
この Lambda 関数は、ID による投稿の取得、投稿の追加、投稿のリストの取得、および指定した投稿に関連する投稿の取得を処理します。
注記
event.field
の switch
ステートメントにより、Lambda 関数は現在解決しているフィールドを確認することができます。
AWS マネジメントコンソールを使用して、この Lambda 関数を作成します。
Lambda のデータソースを設定する
Lambda 関数を作成した後、 AWS AppSync コンソールで GraphQL API に移動し、[データソース] タブを選択します。
[データソースの作成] を選択し、[データソース名]としてわかりやすい名前 (Lambda
など) を入力してから、[データソースタイプ] に対して [AWS Lambda 関数]を選択します。[リージョン] で、関数と同じリージョンを選択します。[関数 ARN] で、Lambda 関数の Amazon リソースネーム (ARN)を選択します。
Lambda 関数を選択したら、新しい AWS Identity and Access Management (IAM) ロール ( AWS AppSync が適切なアクセス許可を割り当てるロール) を作成するか、次のインラインポリシーを持つ既存のロールを選択できます。
また、次のように IAM ロールの AWS AppSync との信頼関係を設定する必要があります。
GraphQL スキーマを作成する
データソースが Lambda 関数に接続されたので、GraphQL スキーマを作成します。
AWS AppSync コンソールのスキーマエディタから、スキーマが次のスキーマと一致していることを確認します。
schema { query: Query mutation: Mutation } type Query { getPost(id:ID!): Post allPosts: [Post] } type Mutation { addPost(id: ID!, author: String!, title: String, content: String, url: String): Post! } type Post { id: ID! author: String! title: String content: String url: String ups: Int downs: Int relatedPosts: [Post] }
リゾルバーを設定する
Lambda のデータソースと有効な GraphQL スキーマが登録されたので、リゾルバーを使用して GraphQL フィールドを Lambda のデータソースに接続することができます。
AWS AppSync JavaScript (APPSYNC_JS
) ランタイムを使用して Lambda 関数を操作するリゾルバーを作成します。JavaScript を使用した AWS AppSync リゾルバーと関数の記述の詳細については、「リゾルバーと関数の JavaScript ランタイム機能」を参照してください。
Lambda のマッピングテンプレートの詳細については、「 Lambda 向け JavaScript のリゾルバー関数リファレンス」を参照してください。
このステップでは、getPost(id:ID!):
Post
、allPosts: [Post]
、addPost(id: ID!, author: String!, title: String,
content: String, url: String): Post!
、および Post.relatedPosts: [Post]
の各フィールドで Lambda 関数にリゾルバーをアタッチします。 AWS AppSync コンソールのスキーマエディタのリゾルバーペインで、getPost(id:ID!): Post
フィールドの横にあるアタッチを選択します。Lambda データソースを選択します。そして、次のコードを入力します。
import { util } from '@aws-appsync/utils'; export function request(ctx) { const {source, args} = ctx return { operation: 'Invoke', payload: { field: ctx.info.fieldName, arguments: args, source }, }; } export function response(ctx) { return ctx.result; }
このリゾルバーコードは、ソースオブジェクトを呼び出すときに、フィールド名、引数のリスト、ソースオブジェクトに関するコンテキストを Lambda 関数に渡します。[保存] を選択します。
最初のリゾルバーが正常に追加されました。残りのフィールドについてこの操作を繰り返します。
GraphQL API をテストする
これで Lambda 関数が GraphQL リゾルバーに接続されたので、コンソールまたはクライアントアプリケーションを使用してミューテーションまたはクエリが実行できます。
AWS AppSync コンソールの左側でクエリを選択し、次のコードを貼り付けます。
addPost ミューテーション
mutation AddPost { addPost( id: 6 author: "Author6" title: "Sixth book" url: "https://www.amazon.com/" content: "This is the book is a tutorial for using GraphQL with AWS AppSync." ) { id author title content url ups downs } }
getPost クエリ
query GetPost { getPost(id: "2") { id author title content url ups downs } }
allPosts クエリ
query AllPosts { allPosts { id author title content url ups downs relatedPosts { id title } } }
エラーを返す
指定したフィールドの解決でエラーが発生する場合があります。 AWS AppSync を使用すると、次のソースからエラーを生成できます。
-
リゾルバーレスポンスハンドラー
-
Lambda function
リゾルバーレスポンスハンドラーから
意図的なエラーを発生させるには、util.error
ユーティリティメソッドを使用できます。これには引数として、errorMessage
、errorType
、および必要に応じて data
の各値を使用します。data
は、エラーの発生時にクライアントに追加のデータを返す場合に利用できます。data
オブジェクトは GraphQL の最終レスポンスで errors
に追加されます。
次の例は、Post.relatedPosts: [Post]
リゾルバーのレスポンスハンドラーでこれを使用する方法を示しています。
// the Post.relatedPosts response handler export function response(ctx) { util.error("Failed to fetch relatedPosts", "LambdaFailure", ctx.result) return ctx.result; }
これにより、以下のような GraphQL レスポンスが生成されます。
{ "data": { "allPosts": [ { "id": "2", "title": "Second book", "relatedPosts": null }, ... ] }, "errors": [ { "path": [ "allPosts", 0, "relatedPosts" ], "errorType": "LambdaFailure", "locations": [ { "line": 5, "column": 5 } ], "message": "Failed to fetch relatedPosts", "data": [ { "id": "2", "title": "Second book" }, { "id": "1", "title": "First book" } ] } ] }
この場合、エラーおよび errorMessage
、errorType
、data
が data.errors[0]
オブジェクトに存在するため、allPosts[0].relatedPosts
は null になります。
Lambda 関数から
AWS AppSync は、Lambda 関数がスローするエラーも理解します。Lambda プログラミングモデルを使用し、処理されるエラーを発生させることができます。Lambda 関数からエラーがスローされた場合、 AWS AppSync は現在のフィールドの解決に失敗します。Lambda から返されたエラーメッセージのみがレスポンスに設定されます。また現在のところ、Lambda 関数からエラーを発生させて、クライアントにエラー関連以外のデータを渡すことはできません。
注記
注意 : Lambda 関数が処理されないエラーを発生させた場合、 AWS AppSync は によって設定されたエラーメッセージを使用します。
以下の Lambda 関数はエラーを発生させます。
export const handler = async (event) => { console.log('Received event {}', JSON.stringify(event, 3)) throw new Error('I always fail.') }
エラーはレスポンスハンドラーで受信されます。util.appendError
でレスポンスにエラーを追加することで、GraphQL レスポンスで返すことができます。これを行うには、 AWS AppSync 関数レスポンスハンドラーを次のように変更します。
// the lambdaInvoke response handler export function response(ctx) { const { error, result } = ctx; if (error) { util.appendError(error.message, error.type, result); } return result; }
これにより、以下のような GraphQL レスポンスが返されます。
{ "data": { "allPosts": null }, "errors": [ { "path": [ "allPosts" ], "data": null, "errorType": "Lambda:Unhandled", "errorInfo": null, "locations": [ { "line": 2, "column": 3, "sourceName": null } ], "message": "I fail. always" } ] }
高度なユースケース: バッチ処理
この例の Lambda 関数には、指定した投稿に関連する投稿のリストを返す relatedPosts
フィールドが含まれています。この例のクエリでは、Lambda 関数からの allPosts
フィールドの呼び出しにより 5 件の投稿が返されます。返された各投稿に対して relatedPosts
の解決も指定しているので、relatedPosts
フィールドの処理が続けて 5 回呼び出されます。
query { allPosts { // 1 Lambda invocation - yields 5 Posts id author title content url ups downs relatedPosts { // 5 Lambda invocations - each yields 5 posts id title } } }
この例では大して意味がないように思われますが、こうした余分な取得処理が積み重なることで、アプリケーションに急速に害をなす可能性があります。
同じクエリで返された関連する Posts
について、再度 relatedPosts
を取得すると、呼び出しの数は大幅に増加します。
query { allPosts { // 1 Lambda invocation - yields 5 Posts id author title content url ups downs relatedPosts { // 5 Lambda invocations - each yield 5 posts = 5 x 5 Posts id title relatedPosts { // 5 x 5 Lambda invocations - each yield 5 posts = 25 x 5 Posts id title author } } } }
この比較的単純なクエリでは、 AWS AppSync は Lambda 関数を 1 + 5 + 25 = 31 回呼び出します。
これはよくある課題であり、N+1 問題と呼ばれます (この例では、N = 5)。これによりアプリケーションのレイテンシーとコストが増大します。
この問題を解決する 1 つの方法は、同様なフィールドのリゾルバーリクエストを同時にバッチ処理することです。この例では、Lambda 関数は指定された 1 つの投稿に関連する投稿のリストを解決するのではなく、指定された一連の投稿に関連する投稿のリストを解決できます。
これを実証するために、relatedPosts
に対するバッチ処理を行うようにリゾルバーを更新してみましょう。
import { util } from '@aws-appsync/utils'; export function request(ctx) { const {source, args} = ctx return { operation: ctx.info.fieldName === 'relatedPosts' ? 'BatchInvoke' : 'Invoke', payload: { field: ctx.info.fieldName, arguments: args, source }, }; } export function response(ctx) { const { error, result } = ctx; if (error) { util.appendError(error.message, error.type, result); } return result; }
このコードでは、解決対象のfieldName
が relatedPosts
である場合に、オペレーションが Invoke
から BatchInvoke
に変更されます。次に、[バッチ処理の設定] セクションで関数のバッチ処理を有効にします。最大バッチサイズを 5
に設定します。[保存] を選択します。
この変更により、Lambda 関数は relatedPosts
の解決時に入力として以下を受け取ります。
[ { "field": "relatedPosts", "source": { "id": 1 } }, { "field": "relatedPosts", "source": { "id": 2 } }, ... ]
リクエストで BatchInvoke
が指定されている場合、Lambda 関数はリクエストのリストを受け取り、結果のリストを返します。
具体的には、 AWS AppSync がそれに応じて結果を一致できるように、結果のリストがリクエストペイロードエントリのサイズと順序と一致する必要があります。
このバッチ処理の例では、Lambda 関数は次のように一連の結果を返します。
[ [{"id":"2","title":"Second book"}, {"id":"3","title":"Third book"}], // relatedPosts for id=1 [{"id":"3","title":"Third book"}] // relatedPosts for id=2 ]
Lambda コードを更新して、relatedPosts
に対するバッチ処理を行うことができます。
export const handler = async (event) => { console.log('Received event {}', JSON.stringify(event, 3)) //throw new Error('I fail. always') const posts = { 1: { id: '1', title: 'First book', author: 'Author1', url: 'https://amazon.com/', content: 'SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1', ups: '100', downs: '10', }, 2: { id: '2', title: 'Second book', author: 'Author2', url: 'https://amazon.com', content: 'SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT', ups: '100', downs: '10', }, 3: { id: '3', title: 'Third book', author: 'Author3', url: null, content: null, ups: null, downs: null }, 4: { id: '4', title: 'Fourth book', author: 'Author4', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4', ups: '1000', downs: '0', }, 5: { id: '5', title: 'Fifth book', author: 'Author5', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT', ups: '50', downs: '0', }, } const relatedPosts = { 1: [posts['4']], 2: [posts['3'], posts['5']], 3: [posts['2'], posts['1']], 4: [posts['2'], posts['1']], 5: [], } if (!event.field && event.length){ console.log(`Got a BatchInvoke Request. The payload has ${event.length} items to resolve.`); return event.map(e => relatedPosts[e.source.id]) } console.log('Got an Invoke Request.') let result switch (event.field) { case 'getPost': return posts[event.arguments.id] case 'allPosts': return Object.values(posts) case 'addPost': // return the arguments back return event.arguments case 'addPostErrorWithData': result = posts[event.arguments.id] // attached additional error information to the post result.errorMessage = 'Error with the mutation, data has changed' result.errorType = 'MUTATION_ERROR' return result case 'relatedPosts': return relatedPosts[event.source.id] default: throw new Error('Unknown field, unable to resolve ' + event.field) } }
個々のエラーを返す
Lambda 関数から単一のエラーを返せることや、レスポンスハンドラーから 1 つのエラーを生成できることは以前の例で説明しました。バッチ処理を呼び出した場合、Lambda 関数からエラーが発生すると、バッチ処理全体が失敗としてフラグ付けされます。これは、データストアとの接続が切れた場合など、回復不可能なエラーが発生するシナリオでは問題ないかもしれません。ここでは、バッチ処理の一部が成功し、他が失敗した場合、エラーと有効なデータの両方を返すことができます。 AWS AppSync では、バッチ処理のレスポンスがバッチの元のサイズと一致する要素のリストとなるように要求されるため、エラーから有効なデータが識別できるようなデータ構造を定義する必要があります。
例えば、関連する一連の投稿が返されることを Lambda 関数が期待している場合には、data、errorMessage、errorType の各フィールドを任意に含む Response
オブジェクトのリストを返します。errorMessage フィールドが存在する場合は、エラーが発生したことを意味します。
次のコードは Lambda 関数の更新方法を示しています。
export const handler = async (event) => { console.log('Received event {}', JSON.stringify(event, 3)) // throw new Error('I fail. always') const posts = { 1: { id: '1', title: 'First book', author: 'Author1', url: 'https://amazon.com/', content: 'SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1', ups: '100', downs: '10', }, 2: { id: '2', title: 'Second book', author: 'Author2', url: 'https://amazon.com', content: 'SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT', ups: '100', downs: '10', }, 3: { id: '3', title: 'Third book', author: 'Author3', url: null, content: null, ups: null, downs: null }, 4: { id: '4', title: 'Fourth book', author: 'Author4', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4', ups: '1000', downs: '0', }, 5: { id: '5', title: 'Fifth book', author: 'Author5', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT', ups: '50', downs: '0', }, } const relatedPosts = { 1: [posts['4']], 2: [posts['3'], posts['5']], 3: [posts['2'], posts['1']], 4: [posts['2'], posts['1']], 5: [], } if (!event.field && event.length){ console.log(`Got a BatchInvoke Request. The payload has ${event.length} items to resolve.`); return event.map(e => { // return an error for post 2 if (e.source.id === '2') { return { 'data': null, 'errorMessage': 'Error Happened', 'errorType': 'ERROR' } } return {data: relatedPosts[e.source.id]} }) } console.log('Got an Invoke Request.') let result switch (event.field) { case 'getPost': return posts[event.arguments.id] case 'allPosts': return Object.values(posts) case 'addPost': // return the arguments back return event.arguments case 'addPostErrorWithData': result = posts[event.arguments.id] // attached additional error information to the post result.errorMessage = 'Error with the mutation, data has changed' result.errorType = 'MUTATION_ERROR' return result case 'relatedPosts': return relatedPosts[event.source.id] default: throw new Error('Unknown field, unable to resolve ' + event.field) } }
relatedPosts
リゾルバーコードを更新します。
import { util } from '@aws-appsync/utils'; export function request(ctx) { const {source, args} = ctx return { operation: ctx.info.fieldName === 'relatedPosts' ? 'BatchInvoke' : 'Invoke', payload: { field: ctx.info.fieldName, arguments: args, source }, }; } export function response(ctx) { const { error, result } = ctx; if (error) { util.appendError(error.message, error.type, result); } else if (result.errorMessage) { util.appendError(result.errorMessage, result.errorType, result.data) } else if (ctx.info.fieldName === 'relatedPosts') { return result.data } else { return result } }
レスポンスハンドラーは、Invoke
オペレーションで Lambda 関数から返されるエラーをチェックし、BatchInvoke
オペレーションの個々の項目について返されたエラーをチェックしてから、最後に fieldName
をチェックするようになりました。relatedPosts
に対して、関数は result.data
を返します。その他のすべてのフィールドでは、関数は result
を返すだけです。例えば、以下のクエリを参照してください。
query AllPosts { allPosts { id title content url ups downs relatedPosts { id } author } }
このクエリでは、次のような GraphQL レスポンスが返されます。
{ "data": { "allPosts": [ { "id": "1", "relatedPosts": [ { "id": "4" } ] }, { "id": "2", "relatedPosts": null }, { "id": "3", "relatedPosts": [ { "id": "2" }, { "id": "1" } ] }, { "id": "4", "relatedPosts": [ { "id": "2" }, { "id": "1" } ] }, { "id": "5", "relatedPosts": [] } ] }, "errors": [ { "path": [ "allPosts", 1, "relatedPosts" ], "data": null, "errorType": "ERROR", "errorInfo": null, "locations": [ { "line": 4, "column": 5, "sourceName": null } ], "message": "Error Happened" } ] }
最大バッチサイズの設定
リゾルバーで最大バッチサイズを設定するには、 AWS Command Line Interface () で次のコマンドを使用しますAWS CLI。
$ aws appsync create-resolver --api-id <api-id> --type-name Query --field-name relatedPosts \ --code "<code-goes-here>" \ --runtime name=APPSYNC_JS,runtimeVersion=1.0.0 \ --data-source-name "<lambda-datasource>" \ --max-batch-size X
注記
リクエストマッピングテンプレートを提供するときは、バッチ処理を使用する BatchInvoke
オペレーションを使用する必要があります。