Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.
Utilisation de AWS Lambda résolveurs dans AWS AppSync
Vous pouvez utiliser AWS Lambda with AWS AppSync pour résoudre n'importe quel champ GraphQL. Par exemple, une requête GraphQL peut envoyer un appel à une instance Amazon Relational Database Service (AmazonRDS), et une mutation GraphQL peut écrire dans un flux Amazon Kinesis. Dans cette section, nous allons vous montrer comment écrire une fonction Lambda qui exécute une logique métier basée sur l'invocation d'une opération de terrain GraphQL.
Création d’une fonction Lambda
L'exemple suivant montre une fonction Lambda écrite en Node.js
(runtime : Node.js 18.x) qui effectue différentes opérations sur des articles de blog dans le cadre d'une application de publication de blog. Notez que le code doit être enregistré dans un nom de fichier portant l'extension .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) } }
Cette fonction Lambda récupère une publication par identifiant, ajoute une publication, récupère une liste de publications et récupère les publications associées à une publication donnée.
Note
La fonction Lambda utilise l'switch
instruction on event.field
pour déterminer le champ en cours de résolution.
Créez cette fonction Lambda à l'aide de la console de AWS gestion.
Configuration d'une source de données pour Lambda
Après avoir créé la fonction Lambda, accédez à votre GraphQL API dans la AWS AppSync console, puis choisissez l'onglet Sources de données.
Choisissez Créer une source de données, entrez un nom de source de données convivial (par exemple,Lambda
), puis pour Type de source de données, choisissez AWS Lambda fonction. Pour Région, choisissez la même région que votre fonction. Pour Function ARN, choisissez le nom de ressource Amazon (ARN) de votre fonction Lambda.
Après avoir choisi votre fonction Lambda, vous pouvez soit créer un nouveau rôle AWS Identity and Access Management (IAM) (pour lequel les autorisations appropriées AWS AppSync sont attribuées), soit choisir un rôle existant doté de la politique intégrée suivante :
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "lambda:InvokeFunction" ], "Resource": "arn:aws:lambda:REGION:ACCOUNTNUMBER:function/LAMBDA_FUNCTION" } ] }
Vous devez également établir une relation de confiance avec AWS AppSync le IAM rôle comme suit :
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "appsync.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
Création d'un schéma GraphQL
Maintenant que la source de données est connectée à votre fonction Lambda, créez un schéma GraphQL.
Dans l'éditeur de schéma de la AWS AppSync console, assurez-vous que votre schéma correspond au schéma suivant :
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] }
Configuration des résolveurs
Maintenant que vous avez enregistré une source de données Lambda et un schéma GraphQL valide, vous pouvez connecter vos champs GraphQL à votre source de données Lambda à l'aide de résolveurs.
Vous allez créer un résolveur qui utilise le runtime AWS AppSync JavaScript (APPSYNC_JS
) et qui interagit avec vos fonctions Lambda. Pour en savoir plus sur l'écriture de AWS AppSync résolveurs et de fonctions avec JavaScript, consultez les fonctionnalités JavaScript d'exécution pour les résolveurs et les fonctions.
Pour plus d'informations sur les modèles de mappage Lambda, consultez la référence des fonctions de JavaScript résolution pour Lambda.
Au cours de cette étape, vous associez un résolveur à la fonction Lambda pour les champs suivants getPost(id:ID!):
Post
:allPosts: [Post]
,addPost(id: ID!, author: String!, title: String,
content: String, url: String): Post!
, et. Post.relatedPosts: [Post]
Dans l'éditeur de schéma de la AWS AppSync console, dans le volet Résolveurs, choisissez Attacher à côté du getPost(id:ID!): Post
champ. Choisissez votre source de données Lambda. Entrez ensuite le code suivant :
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; }
Ce code de résolution transmet le nom du champ, la liste des arguments et le contexte de l'objet source à la fonction Lambda lorsqu'elle l'invoque. Choisissez Save (Enregistrer).
Vous avez joint votre première résolveur avec succès. Répétez cette opération pour les autres champs.
Testez votre GraphQL API
Maintenant que votre fonction Lambda est connectée aux résolveurs GraphQL, vous pouvez exécuter des mutations et des requêtes à l'aide de la console ou d'une application cliente.
Sur le côté gauche de la AWS AppSync console, choisissez Requêtes, puis collez le code suivant :
addPost Mutation
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 Requête
query GetPost { getPost(id: "2") { id author title content url ups downs } }
allPosts Requête
query AllPosts { allPosts { id author title content url ups downs relatedPosts { id title } } }
Erreurs de renvoi
Toute résolution de champ donnée peut entraîner une erreur. Avec AWS AppSync, vous pouvez générer des erreurs provenant des sources suivantes :
-
Gestionnaire de réponses Resolver
-
Fonction Lambda
À partir du gestionnaire de réponses du résolveur
Pour signaler des erreurs intentionnelles, vous pouvez utiliser la méthode util.error
utilitaire. Il prend un argument an errorMessage
errorType
, un et une data
valeur optionnelle. L'objet data
est utile pour renvoyer des données supplémentaires au client, lorsqu'une erreur a été déclenchée. L'objet data
sera ajouté à errors
dans la réponse GraphQL finale.
L'exemple suivant montre comment l'utiliser dans le gestionnaire de réponses du Post.relatedPosts: [Post]
résolveur.
// the Post.relatedPosts response handler export function response(ctx) { util.error("Failed to fetch relatedPosts", "LambdaFailure", ctx.result) return ctx.result; }
Cela génère une réponse GraphQL similaire à ce qui suit :
{ "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" } ] } ] }
où allPosts[0].relatedPosts
est null du fait de l'erreur et errorMessage
, errorType
et data
sont présents dans l'objet data.errors[0]
.
À partir de la fonction Lambda
AWS AppSync comprend également les erreurs générées par la fonction Lambda. Le modèle de programmation Lambda vous permet de signaler les erreurs gérées. Si la fonction Lambda génère une erreur, elle AWS AppSync ne parvient pas à résoudre le champ actuel. Seul le message d'erreur renvoyé par Lambda est défini dans la réponse. Actuellement, vous ne pouvez pas renvoyer de données superflues au client en déclenchant une erreur à partir de la fonction Lambda.
Note
Si votre fonction Lambda génère une erreur non gérée, AWS AppSync utilise le message d'erreur défini par Lambda.
La fonction Lambda suivante génère une erreur :
export const handler = async (event) => { console.log('Received event {}', JSON.stringify(event, 3)) throw new Error('I always fail.') }
L'erreur est reçue dans votre gestionnaire de réponses. Vous pouvez le renvoyer dans la réponse GraphQL en ajoutant l'erreur à la réponse avec. util.appendError
Pour ce faire, modifiez le gestionnaire de réponse de votre AWS AppSync fonction comme suit :
// the lambdaInvoke response handler export function response(ctx) { const { error, result } = ctx; if (error) { util.appendError(error.message, error.type, result); } return result; }
Cela renvoie une réponse GraphQL similaire à ce qui suit :
{ "data": { "allPosts": null }, "errors": [ { "path": [ "allPosts" ], "data": null, "errorType": "Lambda:Unhandled", "errorInfo": null, "locations": [ { "line": 2, "column": 3, "sourceName": null } ], "message": "I fail. always" } ] }
Cas d'utilisation avancé : traitement par lots
Dans cet exemple, la fonction Lambda possède un relatedPosts
champ qui renvoie une liste de publications connexes pour une publication donnée. Dans les exemples de requêtes, l'invocation du allPosts
champ par la fonction Lambda renvoie cinq messages. Comme nous avons précisé que nous voulions également résoudre le problème relatedPosts
pour chaque message renvoyé, l'opération de relatedPosts
terrain est invoquée cinq fois.
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 } } }
Bien que cela ne semble pas important dans cet exemple spécifique, ce surchargement aggravé peut rapidement saper l'application.
Si vous souhaitez à nouveau extraire relatedPosts
sur le Posts
associé renvoyé dans la même requête, le nombre d'appels augmenterait considérablement.
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 } } } }
Dans cette requête relativement simple, AWS AppSync invoquerait la fonction Lambda 1 + 5 + 25 = 31 fois.
Il s'agit d'un défi assez courant, souvent appelé « problème N+1 » (dans ce cas, N = 5) et qui peut entraîner une augmentation de la latence et des coûts de l'application.
L'une des approches possibles pour résoudre ce problème est de regrouper les demandes de résolveur de champ similaires. Dans cet exemple, au lieu de demander à la fonction Lambda de résoudre une liste de publications connexes pour une publication donnée, elle pourrait résoudre une liste de publications connexes pour un lot de publications donné.
Pour le démontrer, mettons à jour le résolveur pour relatedPosts
qu'il gère le traitement par lots.
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; }
Le code change désormais l'opération de Invoke
à BatchInvoke
celle en fieldName
cours de résolutionrelatedPosts
. Activez maintenant le traitement par lots sur la fonction dans la section Configurer le traitement par lots. Définissez la taille de lot maximale définie sur. 5
Choisissez Save (Enregistrer).
Avec cette modification, lors de la résolutionrelatedPosts
, la fonction Lambda reçoit les informations suivantes en entrée :
[ { "field": "relatedPosts", "source": { "id": 1 } }, { "field": "relatedPosts", "source": { "id": 2 } }, ... ]
Lorsqu'elle BatchInvoke
est spécifiée dans la demande, la fonction Lambda reçoit une liste de demandes et renvoie une liste de résultats.
Plus précisément, la liste des résultats doit correspondre à la taille et à l'ordre des entrées de charge utile de la demande afin de AWS AppSync pouvoir correspondre aux résultats en conséquence.
Dans cet exemple de traitement par lots, la fonction Lambda renvoie un lot de résultats comme suit :
[ [{"id":"2","title":"Second book"}, {"id":"3","title":"Third book"}], // relatedPosts for id=1 [{"id":"3","title":"Third book"}] // relatedPosts for id=2 ]
Vous pouvez mettre à jour votre code Lambda pour gérer le traitement par lots pour : 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) } }
Renvoi d'erreurs individuelles
Les exemples précédents montrent qu'il est possible de renvoyer une seule erreur depuis la fonction Lambda ou de générer une erreur depuis votre gestionnaire de réponses. Pour les appels par lots, le fait de générer une erreur à partir de la fonction Lambda indique qu'un lot entier a échoué. Cela peut être acceptable pour des scénarios spécifiques dans lesquels une erreur irrécupérable se produit, telle qu'un échec de connexion à un magasin de données. Toutefois, dans les cas où certains éléments du lot réussissent et d'autres échouent, il est possible de renvoyer à la fois des erreurs et des données valides. Étant donné que la réponse par lots AWS AppSync nécessite de répertorier les éléments correspondant à la taille d'origine du lot, vous devez définir une structure de données capable de différencier les données valides d'une erreur.
Par exemple, si la fonction Lambda est censée renvoyer un lot de publications connexes, vous pouvez choisir de renvoyer une liste d'Response
objets contenant des données et errorTypedes champs facultatifs pour chaque objet. errorMessage Si le errorMessagechamp est présent, cela signifie qu'une erreur s'est produite.
Le code suivant montre comment mettre à jour la fonction 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) } }
Mettez à jour le code du relatedPosts
résolveur :
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 } }
Le gestionnaire de réponses vérifie désormais les erreurs renvoyées par la Invoke
fonction Lambda lors des opérations, vérifie les erreurs renvoyées pour les éléments individuels des BatchInvoke
opérations et vérifie enfin le. fieldName
CarrelatedPosts
, la fonction revientresult.data
. Pour tous les autres champs, la fonction renvoie simplementresult
. Par exemple, consultez la requête ci-dessous :
query AllPosts { allPosts { id title content url ups downs relatedPosts { id } author } }
Cette requête renvoie une réponse GraphQL similaire à la suivante :
{ "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" } ] }
Configuration de la taille de lot maximale
Pour configurer la taille de lot maximale sur un résolveur, utilisez la commande suivante dans le 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
Note
Lorsque vous fournissez un modèle de mappage de demandes, vous devez utiliser l'BatchInvoke
opération pour utiliser le traitement par lots.