AppSync si vous voulez un endpoint GraphQL qu'AWS fait tourner à votre place, et plus encore si les subscriptions font partie du produit ; Apollo Server si le schéma, le middleware et les sources de données doivent vous appartenir, et que vous acceptez d'administrer le processus qui les sert. Tout le reste découle de cette seule ligne de partage : un service managé facturé à l'opération, ou une bibliothèque hébergée sur Lambda, Fargate ou un conteneur, payée à l'heure de calcul.
AppSync et Apollo Server : qu'est-ce qui les sépare vraiment ?
Ce ne sont pas deux objets de même nature, et la plupart des tableaux comparatifs se trompent dès la première ligne. AWS AppSync est un endpoint GraphQL managé : vous publiez un schéma, vous attachez des resolvers, et AWS fait tourner le parseur, le moteur d'exécution, la couche de connexions et la montée en charge. Apollo Server est une bibliothèque. Vous l'importez dans un processus Node, vous décidez où ce processus vit, et vous possédez tout ce qui l'entoure : déploiement, scaling, correctifs, logs, astreinte.
La question n'est donc jamais « quelle implémentation GraphQL est la meilleure », mais « GraphQL doit-il être une brique d'infrastructure administrée par quelqu'un d'autre, ou un morceau de notre application ». Les équipes qui font déjà tourner des conteneurs et veulent des resolvers en TypeScript ordinaire, à côté du reste du code métier, penchent vers Apollo. Celles qui veulent un schéma, une URL et rien de nouveau à surveiller la nuit penchent vers AppSync. Nous déroulons le même raisonnement côté REST dans notre comparatif AppSync et API Gateway.
En quoi les resolvers AppSync diffèrent-ils des resolvers Apollo ?
Chez AppSync, un resolver est une petite fonction JavaScript composée d'un handler de requête et d'un handler de réponse, exécutée dans un runtime volontairement contraint : pas d'appels réseau à vous, pas de packages npm, pas de traitement long. Elle décrit l'opération, et AppSync l'exécute sur la source de données : DynamoDB, Aurora via la Data API, OpenSearch, un endpoint HTTP, ou une fonction Lambda quand ce runtime ne suffit plus. Les templates VTL sont la forme historique de la même idée, et peuplent encore beaucoup de stacks en production.
# Le schéma, identique quel que soit le camp choisi
type Order {
id: ID!
status: String!
}
type Mutation {
updateStatus(id: ID!, status: String!): Order
}
type Subscription {
onOrderUpdated(id: ID!): Order
@aws_subscribe(mutations: ["updateStatus"])
}
// Resolver JavaScript AppSync : DynamoDB en direct, sans Lambda
import { util } from '@aws-appsync/utils';
export function request(ctx) {
return {
operation: 'UpdateItem',
key: util.dynamodb.toMapValues({ id: ctx.args.id }),
update: {
expression: 'SET #s = :s',
expressionNames: { '#s': 'status' },
expressionValues: util.dynamodb.toMapValues({ ':s': ctx.args.status }),
},
};
}
export function response(ctx) {
return ctx.result;
}
Le compromis est réel. Un resolver qui attaque DynamoDB en direct n'a ni cold start ni fonction à maintenir, difficile à battre pour des lectures et des écritures simples. Dès qu'un champ réclame un SDK tiers, un client de cache ou du code métier partagé, vous branchez un resolver Lambda et vous revoilà à écrire et à administrer des fonctions. Chez Apollo, le resolver est une fonction TypeScript de votre dépôt, libre d'importer ce qu'elle veut, avec un DataLoader dans le contexte et la même histoire de tests que le reste du code.
// Apollo Server derrière un handler Lambda : du TypeScript ordinaire
import { ApolloServer } from '@apollo/server';
import { startServerAndCreateLambdaHandler, handlers } from '@as-integrations/aws-lambda';
const server = new ApolloServer({
typeDefs,
resolvers: {
Mutation: {
updateStatus: (_parent, args, ctx) => ctx.orders.setStatus(args.id, args.status),
},
},
});
export const handler = startServerAndCreateLambdaHandler(
server,
handlers.createAPIGatewayProxyEventV2RequestHandler(),
{
context: async ({ event }) => ({
orders: makeOrderService(),
user: await authentifier(event.headers),
}),
},
);
L'autorisation rejoue exactement le même arbitrage. Sur AppSync, les pools Cognito, IAM, OIDC et les clés d'API se déclarent, champ par champ si besoin, un authorizer Lambda couvrant ce que les modes intégrés ne font pas. Si votre modèle d'auth vit déjà dans Cognito ou dans IAM, c'est du travail que vous ne faites pas du tout. S'il vit dans un service de session maison avec des règles par tenant, vous écrirez cet authorizer de toute façon, et la fonction de contexte d'Apollo aurait été plus directe.
Pourquoi les subscriptions tranchent-elles le débat ?
Parce que la couche WebSocket est précisément la partie que personne ne veut porter. AppSync intègre les subscriptions sur WebSocket dans le service : un champ de subscription déclaré dans le schéma, rattaché à une mutation, et AWS gère les connexions, la diffusion, le filtrage, les reconnexions et l'échelle.
Avec Apollo Server, les subscriptions sont à votre charge. Cela suppose un processus qui garde les connexions ouvertes, ce qui écarte Lambda pour le socket lui-même et vous oriente vers Fargate, ECS ou une autre plateforme de conteneurs. Cela suppose aussi un backend PubSub pour qu'un message publié sur une instance atteigne les abonnés d'une autre, des health checks, un drainage des connexions, et une réponse à la question du déploiement qui coupe tous les sockets d'un coup. Quand le temps réel est au centre du produit, cette seule ligne décide du comparatif plus souvent que toutes les autres.
Couche GraphQL managée ou la vôtre : le choix vous bloque ? Décrivez votre système : diagnostic d'une page sous 48 h.
Recevoir mon diagnostic →Fédération ou Merged APIs : quoi pour plusieurs équipes ?
Quand un même graphe sert plusieurs équipes, les deux camps ont une réponse, et ces réponses ne sont pas équivalentes. Apollo Federation compose des subgraphs déployés indépendamment en un supergraph, avec des références d'entités qui permettent à un type déclaré dans un subgraph d'être étendu par un autre. C'est l'option mature : un routeur devant, des schema checks, et une étape de composition en CI qui fait échouer le build quand un changement casserait un consommateur.
Les Merged APIs d'AppSync fusionnent plusieurs API sources en un endpoint unique, chaque équipe gardant son API AppSync, avec des règles de conflit au moment de la fusion. Le découpage des espaces de noms et des responsabilités est bien couvert, mais une fusion n'est pas de la résolution d'entités fédérées : un même type ne peut pas être résolu à travers plusieurs API sources comme le ferait une entité fédérée. Merged APIs quand chaque équipe possède une portion distincte du graphe ; Federation quand elles possèdent des champs différents des mêmes types.
AppSync, Apollo sur Lambda ou Apollo sur Fargate : le comparatif
Nommer les axes fait tomber l'essentiel du débat. Le tableau ci-dessous aligne les trois déploiements réalistes, avec le scénario gagnant de chacun.
| Critère | AppSync | Apollo Server sur Lambda | Apollo Server sur Fargate |
|---|---|---|---|
| Qui le fait tourner | AWS : parseur, moteur, scaling, sockets | Vous le code, AWS le runtime | Vous le code, la tâche, la politique de scaling |
| Subscriptions | Intégrées, WebSocket managé | Hors sujet : Lambda ne garde pas de socket | À construire, avec PubSub et drainage |
| Fédération | Merged APIs : une fusion, pas d'entités fédérées | Apollo Federation complète, en subgraph | Apollo Federation complète, routeur compris |
| Autorisation | Cognito, IAM, OIDC, clés d'API, déclaratif par champ | Votre code dans la fonction de contexte | Votre code, plus ce qui expose la tâche |
| Liberté des resolvers | Runtime JS contraint, ou une Lambda par champ | Tout package, toute source de données | Tout package, toute source, connexions chaudes |
| Forme de coût | À l'opération, plus temps réel et cache optionnel | À l'invocation et à la mémoire-temps | Au vCPU-heure et au Go-heure, temps mort compris |
| Quand il gagne | Produits temps réel, auth AWS native, petite équipe | Trafic de queries en rafales avec logique maison | Trafic soutenu, subscriptions, graphe fédéré |
Le schéma est-il portable, et que coûte une sortie ?
Le schéma, lui, est portable. Le SDL GraphQL est un standard, et un schéma écrit pour AppSync se lit partout ailleurs. Ce qui ne bouge pas, c'est tout ce qui l'entoure. Les resolvers JavaScript utilisent les utilitaires AppSync et son contrat de requête, les templates VTL n'existent que là, et les intégrations directes aux sources de données, la sémantique des subscriptions liée aux mutations, la configuration du cache et l'autorisation déclarative doivent toutes être réécrites en vrai code le jour de la sortie.
La formulation honnête, c'est un coût de sortie, pas un slogan sur l'enfermement. Quitter AppSync signifie réécrire les resolvers en fonctions, monter un serveur et construire une couche de subscriptions : un projet, pas un script de migration. Dans l'autre sens, passer d'Apollo à AppSync suppose de faire entrer du code de resolver arbitraire dans un runtime contraint, ou d'envelopper chaque champ dans une Lambda, ce qui efface souvent la raison même du choix. Décidez avec ce chiffre sous les yeux, comme pour les autres décisions de services AWS que nous publions.
À quoi ressemblent les deux formes de coût ?
Des formes, pas des chiffres. AppSync facture à la query et à la mutation, le temps réel étant compté à part, par mise à jour de subscription livrée et par minute de connexion, le cache serveur optionnel ajoutant une instance facturée à l'heure. Apollo Server n'a aucun prix à l'opération : vous payez ce qui le fait tourner, invocations et mémoire-temps sur Lambda, ou vCPU-heures et Go-heures sur Fargate, plus le répartiteur de charge et le backend PubSub dès que les subscriptions entrent en jeu.
Le point de croisement devient donc prévisible. Un trafic en rafales avec de longues périodes creuses convient à la facturation à l'opération, qui ne coûte rien au repos. Un trafic soutenu et volumineux convient à la forme calcul, où une tâche bien occupée sert un très grand nombre d'opérations pour un tarif horaire fixe. Confrontez votre histogramme de trafic réel à la tarification AppSync et au prix de calcul de votre runtime avant de suivre l'une ou l'autre intuition, et comptez les heures d'exploitation qu'Apollo ajoute, même si aucune facture ne les mentionne. Si le facteur décisif se révèle être ce qui expose le service plutôt que le service lui-même, notre comparatif API Gateway, ALB et Function URLs couvre cette couche.
Check-list de décision
- Des subscriptions dans le produit et aucune envie d'administrer des serveurs WebSocket : AppSync.
- Une auth déjà dans Cognito, IAM ou un fournisseur OIDC : AppSync, déclaré champ par champ.
- Des resolvers qui réclament des packages arbitraires, du code métier partagé ou des sources de données peu commodes : Apollo Server.
- Plusieurs équipes sur des champs différents des mêmes types : Apollo Federation. Des portions distinctes du graphe : Merged APIs.
- Trafic en rafales avec de longs temps morts : facturation à l'opération. Volume soutenu : un conteneur que vous gardez occupé.
- Dans les deux cas : schema checks en CI, persisted queries ou limite de profondeur, et une alarme sur les erreurs de resolver.