Fédération
La fédération offre un moyen de diviser votre serveur GraphQL monolithique en microservices indépendants. Elle se compose de deux composants : une passerelle et un ou plusieurs microservices fédérés. Chaque microservice détient une partie du schéma et la passerelle fusionne les schémas en un seul schéma pouvant être consommé par le client.
Pour citer la documentation Apollo, la fédération est conçue avec ces principes fondamentaux :
- Construire un graphe doit être déclaratif. Avec la fédération, vous composez un graphe de manière déclarative à partir de votre schéma au lieu d’écrire du code d’assemblage de schéma impératif.
- Le code doit être séparé par préoccupation, et non par types. Souvent, aucune équipe unique ne contrôle tous les aspects d’un type important comme un utilisateur ou un produit, donc la définition de ces types doit être distribuée entre les équipes et les bases de code, plutôt que centralisée.
- Le graphe doit être simple à consommer pour les clients. Ensemble, les services fédérés peuvent former un graphe complet et axé sur le produit qui reflète fidèlement comment il est consommé par le client.
- C’est juste GraphQL, utilisant uniquement les fonctionnalités conformes à la spécification du langage. N’importe quel langage, pas seulement JavaScript, peut implémenter la fédération.
Dans les sections suivantes, nous allons mettre en place une application de démonstration qui se compose d’une passerelle et de deux points de terminaison fédérés : le service des utilisateurs et le service des posts.
Fédération avec Apollo
Commencez par installer les dépendances requises :
$ npm install --save @apollo/subgraph
Schéma d’abord
Le « service utilisateur » fournit un schéma simple. Notez la directive @key
: elle indique au planificateur de requêtes Apollo qu’une instance particulière de User
peut être récupérée si vous spécifiez son id
. De plus, notez que nous extend
le type Query
.
type User @key(fields: "id") { id: ID! name: String!}
extend type Query { getUser(id: ID!): User}
Le resolver fournit une méthode supplémentaire nommée resolveReference()
. Cette méthode est déclenchée par la passerelle Apollo chaque fois qu’une ressource liée nécessite une instance d’utilisateur. Nous verrons un exemple de cela dans le service des posts plus tard. Veuillez noter que la méthode doit être annotée avec le décorateur @ResolveReference()
.
import { Args, Query, Resolver, ResolveReference } from '@nestjs/graphql';import { UsersService } from './users.service';
@Resolver('User')export class UsersResolver { constructor(private usersService: UsersService) {}
@Query() getUser(@Args('id') id: string) { return this.usersService.findById(id); }
@ResolveReference() resolveReference(reference: { __typename: string; id: string }) { return this.usersService.findById(reference.id); }}
Enfin, nous connectons le tout en enregistrant le GraphQLModule
en passant le driver ApolloFederationDriver
dans l’objet de configuration :
import { ApolloFederationDriver, ApolloFederationDriverConfig } from '@nestjs/apollo';import { Module } from '@nestjs/common';import { GraphQLModule } from '@nestjs/graphql';import { UsersResolver } from './users.resolver';
@Module({ imports: [ GraphQLModule.forRoot<ApolloFederationDriverConfig>({ driver: ApolloFederationDriver, autoSchemaFile: true, }), ], providers: [UsersResolver],})export class AppModule {}
Code d’abord
Commencez par ajouter quelques décorateurs supplémentaires à l’entité User
.
import { Directive, Field, ID, ObjectType } from '@nestjs/graphql';
@ObjectType()@Directive('@key(fields: "id")')export class User { @Field((type) => ID) id: number;
@Field() name: string;}
Le resolver fournit également une méthode supplémentaire nommée resolveReference()
. Cette méthode est déclenchée par la passerelle chaque fois qu’une ressource liée nécessite une instance User
.
import { Args, Query, Resolver, ResolveReference } from '@nestjs/graphql';import { UsersService } from './users.service';
@Resolver((of) => User)export class UsersResolver { constructor(private usersService: UsersService) {}
@Query((returns) => User) getUser(@Args('id') id: number): User { return this.usersService.findById(id); }
@ResolveReference() resolveReference(reference: { __typename: string; id: number }): User { return this.usersService.findById(reference.id); }}
Enfin, nous connectons le tout en enregistrant le GraphQLModule
et en spécifiant le comportement de la fédération.
import { ApolloFederationDriver, ApolloFederationDriverConfig } from '@nestjs/apollo';import { Module } from '@nestjs/common';import { GraphQLModule } from '@nestjs/graphql';import { UsersResolver } from './users.resolver';import { UsersService } from './users.service';
@Module({ imports: [ GraphQLModule.forRoot<ApolloFederationDriverConfig>({ driver: ApolloFederationDriver, autoSchemaFile: true, }), ], providers: [UsersResolver, UsersService],})export class AppModule {}
Exemple fédéré : Posts
Le service de posts est censé servir des posts agrégés via la requête getPosts
, mais aussi étendre notre type User
avec le champ user.posts
.
Schéma d’abord
Le « service des posts » référence le type User
dans son schéma en le marquant avec le mot-clé extend
. Il déclare également une propriété supplémentaire sur le type User
(posts
). Notez la directive @key
utilisée pour faire correspondre les instances d’utilisateur, et la directive @external
indiquant que le champ id
est géré ailleurs.
type Post @key(fields: "id") { id: ID! title: String! body: String! user: User}
extend type User @key(fields: "id") { id: ID! @external posts: [Post]}
extend type Query { getPosts: [Post]}
Dans l’exemple suivant, le PostsResolver
fournit la méthode getUser()
qui renvoie une référence contenant __typename
et certaines propriétés supplémentaires dont votre application peut avoir besoin pour résoudre la référence, dans ce cas id
. __typename
est utilisé par la passerelle GraphQL pour identifier le microservice responsable du type User
et récupérer l’instance correspondante. Le service des utilisateurs décrit ci-dessus sera demandé lors de l’exécution de la méthode resolveReference()
.
import { Query, Resolver, Parent, ResolveField } from '@nestjs/graphql';import { PostsService } from './posts.service';import { Post } from './posts.interfaces';
@Resolver('Post')export class PostsResolver { constructor(private postsService: PostsService) {}
@Query('getPosts') getPosts() { return this.postsService.findAll(); }
@ResolveField('user') getUser(@Parent() post: Post) { return { __typename: 'User', id: post.userId }; }}
Et finalement, regroupez le tout dans un module. Notez les options de construction du schéma, où nous spécifions que User
est un type orphelin (externe).
import { MercuriusGatewayDriver, MercuriusGatewayDriverConfig } from '@nestjs/mercurius';import { Module } from '@nestjs/common';import { GraphQLModule } from '@nestjs/graphql';
@Module({ imports: [ GraphQLModule.forRoot<MercuriusGatewayDriverConfig>({ driver: MercuriusGatewayDriver, gateway: { services: [ { name: 'users', url: 'http://user-service/graphql' }, { name: 'posts', url: 'http://post-service/graphql' }, ], }, }), ],})export class AppModule {}
Exemple fédéré : Passerelle
Commencez par installer la dépendance requise :
$ npm install --save @apollo/gateway
La passerelle nécessite qu’une liste de points de terminaison soit spécifiée et elle découvrira automatiquement les schémas correspondants. Par conséquent, l’implémentation du service de passerelle restera la même pour les approches de code et de schéma.
import { IntrospectAndCompose } from '@apollo/gateway';import { MercuriusGatewayDriver, MercuriusGatewayDriverConfig } from '@nestjs/mercurius';import { Module } from '@nestjs/common';import { GraphQLModule } from '@nestjs/graphql';
@Module({ imports: [ GraphQLModule.forRoot<MercuriusGatewayDriverConfig>({ driver: MercuriusGatewayDriver, gateway: { supergraphSdl: new IntrospectAndCompose({ subgraphs: [ { name: 'users', url: 'http://user-service/graphql' }, { name: 'posts', url: 'http://post-service/graphql' }, ], }), }, }), ],})export class AppModule {}
Fédération 2
Pour citer la documentation Apollo, la fédération 2 améliore l’expérience des développeurs par rapport à l’ancienne fédération Apollo (appelée fédération 1 dans ce document), qui est compatible avec la plupart des supergraphs originaux.
Dans les sections suivantes, nous allons mettre à niveau l’exemple précédent vers la fédération 2.
Exemple fédéré : Utilisateurs
Un changement dans la fédération 2 est que les entités n’ont pas de sous-graphe d’origine, donc nous n’avons plus besoin d’étendre Query
. Pour plus de détails, veuillez vous référer au sujet des entités dans la documentation Apollo Federation 2.
Schéma d’abord
Nous pouvons simplement supprimer le mot-clé extend
du schéma.
type User @key(fields: "id") { id: ID! name: String!}
type Query { getUser(id: ID!): User}
Code d’abord
Pour utiliser la fédération 2, nous devons spécifier la version de fédération dans l’option autoSchemaFile
.
import { ApolloFederationDriver, ApolloFederationDriverConfig } from '@nestjs/apollo';import { Module } from '@nestjs/common';
@Module({ imports: [ GraphQLModule.forRoot<ApolloFederationDriverConfig>({ driver: ApolloFederationDriver, autoSchemaFile: { federation: 2, }, }), ],})export class AppModule {}
Exemple fédéré : Posts
Pour la même raison que ci-dessus, nous n’avons plus besoin d’étendre User
et Query
.
Schéma d’abord
Nous pouvons simplement supprimer les directives extend
et external
du schéma :
type Post @key(fields: "id") { id: ID! title: String! body: String! user: User}
type User @key(fields: "id") { id: ID! posts: [Post]}
type Query { getPosts: [Post]}
Code d’abord
Puisque nous n’étendons plus l’entité User
, nous pouvons simplement supprimer les directives extends
et external
de User
.
import { Directive, ObjectType, Field, ID } from '@nestjs/graphql';
@ObjectType()@Directive('@key(fields: "id")')export class User { @Field((type) => ID) id: number;
@Field(() => [Post]) posts?: Post[];}
De plus, nous devons également spécifier dans le GraphQLModule
d’utiliser la fédération 2.
import { MercuriusFederationDriver, MercuriusFederationDriverConfig } from '@nestjs/mercurius';import { Module } from '@nestjs/common';
@Module({ imports: [ GraphQLModule.forRoot<MercuriusFederationDriverConfig>({ driver: MercuriusFederationDriver, autoSchemaFile: { federation: 2, }, }), ],})export class AppModule {}