Federation (liên hợp) cung cấp một cách để chia nhỏ GraphQL server dạng monolithic của bạn thành các microservice độc lập. Nó gồm hai thành phần: một gateway (cổng vào) và một hoặc nhiều microservice federated. Mỗi microservice giữ một phần của schema, và gateway sẽ hợp nhất (merge) các schema này thành một schema duy nhất mà client có thể sử dụng.
Trích dẫn từ tài liệu Apollo, Federation được thiết kế theo các nguyên tắc cốt lõi sau:
- Việc xây dựng một graph nên mang tính khai báo (declarative). Với federation, bạn tổng hợp một graph theo cách khai báo ngay trong schema, thay vì viết code schema stitching (ghép schema) theo kiểu mệnh lệnh (imperative).
- Code nên được tách theo mối quan tâm (concern), chứ không phải theo type. Thường thì không có team nào một mình kiểm soát mọi khía cạnh của một type quan trọng như User hay Product, nên định nghĩa của các type này nên được phân tán qua nhiều team và codebase, thay vì tập trung một chỗ.
- Graph nên đơn giản để client sử dụng. Khi kết hợp lại, các federated service tạo thành một graph hoàn chỉnh, hướng theo sản phẩm, phản ánh đúng cách nó được client tiêu thụ.
- Đây đơn giản là GraphQL, chỉ dùng các tính năng tuân theo spec của ngôn ngữ này. Bất kỳ ngôn ngữ nào, không riêng gì JavaScript, đều có thể triển khai federation.
Cảnh báo Federation hiện chưa hỗ trợ subscription.
Trong các phần dưới đây, ta sẽ dựng một ứng dụng demo gồm một gateway và hai endpoint federated: Users service và Posts service.
Federation với Apollo
Bắt đầu bằng cách cài đặt các package cần thiết:
$ npm install --save @apollo/subgraph
Schema first
“User service” cung cấp một schema đơn giản. Lưu ý directive @key: nó chỉ cho query planner của Apollo biết rằng một instance cụ thể của User có thể được lấy về nếu bạn chỉ định id của nó. Ngoài ra, lưu ý rằng ta extend (mở rộng) type Query.
type User @key(fields: "id") {
id: ID!
name: String!
}
extend type Query {
getUser(id: ID!): User
}
Resolver cung cấp thêm một method tên là resolveReference(). Method này được Apollo Gateway kích hoạt bất cứ khi nào một resource liên quan cần một instance User. Ta sẽ thấy ví dụ về việc này ở Posts service sau. Lưu ý method này phải được gắn decorator @ResolveReference().
import { Args, Query, Resolver, ResolveReference } from '@nestjs/graphql';
import { UsersService } from './users.service.js';
@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);
}
}
Cuối cùng, ta ghép mọi thứ lại bằng cách đăng ký GraphQLModule, truyền driver ApolloFederationDriver trong object cấu hình:
import {
ApolloFederationDriver,
ApolloFederationDriverConfig,
} from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { UsersResolver } from './users.resolver.js';
@Module({
imports: [
GraphQLModule.forRoot<ApolloFederationDriverConfig>({
driver: ApolloFederationDriver,
typePaths: ['**/*.graphql'],
}),
],
providers: [UsersResolver],
})
export class AppModule {}
Code first
Bắt đầu bằng cách thêm vài decorator vào entity User.
import { Directive, Field, ID, ObjectType } from '@nestjs/graphql';
@ObjectType()
@Directive('@key(fields: "id")')
export class User {
@Field(() => ID)
id: number;
@Field()
name: string;
}
Resolver cung cấp thêm một method tên là resolveReference(). Method này được Apollo Gateway kích hoạt bất cứ khi nào một resource liên quan cần một instance User. Ta sẽ thấy ví dụ về việc này ở Posts service sau. Lưu ý method này phải được gắn decorator @ResolveReference().
import { Args, Query, Resolver, ResolveReference } from '@nestjs/graphql';
import { User } from './user.entity.js';
import { UsersService } from './users.service.js';
@Resolver(() => User)
export class UsersResolver {
constructor(private usersService: UsersService) {}
@Query(() => 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);
}
}
Cuối cùng, ta ghép mọi thứ lại bằng cách đăng ký GraphQLModule, truyền driver ApolloFederationDriver trong object cấu hình:
import {
ApolloFederationDriver,
ApolloFederationDriverConfig,
} from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { UsersResolver } from './users.resolver.js';
import { UsersService } from './users.service.js'; // Not included in this example
@Module({
imports: [
GraphQLModule.forRoot<ApolloFederationDriverConfig>({
driver: ApolloFederationDriver,
autoSchemaFile: true,
}),
],
providers: [UsersResolver, UsersService],
})
export class AppModule {}
Bạn có thể xem ví dụ hoạt động thực tế tại đây cho cách tiếp cận code first, và tại đây cho cách tiếp cận schema first.
Ví dụ federated: Posts
Posts service được thiết kế để phục vụ danh sách post tổng hợp qua query getPosts, đồng thời mở rộng type User với field user.posts.
Schema first
“Posts service” tham chiếu đến type User trong schema của nó bằng cách đánh dấu bằng từ khóa extend. Nó cũng khai báo thêm một thuộc tính trên type User (posts). Lưu ý directive @key dùng để khớp các instance của User, và directive @external cho biết field id được quản lý ở nơi khác.
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]
}
Trong ví dụ dưới đây, PostsResolver cung cấp method getUser() trả về một reference (tham chiếu) chứa __typename cùng một số thuộc tính bổ sung mà ứng dụng của bạn cần để resolve reference đó, trong trường hợp này là id. __typename được GraphQL Gateway dùng để xác định microservice nào chịu trách nhiệm cho type User và lấy về instance tương ứng. “Users service” đã mô tả ở trên sẽ được gọi tới khi method resolveReference() được thực thi.
import { Query, Resolver, Parent, ResolveField } from '@nestjs/graphql';
import { PostsService } from './posts.service.js';
import type { Post } from './posts.interfaces.js';
@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 };
}
}
Cuối cùng, ta cần đăng ký GraphQLModule, tương tự như đã làm ở phần “Users service”.
import {
ApolloFederationDriver,
ApolloFederationDriverConfig,
} from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { PostsResolver } from './posts.resolver.js';
@Module({
imports: [
GraphQLModule.forRoot<ApolloFederationDriverConfig>({
driver: ApolloFederationDriver,
typePaths: ['**/*.graphql'],
}),
],
providers: [PostsResolver],
})
export class AppModule {}
Code first
Trước tiên, ta cần khai báo một class đại diện cho entity User. Mặc dù entity này thực sự sống ở một service khác, ta vẫn sẽ sử dụng nó (mở rộng định nghĩa của nó) ở đây. Lưu ý các directive @extends và @external.
import { Directive, ObjectType, Field, ID } from '@nestjs/graphql';
import { Post } from './post.entity.js';
@ObjectType()
@Directive('@extends')
@Directive('@key(fields: "id")')
export class User {
@Field(() => ID)
@Directive('@external')
id: number;
@Field(() => [Post])
posts?: Post[];
}
Bây giờ hãy tạo resolver tương ứng cho phần mở rộng của ta trên entity User, như sau:
import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
import { PostsService } from './posts.service.js';
import { Post } from './post.entity.js';
import { User } from './user.entity.js';
@Resolver(() => User)
export class UsersResolver {
constructor(private readonly postsService: PostsService) {}
@ResolveField(() => [Post])
public posts(@Parent() user: User): Post[] {
return this.postsService.forAuthor(user.id);
}
}
Ta cũng cần định nghĩa class entity Post:
import { Directive, Field, ID, Int, ObjectType } from '@nestjs/graphql';
import { User } from './user.entity.js';
@ObjectType()
@Directive('@key(fields: "id")')
export class Post {
@Field(() => ID)
id: number;
@Field()
title: string;
@Field(() => Int)
authorId: number;
@Field(() => User)
user?: User;
}
Và resolver của nó:
import { Query, Args, ResolveField, Resolver, Parent } from '@nestjs/graphql';
import { PostsService } from './posts.service.js';
import { Post } from './post.entity.js';
import { User } from './user.entity.js';
@Resolver(() => Post)
export class PostsResolver {
constructor(private readonly postsService: PostsService) {}
@Query(() => Post)
findPost(@Args('id') id: number): Post {
return this.postsService.findOne(id);
}
@Query(() => [Post])
getPosts(): Post[] {
return this.postsService.all();
}
@ResolveField(() => User)
user(@Parent() post: Post): any {
return { __typename: 'User', id: post.authorId };
}
}
Và cuối cùng, ghép mọi thứ lại trong một module. Lưu ý phần schema build options, nơi ta chỉ định rằng User là một orphaned type (type “mồ côi”, tức type bên ngoài).
import {
ApolloFederationDriver,
ApolloFederationDriverConfig,
} from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { User } from './user.entity.js';
import { PostsResolver } from './posts.resolvers.js';
import { UsersResolver } from './users.resolvers.js';
import { PostsService } from './posts.service.js'; // Not included in example
@Module({
imports: [
GraphQLModule.forRoot<ApolloFederationDriverConfig>({
driver: ApolloFederationDriver,
autoSchemaFile: true,
buildSchemaOptions: {
orphanedTypes: [User],
},
}),
],
providers: [PostsResolver, UsersResolver, PostsService],
})
export class AppModule {}
Bạn có thể xem ví dụ hoạt động thực tế tại đây cho cách tiếp cận code first, và tại đây cho cách tiếp cận schema first.
Ví dụ federated: Gateway
Bắt đầu bằng cách cài đặt package cần thiết:
$ npm install --save @apollo/gateway
Gateway yêu cầu chỉ định một danh sách endpoint, và nó sẽ tự động dò (auto-discover) các schema tương ứng. Do đó, phần triển khai gateway service sẽ giống nhau cho cả hai cách tiếp cận code first và schema first.
import { IntrospectAndCompose } from '@apollo/gateway';
import { ApolloGatewayDriver, ApolloGatewayDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
@Module({
imports: [
GraphQLModule.forRoot<ApolloGatewayDriverConfig>({
driver: ApolloGatewayDriver,
server: {
// ... Apollo server options
cors: true,
},
gateway: {
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'users', url: 'http://user-service/graphql' },
{ name: 'posts', url: 'http://post-service/graphql' },
],
}),
},
}),
],
})
export class AppModule {}
Bạn có thể xem ví dụ hoạt động thực tế tại đây cho cách tiếp cận code first, và tại đây cho cách tiếp cận schema first.
Ghi chú người dịch So với microservice REST truyền thống — nơi mỗi service tự expose API riêng và một API Gateway (kiểu Kong, Express gateway) chỉ đơn thuần route request tới đúng service — Apollo Gateway ở đây làm nhiều hơn: nó thực sự hợp nhất schema thành một graph logic duy nhất, rồi tự lập kế hoạch truy vấn (query plan) để gọi song song/tuần tự tới nhiều subgraph và ráp kết quả lại, tất cả trong một request GraphQL. Đây là điểm khác biệt cốt lõi so với việc chỉ forward request như API Gateway thông thường.
Federation với Mercurius
Bắt đầu bằng cách cài đặt các package cần thiết:
$ npm install --save @apollo/subgraph @nestjs/mercurius
Lưu ý Package @apollo/subgraph là bắt buộc để build subgraph schema (các hàm buildSubgraphSchema, printSubgraphSchema).
Schema first
“User service” cung cấp một schema đơn giản. Lưu ý directive @key: nó chỉ cho query planner của Mercurius biết rằng một instance cụ thể của User có thể được lấy về nếu bạn chỉ định id của nó. Ngoài ra, lưu ý rằng ta extend type Query.
type User @key(fields: "id") {
id: ID!
name: String!
}
extend type Query {
getUser(id: ID!): User
}
Resolver cung cấp thêm một method tên là resolveReference(). Method này được Mercurius Gateway kích hoạt bất cứ khi nào một resource liên quan cần một instance User. Ta sẽ thấy ví dụ về việc này ở Posts service sau. Lưu ý method này phải được gắn decorator @ResolveReference().
import { Args, Query, Resolver, ResolveReference } from '@nestjs/graphql';
import { UsersService } from './users.service.js';
@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);
}
}
Cuối cùng, ta ghép mọi thứ lại bằng cách đăng ký GraphQLModule, truyền driver MercuriusFederationDriver trong object cấu hình:
import {
MercuriusFederationDriver,
MercuriusFederationDriverConfig,
} from '@nestjs/mercurius';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { UsersResolver } from './users.resolver.js';
@Module({
imports: [
GraphQLModule.forRoot<MercuriusFederationDriverConfig>({
driver: MercuriusFederationDriver,
typePaths: ['**/*.graphql'],
federationMetadata: true,
}),
],
providers: [UsersResolver],
})
export class AppModule {}
Code first
Bắt đầu bằng cách thêm vài decorator vào entity User.
import { Directive, Field, ID, ObjectType } from '@nestjs/graphql';
@ObjectType()
@Directive('@key(fields: "id")')
export class User {
@Field(() => ID)
id: number;
@Field()
name: string;
}
Resolver cung cấp thêm một method tên là resolveReference(). Method này được Mercurius Gateway kích hoạt bất cứ khi nào một resource liên quan cần một instance User. Ta sẽ thấy ví dụ về việc này ở Posts service sau. Lưu ý method này phải được gắn decorator @ResolveReference().
import { Args, Query, Resolver, ResolveReference } from '@nestjs/graphql';
import { User } from './user.entity.js';
import { UsersService } from './users.service.js';
@Resolver(() => User)
export class UsersResolver {
constructor(private usersService: UsersService) {}
@Query(() => 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);
}
}
Cuối cùng, ta ghép mọi thứ lại bằng cách đăng ký GraphQLModule, truyền driver MercuriusFederationDriver trong object cấu hình:
import {
MercuriusFederationDriver,
MercuriusFederationDriverConfig,
} from '@nestjs/mercurius';
import { Module } from '@nestjs/common';
import { UsersResolver } from './users.resolver.js';
import { UsersService } from './users.service.js'; // Not included in this example
@Module({
imports: [
GraphQLModule.forRoot<MercuriusFederationDriverConfig>({
driver: MercuriusFederationDriver,
autoSchemaFile: true,
federationMetadata: true,
}),
],
providers: [UsersResolver, UsersService],
})
export class AppModule {}
Ví dụ federated: Posts
Posts service được thiết kế để phục vụ danh sách post tổng hợp qua query getPosts, đồng thời mở rộng type User với field user.posts.
Schema first
“Posts service” tham chiếu đến type User trong schema của nó bằng cách đánh dấu bằng từ khóa extend. Nó cũng khai báo thêm một thuộc tính trên type User (posts). Lưu ý directive @key dùng để khớp các instance của User, và directive @external cho biết field id được quản lý ở nơi khác.
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]
}
Trong ví dụ dưới đây, PostsResolver cung cấp method getUser() trả về một reference chứa __typename cùng một số thuộc tính bổ sung mà ứng dụng của bạn cần để resolve reference đó, trong trường hợp này là id. __typename được GraphQL Gateway dùng để xác định microservice nào chịu trách nhiệm cho type User và lấy về instance tương ứng. “Users service” đã mô tả ở trên sẽ được gọi tới khi method resolveReference() được thực thi.
import { Query, Resolver, Parent, ResolveField } from '@nestjs/graphql';
import { PostsService } from './posts.service.js';
import type { Post } from './posts.interfaces.js';
@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 };
}
}
Cuối cùng, ta cần đăng ký GraphQLModule, tương tự như đã làm ở phần “Users service”.
import {
MercuriusFederationDriver,
MercuriusFederationDriverConfig,
} from '@nestjs/mercurius';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { PostsResolver } from './posts.resolver.js';
@Module({
imports: [
GraphQLModule.forRoot<MercuriusFederationDriverConfig>({
driver: MercuriusFederationDriver,
federationMetadata: true,
typePaths: ['**/*.graphql'],
}),
],
providers: [PostsResolver],
})
export class AppModule {}
Code first
Trước tiên, ta cần khai báo một class đại diện cho entity User. Mặc dù entity này thực sự sống ở một service khác, ta vẫn sẽ sử dụng nó (mở rộng định nghĩa của nó) ở đây. Lưu ý các directive @extends và @external.
import { Directive, ObjectType, Field, ID } from '@nestjs/graphql';
import { Post } from './post.entity.js';
@ObjectType()
@Directive('@extends')
@Directive('@key(fields: "id")')
export class User {
@Field(() => ID)
@Directive('@external')
id: number;
@Field(() => [Post])
posts?: Post[];
}
Bây giờ hãy tạo resolver tương ứng cho phần mở rộng của ta trên entity User, như sau:
import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
import { PostsService } from './posts.service.js';
import { Post } from './post.entity.js';
import { User } from './user.entity.js';
@Resolver(() => User)
export class UsersResolver {
constructor(private readonly postsService: PostsService) {}
@ResolveField(() => [Post])
public posts(@Parent() user: User): Post[] {
return this.postsService.forAuthor(user.id);
}
}
Ta cũng cần định nghĩa class entity Post:
import { Directive, Field, ID, Int, ObjectType } from '@nestjs/graphql';
import { User } from './user.entity.js';
@ObjectType()
@Directive('@key(fields: "id")')
export class Post {
@Field(() => ID)
id: number;
@Field()
title: string;
@Field(() => Int)
authorId: number;
@Field(() => User)
user?: User;
}
Và resolver của nó:
import { Query, Args, ResolveField, Resolver, Parent } from '@nestjs/graphql';
import { PostsService } from './posts.service.js';
import { Post } from './post.entity.js';
import { User } from './user.entity.js';
@Resolver(() => Post)
export class PostsResolver {
constructor(private readonly postsService: PostsService) {}
@Query(() => Post)
findPost(@Args('id') id: number): Post {
return this.postsService.findOne(id);
}
@Query(() => [Post])
getPosts(): Post[] {
return this.postsService.all();
}
@ResolveField(() => User)
user(@Parent() post: Post): any {
return { __typename: 'User', id: post.authorId };
}
}
Và cuối cùng, ghép mọi thứ lại trong một module. Lưu ý phần schema build options, nơi ta chỉ định rằng User là một orphaned type.
import {
MercuriusFederationDriver,
MercuriusFederationDriverConfig,
} from '@nestjs/mercurius';
import { Module } from '@nestjs/common';
import { User } from './user.entity.js';
import { PostsResolver } from './posts.resolvers.js';
import { UsersResolver } from './users.resolvers.js';
import { PostsService } from './posts.service.js'; // Not included in example
@Module({
imports: [
GraphQLModule.forRoot<MercuriusFederationDriverConfig>({
driver: MercuriusFederationDriver,
autoSchemaFile: true,
federationMetadata: true,
buildSchemaOptions: {
orphanedTypes: [User],
},
}),
],
providers: [PostsResolver, UsersResolver, PostsService],
})
export class AppModule {}
Ví dụ federated: Gateway
Gateway yêu cầu chỉ định một danh sách endpoint, và nó sẽ tự động dò các schema tương ứng. Do đó, phần triển khai gateway service sẽ giống nhau cho cả hai cách tiếp cận code first và schema first.
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 {}
Federation 2
Trích dẫn từ tài liệu Apollo, Federation 2 cải thiện trải nghiệm dev so với Apollo Federation nguyên bản (trong tài liệu này gọi là Federation 1), và vẫn tương thích ngược với phần lớn các supergraph cũ.
Cảnh báo Mercurius chưa hỗ trợ đầy đủ Federation 2. Bạn có thể xem danh sách các thư viện hỗ trợ Federation 2 tại đây.
Trong các phần dưới đây, ta sẽ nâng cấp ví dụ trước đó lên Federation 2.
Ví dụ federated: Users
Một thay đổi trong Federation 2 là entity không còn subgraph “gốc” (originating subgraph), nên ta không cần extend Query nữa. Để biết thêm chi tiết, xem chủ đề về entity trong tài liệu Apollo Federation 2.
Schema first
Ta chỉ cần bỏ từ khóa extend khỏi schema.
type User @key(fields: "id") {
id: ID!
name: String!
}
type Query {
getUser(id: ID!): User
}
Code first
Để dùng Federation 2, ta cần chỉ định phiên bản federation trong tùy chọn autoSchemaFile.
import {
ApolloFederationDriver,
ApolloFederationDriverConfig,
} from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { UsersResolver } from './users.resolver.js';
import { UsersService } from './users.service.js'; // Not included in this example
@Module({
imports: [
GraphQLModule.forRoot<ApolloFederationDriverConfig>({
driver: ApolloFederationDriver,
autoSchemaFile: {
federation: 2,
},
}),
],
providers: [UsersResolver, UsersService],
})
export class AppModule {}
Ví dụ federated: Posts
Với lý do tương tự như trên, ta không cần extend User và Query nữa.
Schema first
Ta chỉ cần bỏ directive extend và external khỏi schema
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 first
Vì ta không còn extend entity User nữa, ta có thể bỏ luôn directive extends và external khỏi User.
import { Directive, ObjectType, Field, ID } from '@nestjs/graphql';
import { Post } from './post.entity.js';
@ObjectType()
@Directive('@key(fields: "id")')
export class User {
@Field(() => ID)
id: number;
@Field(() => [Post])
posts?: Post[];
}
Ngoài ra, tương tự như ở Users service, ta cần chỉ định trong GraphQLModule để dùng Federation 2.
import {
ApolloFederationDriver,
ApolloFederationDriverConfig,
} from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { User } from './user.entity.js';
import { PostsResolver } from './posts.resolvers.js';
import { UsersResolver } from './users.resolvers.js';
import { PostsService } from './posts.service.js'; // Not included in example
@Module({
imports: [
GraphQLModule.forRoot<ApolloFederationDriverConfig>({
driver: ApolloFederationDriver,
autoSchemaFile: {
federation: 2,
},
buildSchemaOptions: {
orphanedTypes: [User],
},
}),
],
providers: [PostsResolver, UsersResolver, PostsService],
})
export class AppModule {}
Ghi chú người dịch Nếu bạn quen với việc tách monolith thành microservice REST, đây là dạng tương đương cho GraphQL: thay vì mỗi service tự expose REST endpoint và client phải tự gọi nhiều nơi rồi ráp dữ liệu (kiểu BFF thủ công), federation cho phép “khai báo” quyền sở hữu từng phần của một type dùng chung (như User) ngay trong schema của từng service, còn việc ráp dữ liệu là do gateway lo — tương tự cách một API Gateway kiểu Laravel/Express tổng hợp nhiều microservice, nhưng ở mức field thay vì mức endpoint. Cạm bẫy thường gặp: nếu bạn quên đánh dấu @key đúng field, hoặc field đó không tồn tại/không khớp giữa các subgraph, composition sẽ lỗi ngay ở bước gateway khởi động chứ không phải lúc runtime — nên luôn kiểm tra log gateway đầu tiên khi thêm subgraph mới.