基于Nestjs+Apollo的GraphQL联邦:子图用户存在性验证实现
在GraphQL联邦中实现创建应用前的用户存在性验证
针对你的场景,有三种可行的实现方式,结合NestJS + Apollo GraphQL联邦的特性,具体如下:
方法一:在Application子图的Resolver中直接调用USER子图验证
在创建应用的Resolver逻辑里,先发起请求到USER子图的查询接口,验证传入的userId对应的用户是否存在,再执行创建操作。
步骤与代码示例:
- 注入
HttpService用于发起HTTP请求到USER子图 - 在
createApplication方法中构造查询USER子图的GraphQL语句 - 根据查询结果判断用户是否存在,不存在则抛出错误
import { Resolver, Mutation, Args } from '@nestjs/graphql'; import { HttpService } from '@nestjs/axios'; import { firstValueFrom } from 'rxjs'; import { ForbiddenException } from '@nestjs/common'; import { ApplicationService } from './application.service'; import { CreateApplicationInput, Application } from './dto'; @Resolver() export class ApplicationResolver { constructor( private readonly appService: ApplicationService, private readonly httpService: HttpService ) {} @Mutation(() => Application) async createApplication( @Args('createApplicationInput') input: CreateApplicationInput ) { // 构造查询USER子图的GraphQL请求 const userValidationQuery = ` query ValidateUser($id: Int!) { user(id: $id) { id } } `; try { const response = await firstValueFrom( this.httpService.post('http://user-subgraph的实际地址/graphql', { query: userValidationQuery, variables: { id: input.userId } }) ); // 若查询不到用户,抛出错误 if (!response.data.data.user) { throw new ForbiddenException('指定的用户不存在'); } } catch (err) { throw new ForbiddenException('用户存在性验证失败,请稍后重试'); } // 验证通过,执行创建应用逻辑 return this.appService.create(input); } }
方法二:在联邦网关层添加自定义验证指令
将验证逻辑集中在网关层,通过自定义GraphQL指令拦截createApplication请求,先验证用户存在性,再转发请求到Application子图。
步骤与代码示例:
- 在网关定义自定义指令
@validateUserExists - 实现指令的验证逻辑,调用USER子图查询用户
- 在Application子图的mutation上添加该指令
网关侧指令实现:
import { ApolloServerPlugin, GraphQLRequestContext } from '@apollo/server'; import { HttpService } from '@nestjs/axios'; import { firstValueFrom } from 'rxjs'; import { Injectable } from '@nestjs/common'; @Injectable() export class ValidateUserPlugin implements ApolloServerPlugin { constructor(private readonly httpService: HttpService) {} async requestDidStart() { return { async didResolveOperation(context: GraphQLRequestContext) { // 定位当前mutation操作及验证指令 const opDef = context.document.definitions.find( def => def.kind === 'OperationDefinition' && def.operation === 'mutation' ) as any; if (!opDef) return; const mutationField = opDef.selectionSet.selections[0]; const validateDirective = mutationField.directives?.find( dir => dir.name.value === 'validateUserExists' ); if (validateDirective) { // 提取需要验证的字段(这里是userId) const targetField = validateDirective.arguments.find( arg => arg.name.value === 'field' ).value.value; const userId = context.request.variables.createApplicationInput[targetField]; // 调用USER子图验证用户 const userQuery = ` query GetUser($id: Int!) { user(id: $id) { id } } `; const response = await firstValueFrom( this.httpService.post('http://user-subgraph的实际地址/graphql', { query: userQuery, variables: { id: userId } }) ); if (!response.data.data.user) { throw new Error('指定的用户不存在'); } } } }; } }
Application子图的Schema修改:
directive @validateUserExists(field: String!) on FIELD_DEFINITION type Mutation { createApplication( createApplicationInput: CreateApplicationInput! ): Application! @validateUserExists(field: "userId") } input CreateApplicationInput { name: String! userId: Int! }
方法三:利用联邦实体引用机制验证
通过在Application子图中定义User实体的引用Resolver,尝试加载指定userId的用户实体,加载失败则视为用户不存在。
步骤与代码示例:
- 在Application子图中定义User实体的联邦类型
- 实现User实体的引用Resolver,调用USER子图查询用户
- 在创建应用的Resolver中尝试加载User实体
Application子图的Schema定义:
type User @key(fields: "id") { id: Int! } type Mutation { createApplication( createApplicationInput: CreateApplicationInput! ): Application! } input CreateApplicationInput { name: String! userId: Int! }
User实体的引用Resolver:
import { Resolver, ResolveReference } from '@nestjs/graphql'; import { HttpService } from '@nestjs/axios'; import { firstValueFrom } from 'rxjs'; @Resolver(() => User) export class UserReferenceResolver { constructor(private readonly httpService: HttpService) {} @ResolveReference() async resolveReference(reference: { __typename: string; id: number }) { const response = await firstValueFrom( this.httpService.post('http://user-subgraph的实际地址/graphql', { query: ` query GetUser($id: Int!) { user(id: $id) { id } } `, variables: { id: reference.id } }) ); return response.data.data.user; } }
修改createApplication的Resolver:
@Mutation(() => Application) async createApplication( @Args('createApplicationInput') input: CreateApplicationInput, @Context() context: any ) { try { // 通过联邦加载器尝试获取用户实体 const user = await context.loaders.user.load(input.userId); if (!user) { throw new ForbiddenException('指定的用户不存在'); } } catch (err) { throw new ForbiddenException('用户存在性验证失败'); } return this.appService.create(input); }
三种方式对比
- 方法一:实现简单,逻辑闭环在子图内,适合小型项目;缺点是硬编码USER子图地址,扩展性一般。
- 方法二:验证逻辑集中在网关,子图无需关心验证规则,适合多子图统一验证的场景;缺点是增加网关复杂度。
- 方法三:符合GraphQL联邦的设计理念,复用实体引用机制,适合已有跨子图实体交互的项目;缺点是需要额外定义实体和Resolver。
内容的提问来源于stack exchange,提问作者Midhun Murali
相关产品推荐
相关产品推荐

