解析器实现与数据源连接


解析器实现与数据源连接

解析器的四种参数

每个解析器函数接收四个参数:parent、args、context、info。它们各自承载不同的信息,理解它们的角色是写好解析器的前提。

// 完整的解析器签名 const fieldNameResolver = ( parent: any, // 父解析器的返回值 args: any, // 查询中传递的参数 context: Context, // 跨请求共享的上下文 info: GraphQLResolveInfo // 字段元信息 ) => any; ​

parent 是解析器链的核心机制。根字段(Query、Mutation下的字段)的parent通常是rootValue(通常是null或空对象)。子字段(User.name、Post.author)的parent是对应父解析器的返回值。这就是为什么 Post.author 的解析器可以直接从 parent.authorId 取到外键。

args 是查询中传递给该字段的参数。比如 user(id: "1001") 中,args.id 就是 "1001"。

context 是一个跨解析器共享的对象,在单个请求的生命周期内传递。典型用法:

interface Context { db: { users: UserRepository; posts: PostRepository; comments: CommentRepository; }; user: { id: string; role: string } | null; // 认证信息 dataSources: DataSources; // Apollo DataSource 实例 pubSub: PubSub; // 发布/订阅 logger: Logger; } ​

context 在请求开始时创建,所有解析器共享同一个context实例。这是传递数据库连接、认证信息、DataLoader实例的标准方式。

info 包含当前字段的Schema信息和AST节点引用,通常在编写通用中间件或性能分析工具时用到。日常开发中很少直接使用。

连接数据库:直接查询

最直接的解析器实现是直接查数据库:

const resolvers = { Query: { user: async (_, { id }, context) => { const row = await context.db.query( 'SELECT id, name, email, avatar_url FROM users WHERE id = $1', [id] ); if (!row) return null; return { id: row.id, name: row.name, email: row.email, avatarUrl: row.avatar_url // 驼峰转换 }; } }, User: { posts: async (parent, { limit }, context) => { return context.db.query( 'SELECT id, title, created_at FROM posts WHERE author_id = $1 ORDER BY created_at DESC LIMIT $2', [parent.id, limit] ); } } }; ​

这种实现简单直接,但有几个问题:数据库字段名到GraphQL字段名的转换需要手动处理(avatar_url → avatarUrl),而且每个解析器各自管理数据库连接,没有统一的错误处理和日志。

Apollo DataSource:数据访问层的标准化

Apollo提供了DataSource抽象,把数据访问逻辑从解析器中分离出来:

import { RESTDataSource } from '@apollo/datasource-rest'; class UserAPI extends RESTDataSource { override baseURL = 'https://api.internal.example.com/'; async getUser(id: string): Promise<User> { return this.get(`users/${id}`); } async getUserPosts(userId: string, limit = 10): Promise<Post[]> { return this.get(`users/${userId}/posts`, { args: { limit } }); } } class PostAPI extends RESTDataSource { override baseURL = 'https://api.internal.example.com/'; async getPost(id: string): Promise<Post> { return this.get(`posts/${id}`); } async getPostComments(postId: string, limit = 10): Promise<Comment[]> { return this.get(`posts/${postId}/comments`, { args: { limit } }); } } // 在context中注入 const server = new ApolloServer({ typeDefs, resolvers, dataSources: () => ({ userAPI: new UserAPI(), postAPI: new PostAPI() }) }); // 解析器中使用DataSource const resolvers = { Query: { user: (_, { id }, { dataSources }) => dataSources.userAPI.getUser(id), }, User: { posts: (parent, { limit }, { dataSources }) => dataSources.userAPI.getUserPosts(parent.id, limit), }, Post: { author: (parent, _, { dataSources }) => dataSources.userAPI.getUser(parent.authorId), comments: (parent, { limit }, { dataSources }) => dataSources.postAPI.getPostComments(parent.id, limit), } }; ​

DataSource的好处是:自动处理HTTP请求的缓存(基于TTL)、取消(请求超时自动取消)、去重(并发请求同一URL只发一次)以及错误重试。它把网络请求的复杂性封装起来,让解析器保持简洁。

混合数据源场景

一个GraphQL服务可能同时连接多种数据源。这正是GraphQL的优势所在——它天然适合做数据聚合层。

const resolvers = { Query: { dashboard: async (_, __, context) => { // 用户信息从PostgreSQL取 const user = await context.db.users.findById(context.userId); // 订单统计从Redis缓存取 const orderStats = await context.redis.get(`stats:${user.id}`); // 推荐商品从Python微服务取(gRPC) const recommendations = await context.recommenderClient.getRecommendations(user.id); // 最近活动从Elasticsearch取 const activities = await context.es.search({ index: 'activities', body: { query: { term: { userId: user.id } }, sort: [{ timestamp: 'desc' }], size: 5 } }); return { user, orderStats, recommendations, activities }; } } }; ​

一个查询,四种数据源,一条返回。前端不需要关心数据从哪来——它只关心GraphQL查询返回的JSON结构。这就是GraphQL作为BFF(Backend for Frontend)层或API网关的价值。

解析器的性能意识

写解析器时需要时刻注意性能,尤其是:

避免在解析器中做重复查询。如果多个子字段都需要用户信息,不要各自查一次数据库——用DataLoader批处理。

避免查询不需要的字段。GraphQL解析器返回完整对象,引擎会从中选取查询指定的字段。但如果你在解析器中用了 SELECT * 或返回了超大的嵌套对象,即使查询只需要 name 一个字段,数据库和序列化的开销已经产生了。

善用context中的共享资源。数据库连接池、Redis客户端、DataLoader实例都应该是context的一部分,而不是每个解析器各自创建。

图:多数据源解析器架构

图:多数据源解析器架构

解析器的设计原则是:小而专注。每个解析器只做一件事——为它的字段提供数据。复杂的业务逻辑放在服务层,解析器只是调用服务层并返回结果。

作者与出处
原作者:灏天文库
来源:平台策划编纂
整理人: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U