Le Blog Amazon Web Services

Appelez les services AWS directement à partir d’AWS AppSync

* Rédigé par Josh Kahn, architecte de solutions AWS senior basé à Chicago, IL.

AWS AppSync est un service GraphQL managé qui permet aux développeurs de créer facilement des applications Web et mobiles basées sur les données. Vous pouvez créer une API GraphQL serverless en connectant AWS AppSync à diverses sources de données, notamment Amazon DynamoDB , AWS Lambda et Amazon Elasticsearch Service. AWS AppSync a ajouté la prise en charge des sources de données HTTP en mai 2018, ce qui facilite l’ajout d’API existantes à vos points de terminaison GraphQL.

AWS AppSync a été étendu pour prendre en charge l’appel de services AWS via des sources de données HTTP. Pour qu’AWS identifie et autorise les demandes HTTP, celles-ci doivent être signées avec le processus Signature Version 4  Sinon, ces demandes sont rejetées. AWS AppSync peut désormais calculer la signature en votre nom, en fonction du rôle IAM fourni dans le cadre de la configuration de la source de données HTTP.

Cela signifie que vous pouvez appeler un large éventail de services AWS sans avoir à écrire une fonction Lambda intermédiaire. Par exemple, vous pouvez commencer à exécuter une machine à états AWS Step Functions, récupérer un mot de passe à partir d’AWS Secrets Manager ou répertorier les API GraphQL disponibles à partir d’AWS AppSync lui-même à partir d’un résolveur AWS AppSync.

Implémentation d’une requête nécessitant un traitement de longue durée sur AWS AppSync

Pour démontrer l’utilité de cette nouvelle fonctionnalité, construisons une API GraphQL avec une requête de longue durée. AWS AppSync limite l’exécution de la requête GraphQL à 30 secondes maximum. Cependant, nous pouvons avoir une requête (par exemple, une recherche) qui prend parfois une minute. Nous utilisons AWS Step Functions pour coordonner la requête longue en plusieurs étapes, mais vous pouvez également choisir de l’implémenter d’une autre manière. Nous utilisons des abonnements AWS AppSync pour mettre à jour le client de manière asynchrone lorsque la requête est terminée.

1. Créer une source de données HTTP

Pour démarrer l’exécution de la machine à états de recherche, à partir d’AWS AppSync, nous commençons par définir une nouvelle source de données HTTP.

Nous devons fournir deux composants supplémentaires pour appeler un service AWS :

  • Un rôle IAM pouvant être assumé par AWS AppSync avec l’autorisation d’appeler des états: StartExecution pour notre machine à états Step Functions
  • La configuration de la signature

AWS AppSync prend actuellement en charge l’ajout de ces composants à l’aide de l’AWS CLI, du SDK et de AWS CloudFormation.
Tout d’abord, nous créons un nouveau rôle IAM avec la stratégie suivante (veillez à noter le rôle Amazon Resource Name (ARN)) :

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "states:StartExecution"
            ],
            "Resource": [
                "arn:aws:states:<REGION>:<ACCOUNT_ID>:stateMachine:aws-appsync-long-query"
            ],
            "Effect": "Allow"
        }
    ]
}

Et la relation de confiance :

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "appsync.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Ensuite, nous créons la source de données HTTP pour AWS AppSync. Pour ce faire, créez le fichier de configuration suivant nommé http.json.

{
    "endpoint": "https://states.<REGION>.amazonaws.com/",
    "authorizationConfig": {
        "authorizationType": "AWS_IAM",
        "awsIamConfig": {
            "signingRegion": "<REGION>",
            "signingServiceName": "states" 
        }
    }
}

Ensuite, utilisez l’AWS CLI pour créer la source de données :

$ aws appsync create-data-source --api-id <API_ID> \ 
                                 --name StepFunctionHttpDataSource \ 
                                 --type HTTP \
                                 --http-config file:///http.json \
                                 --service-role-arn <ROLE_ARN>

