- 后端
- Web框架
【免费下载链接】nest
A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀
本指南围绕 NestJS 仓库中 GraphQL Federation(Schema-First 模式)示例的 users-application 子服务展开,系统讲解如何用@nestjs/graphql与ApolloFederationDriver构建一个可被联邦网关合并的独立子服务:从 Schema 定义、@key实体标记、@ResolveReference引用解析到模块装配与启动运行。读完你既能照抄一份可运行的联邦子服务骨架,也能理解其在多服务架构中的角色边界。
一、示例在仓库中的位置与整体结构
本示例位于 sample/32-graphql-federation-schema-first/users-application 目录,是 GraphQL Federation(Schema-First)示例套件的用户子服务部分。整个 32 号示例由三个应用组成:
gateway:Apollo Gateway,负责合并各子服务的 Schema 并对外统一提供查询入口;users-application:本文主角,提供User实体与getUser查询;posts-application:另一子服务,通过联邦引用(reference)解析User的关联数据。
users-application 的文件布局如下:
users-application/ ├── src/ │ ├── app.module.ts │ ├── main.ts │ └── users/ │ ├── models/user.model.ts │ ├── users.graphql │ ├── users.module.ts │ ├── users.resolver.ts │ ├── users.resolver.spec.ts │ ├── users.service.ts │ └── users.service.spec.ts ├── nest-cli.json ├── package.json ├── tsconfig.json └── vitest.config.mts子服务本身是标准 NestJS 应用:入口、根模块、业务模块一应俱全,唯一特别之处在于根模块中接入的是GraphQLModule.forRoot且驱动为ApolloFederationDriver(见 users.module.ts)。
二、依赖与脚本:搭建联邦子服务需要什么
package.json 是本子服务的依赖清单,其中与联邦直接相关的核心依赖为:
@nestjs/graphql(14.x):NestJS GraphQL 集成层;@nestjs/apollo(14.x):提供ApolloFederationDriver与ApolloFederationDriverConfig;@apollo/subgraph(2.15.x):Apollo 子服务构建工具;@apollo/gateway(2.14.x):网关依赖(此示例中 gateway 与子服务共用同一份依赖约定);graphql(17.x)与graphql-tools(9.x):Schema 解析与工具库;reflect-metadata:Nest 依赖注入与装饰器元数据所需。
项目脚本与 NestJS 标准模板一致,但测试统一走 Vitest:
# 安装依赖 $ npm install # 开发模式 $ npm run start # 监听模式 $ npm run start:dev # 生产模式(先构建再启动) $ npm run start:prod # 单元测试(Vitest) $ npm run testtype: "module"表明该子服务以 ESM 方式运行,这也是main.ts中导入路径带.js后缀的原因(见下文)。vitest.config.mts将测试范围限定为src/**/*.spec.ts并开启全局断言。
三、模块装配:ApolloFederationDriver 的接入方式
3.1 根模块
app.module.ts 只是简单导入UsersModule,不注册任何控制器或全局 provider:
import { Module } from '@nestjs/common'; import { UsersModule } from './users/users.module.js'; @Module({ imports: [UsersModule], controllers: [], providers: [], }) export class AppModule {}3.2 业务模块:GraphQL 配置
真正的联邦配置在 users.module.ts:
import { ApolloFederationDriver, ApolloFederationDriverConfig, } from '@nestjs/apollo'; import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; import { UsersResolver } from './users.resolver.js'; import { UsersService } from './users.service.js'; @Module({ providers: [UsersResolver, UsersService], imports: [ GraphQLModule.forRoot<ApolloFederationDriverConfig>({ driver: ApolloFederationDriver, typePaths: ['**/*.graphql'], }), ], }) export class UsersModule {}关键点:
driver: ApolloFederationDriver将 GraphQL 执行引擎切换为 Apollo Federation 专用驱动,子服务因此会暴露联邦必需的_service(SDL 查询)与_entities(实体解析)端点;typePaths: ['**/*.graphql']是 Schema-First 的核心配置:NestJS 会按该 glob 自动扫描并合并所有.graphql文件作为 SDL。这正是"Schema-First"与 Code-First(装饰器直接生成 Schema)的区别所在。
四、Schema-First 的 Schema 定义:联邦实体声明
users.graphql 是子服务对外发布的 SDL:
type User @key(fields: "id") { id: ID! name: String! } extend type Query { getUser(id: ID!): User }逐行解读:
@key(fields: "id"):联邦规范的核心指令,声明User类型在本子服务中的主键为id。网关将依据该键把不同子服务中同类型的实体合并为一个完整对象;type User定义了实体字段id(ID!,非空)与name(String!,非空);extend type Query:子服务不拥有全局Query的完整定义,因此用extend把自己的查询getUser(id: ID!): User追加到合并后的根类型上。
Code-First 等价实现对照
本示例同时提供了 Code-First 的联邦版本(见 sample/31-graphql-federation-code-first)。在 Schema-First 中@key写在.graphql文件里,而 Code-First 则用装饰器表达:user.model.ts 展示了如何在 NestJS 中把 SDL 映射为 TypeScript 类:
import { Directive, Field, ID, ObjectType } from '@nestjs/graphql'; @ObjectType() @Directive('@key(fields: "id")') export class User { @Field((type) => ID) id: number; @Field() name: string; }@Directive('@key(fields: "id")')与.graphql文件中的@key(fields: "id")一一对应,说明无论哪种模式,联邦语义(键与引用解析)都是通过同一套指令体系表达的。该模型文件在本 Schema-First 示例中主要供 Resolver 与 Service 引用类型,Schema 本体仍以 SDL 为准。
五、Resolver:普通查询与联邦引用解析
users.resolver.ts 是子服务的解析层,同时承担两类职责:
import { Args, ID, Query, Resolver, ResolveReference } from '@nestjs/graphql'; import { UsersService } from './users.service.js'; @Resolver('User') export class UsersResolver { constructor(private usersService: UsersService) {} @Query() getUser(@Args({ name: 'id', type: () => ID }) id: number) { return this.usersService.findById(id); } @ResolveReference() resolveReference(reference: { __typename: string; id: number }) { return this.usersService.findById(reference.id); } }5.1 普通查询:getUser
@Query()对应 SDL 中的getUser(id: ID!): User,参数id声明为ID类型。@Resolver('User')将 Resolver 与User类型绑定,使方法返回值具备联邦实体语义。
5.2 联邦引用解析:resolveReference
@ResolveReference()是联邦子服务的关键钩子:当网关跨子服务合并实体时,会向本子服务发送一个"引用对象"(包含__typename与主键字段id),本方法据此加载并返回完整实体。这里接收的reference结构为{ __typename: string; id: number },与联邦规范中_entities解析器收到的参数一致。
可以推断,网关在posts-application中遇到对User的引用(例如帖子关联作者)时,正是调用这里的方法把id解析回{ id, name }完整对象——这是联邦"单实体、多子服务分工"的数据流核心。
六、Service:数据存取层
users.service.ts 使用内存数组模拟数据源,便于示例零依赖运行:
import { Injectable } from '@nestjs/common'; @Injectable() export class UsersService { private users = [ { id: 1, name: 'John Doe' }, { id: 2, name: 'Richard Roe' }, ]; findById(id: number) { return this.users.find((user) => user.id === Number(id)); } }findById同时服务于getUser查询与resolveReference引用解析,一条数据路径两处复用;Number(id)将字符串/数字形式的ID统一转换为数字比较,规避 GraphQLID标量序列化带来的类型差异;- 生产环境中该内存数组可替换为 TypeORM、Prisma 或远程 API,Resolver/Service 的分层使替换成本极低。
七、启动入口与运行验证
main.ts 与普通 NestJS 应用无异:
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module.js'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } await bootstrap();注意两点:
- 顶层 await:因
package.json声明"type": "module",文件采用 ESM 语法,bootstrap()直接以顶层await执行,无需包裹在void bootstrap()中; - 导入后缀
.js:ESM 下 TypeScript 编译产物为.js,导入路径必须显式写.js才能在 Node 侧正确解析。
运行:
$ cd sample/32-graphql-federation-schema-first/users-application $ npm install $ npm run start启动后服务监听 3000 端口。若配合同一示例中的gateway应用(默认端口 4000)一起启动,网关会通过子服务的联邦端点拉取 SDL 并合并 Schema;直接访问子服务的 GraphQL 端点,可用如下查询验证getUser:
query { getUser(id: 1) { id name } }预期返回{ "data": { "getUser": { "id": "1", "name": "John Doe" } } }。
八、测试:Resolver 与 Service 的单元验证
仓库为子服务配备了两份 Vitest 单元测试(配置见 vitest.config.mts,将include限定为src/**/*.spec.ts):
- users.resolver.spec.ts:mock
UsersService后验证getUser与resolveReference的返回; - users.service.spec.ts:验证
findById的数据查找逻辑。
运行测试:
$ npm run test # vitest run $ npm run test:watch # 监听模式 $ npm run test:cov # 覆盖率九、小结:Schema-First 联邦子服务的四要素
综合以上源码,一个可被联邦网关合并的 NestJS 子服务由四要素构成:
- 驱动接入:
GraphQLModule.forRoot使用ApolloFederationDriver(users.module.ts); - SDL 声明:
.graphql文件定义实体并以@key(fields: "id")标注主键,用extend type Query追加查询(users.graphql); - 引用解析:
@ResolveReference()方法根据网关下发的引用对象还原完整实体(users.resolver.ts); - 数据服务:将查询与解析统一委托给 Service 层,保持数据源可替换(users.service.ts)。
如需对比装饰器风格的联邦写法,可参考同仓库的 sample/31-graphql-federation-code-first;若要查看网关如何合并本子服务的 Schema,参见 32-graphql-federation-schema-first/gateway 目录及其 README.md。
- 后端
- Web框架
【免费下载链接】nest
A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀
相关推荐
YouTube.js 的 JsMatchers:基于 ESTree AST 的 n/sig 解密函数提取匹配器深度解析
YouTube.js 的 JsMatchers:基于 ESTree AST 的 n/sig 解密函数提取匹配器深度解析 导读 JsMatchers 是 YouT
后端Web框架使用 Java 与 graphql-java 构建 GraphQL 服务器:从 schema-first 到 code-first 的完整指南
使用 Java 与 graphql java 构建 GraphQL 服务器:从 schema first 到 code first 的完整指南 本指南以 how
GraphQL Java 实战:从 Schema-First 到 Code-First,用 graphql-spqr 从业务代码直接生成 Schema
GraphQL Java 实战:从 Schema First 到 Code First,用 graphql spqr 从业务代码直接生成 Schema 本文围绕
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考