Vous pouvez également créer la même source de données à l’aide de AWS CloudFormation :

StepFunctionsHttpDataSource:
    Type: AWS::AppSync::DataSource
    Properties:
      ApiId: !GetAtt SearchApi.ApiId
      Name: StepFunctionsHttpDataSource
      Description: Step Functions HTTP
      Type: HTTP
      # IAM role defined elsewhere in AWS CloudFormation template
      ServiceRoleArn: !GetAtt AppSyncServiceRole.Arn
      HttpConfig:
        Endpoint: !Sub https://states.${AWS::Region}.amazonaws.com/
        AuthorizationConfig:
          AuthorizationType: AWS_IAM
          AwsIamConfig:
            SigningRegion: !Ref AWS::Region
            SigningServiceName: states

Avec notre source de données HTTP définie et configurée pour signer les demandes à AWS Step Functions, nous pouvons construire notre API GraphQL.

2. Construire une API GraphQL

Pour activer notre requête nécessitant un traitement de longue durée, nous soumettons la demande de recherche et renvoyons immédiatement le statut de la recherche ( PENDING ), ainsi qu’un identifiant unique pour la requête. Notre client doit ensuite s’abonner aux mises à jour pour cette requête particulière. Le schéma GraphQL est le suivant :

type Result {
  id: ID!
  status: ResultStatus!
  listings: [String]
}

enum ResultStatus {
  PENDING
  COMPLETE
  ERROR
}

input ResultInput {
  id: ID!
  status: ResultStatus!
  listings: [String]!
}

type Query {
  # called by client to initiate long running search
  search(text: String!): Result
}

type Mutation {
  # called by backend when search is complete
  publishResult(result: ResultInput): Result
}

type Subscription {
  onSearchResult(id: ID!): [Result]
    @aws_subscribe(mutations: [ "publishResult" ])
}

schema {
  query: Query
  mutation: Mutation
  subscription: Subscription
}

Ensuite, définissez le résolveur pour la requête de recherche dans AWS Management Console, AWS CLI, SDK ou AWS CloudFormation. Sélectionnez la source de données HTTP créée précédemment et configurez le modèle de correspondance de demande comme suit :

$util.qr($ctx.stash.put("executionId", $util.autoId()))

{
  "version": "2018-05-29",
  "method": "POST",
  "resourcePath": "/",
  "params": {
    "headers": {
      "content-type": "application/x-amz-json-1.0",
      "x-amz-target":"AWSStepFunctions.StartExecution"
    },
    "body": {
      "stateMachineArn": "arn:aws:states:<REGION>:<ACCOUNT_ID>:stateMachine:aws-appsync-long-query",
      "input": "{ \"name\": \"$ctx.stash.executionId\" }"
    }
  }
}

Il y a quelques éléments à noter dans ce fichier de correspondance :

  • Nous utilisons la fonction AWS AppSync $util.autoId() pour générer un indentifiant unique pour la requête de recherche. Cette valeur est stockée dans le contexte et est ensuite renvoyée au client. Cette valeur est également transmise en tant que paramètre d’entrée à notre machine à états,
  • L’ entête x-amz-target spécifie le service AWS et l’action associée à appeler,
  • Les données d’entrée JSON, telles que l’entrée de notre machine à états, doivent être à double échappement.

Le mappage de réponse pour ce résolveur renvoie immédiatement l’identifiant de la requête et le statut PENDING :

{
  "id": "${ctx.stash.executionId}",
  "status": "PENDING"
}

Lors de l’exécution de la requête de recherche, AWS AppSync démarre la machine à états souhaitée pour Step Functions, en passant l’identifiant d’exécution en tant que paramètre. La dernière étape de la machine à états appelle une fonction Lambda. Cette fonction déclenche une mutation AppSync. Cela provoque la publication des résultats sur le client via un abonnement.

3. Implémenter la requête nécessitant un traitement long et retour d’un résultat

Avec notre requête AWS AppSync nous pouvons maintenant déclencher l’exécution de la machine à états de Step Functions. Réalisons l’implémentation de la requête pour renvoyer une réponse au client. À des fins de démonstration, notre machine à états est assez simple. Elle attend une minute, puis appelle la tâche «Renvoyer les résultats», comme indiqué dans le diagramme suivant.

Vous pouvez facilement mettre à jour cette définition de machine à états pour prendre en charge des requêtes plus complexes ou à étapes multiples, à condition que la définition renvoie des résultats via une mutation AWS AppSync.

Généralement, un client GraphQL exécute une mutation de données qui provoque une modification dans une source de données, ce qui déclenche ensuite une notification de la modification aux abonnés. Dans ce cas, nous définissons une fonction Lambda qui appelle la mutation pour déclencher une notification des résultats.

Au lieu d’inclure une bibliothèque GraphQL, nous utilisons un simple POST HTTP vers le point de terminaison AWS AppSync, en transmettant des paramètres appropriés pour une mutation. Bien que notre exemple utilise un type d’autorisation AWS AppSync API_KEY, nous recommandons d’utiliser un type d’autorisation AWS_IAM dans un scénario de production et également de signer la demande. À l’aide de l’ exécution de nodejs8.10, notre fonction de renvoi de résultat est implémentée comme suit :

const PublishResultMutation = `mutation PublishResult(
    $id: ID!,
    $status: ResultStatus!,
    $listings: [String]!
  ) {
    publishResult(result: {id: $id, status: $status, listings: $listings}) {
      id
      status
      listings
    }
  }`;

const executeMutation = async(id) => {
  const mutation = {
    query: PublishResultMutation,
    operationName: 'PublishResult',
    variables: {
      id: id,
      status: 'COMPLETE',
      listings: [ "foo", "bar" ]
    },
  };

  try {
    let response = await axios({
      method: 'POST',
      url: process.env.APPSYNC_ENDPOINT,
      data: JSON.stringify(mutation),
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': process.env.APPSYNC_API_KEY,
      }
    });
    console.log(response.data);
  } catch (error) {
    console.error(`[ERROR] ${error.response.status} - ${error.response.data}`);
    throw error;
  }
};

exports.handler = async(event) => {
  // unique identifier of query is passed as event.name
  await executeMutation(event.name)
  return { message: `finished` }
}

AWS AppSync envoie une notification au client abonné pour ce résultat de requête spécifique. Le client peut alors mettre à jour son application de manière appropriée.

4. Implémenter le client

Avec AWS Amplify, nous pouvons implémenter un client simple pour montrer le processus de soumission de la requête, puis de souscription aux résultats à l’aide de l’identifiant renvoyé.

import Amplify, { API, graphqlOperation } from "aws-amplify";

// 1. Submit search query
const { data: { search } } = await API.graphql(
    graphqlOperation(searchQuery, { text: 'test' })
);
console.log(`Query ID: ${search.id}`);

// 2. Subscribe to search result
const subscription = API.graphql(
        graphqlOperation(onSearchResultSubscription, { queryId: search.id })
    ).subscribe({
        next: (result) => {
            // Stop receiving data updates from the subscription
            subscription.unsubscribe();
            console.log(result);
        }
    });

Vous pouvez trouver un exemple de ce projet sur GitHub.

L’appel de services AWS directement depuis AWS AppSync peut simplifier un certain nombre de cas d’utilisation, tels que la mise en œuvre d’une requête de longue durée ou l’activation d’AWS AppSync en tant que façade GraphQL pour d’autres services. Pour plus de détails, consultez le guide AWS AppSync Developer Guide. Nous sommes impatients de voir comment vous utiliserez cette nouvelle fonctionnalité. Contactez-nous sur le forum AWS AppSync pour nous faire part de vos commentaires.

Traduit en Français par Frédéric Nowak, Architecte solutions, passionné de jeux vidéo et aidant les clients de toute taille à migrer vers le cloud.