GraphQL Resolver拆分后用扩展运算符合并是否为最佳实践?
关于Mercurius中拆分并合并GraphQL Resolver的最佳实践疑问
我查阅的诸多教程里,GraphQL Resolver最常见的写法如下:
export const rootResolvers: IResolvers = { Query: { getUserById: async (root, { id }, context, info) => { return await getUserController(id); }, getUsers: async (root, args, context, info) => { return await getUsersController(); }, }, Mutation: { createUser: async (_, args, {userData}, info) => { return registerUserController(userData); }, updateUser: async (_, args, {userData}, info) => { return updateUserController(userData); }, }, }
但我希望直接在Resolver里编写业务逻辑而非依赖控制器,而且每个Resolver的代码量会比较大,所以我把根Resolver拆成了下面的形式:
const getUserResolver = { Query: { getUsers: async (root, args, context, info) => { return await getUsersController(); }, } } ... const userCreateResolver = { Mutation: { createUser: async (_, args, {userData}, info) => { return registerUserController(userData); }, } } const userUpdateResolver = { Mutation: { updateUser: async (_, args, {userData}, info) => { return registerUserController(userData); }, } }
现在我用扩展运算符把所有Resolver合并成一个对象:
const rootResolver: IResolvers = { ...getUserResolver.Query, ...userCreateResolver.Mutation, ...userUpdateResolver.Mutation }
我想知道这种方式算不算最佳实践。另外我试过用lodash和graphql-tools/merge包,但因为@mercuriusjs要求Resolver必须是对象而非数组,导致报错。以下是我注册Mercurius的代码:
// Register mercurius to the server. app.register(mercurius, { schema: buildFederationSchema(rootTypeDefs, { isGateway: false, }), resolvers: rootResolver, <-------------------- context: buildContext, graphiql: true, queryDepth: 7, });
解答
你的这种拆分+合并的思路是合理且符合可维护性要求的,但可以从几个维度优化,让它更接近最佳实践:
拆分粒度更清晰
- 建议按业务领域拆分Resolver文件,比如把用户相关的Query和Mutation都放在
user.resolvers.ts里,而不是拆成单个操作的文件。这样既避免文件过多,又能保持业务逻辑的内聚性。例如:// user.resolvers.ts export const userResolvers = { Query: { getUsers: async (root, args, context, info) => { // 这里直接写业务逻辑 }, getUserById: async (root, { id }, context, info) => { // 业务逻辑 } }, Mutation: { createUser: async (_, args, {userData}, info) => { // 业务逻辑 }, updateUser: async (_, args, {userData}, info) => { // 业务逻辑 } } } - 这样拆分后,合并时只需要按顶层的Query/Mutation来合并,结构更清晰:
import { userResolvers } from './user.resolvers'; import { postResolvers } from './post.resolvers'; const rootResolver: IResolvers = { Query: { ...userResolvers.Query, ...postResolvers.Query }, Mutation: { ...userResolvers.Mutation, ...postResolvers.Mutation } }
- 建议按业务领域拆分Resolver文件,比如把用户相关的Query和Mutation都放在
合并方式的优化
- 你当前直接展开Query/Mutation到根对象的写法,其实会让
rootResolver的结构偏离标准的GraphQL Resolver结构(标准结构是顶层包含Query、Mutation等字段)。虽然Mercurius可能能识别,但长期来看会降低可读性,建议保持标准结构,像上面那样按Query和Mutation分别合并。 - 如果你坚持要按单个操作拆分,也可以写一个简单的合并工具函数,避免手动重复展开:
function mergeResolvers(resolversArray: IResolvers[]) { return resolversArray.reduce((merged, resolver) => { if (resolver.Query) { merged.Query = { ...merged.Query, ...resolver.Query }; } if (resolver.Mutation) { merged.Mutation = { ...merged.Mutation, ...resolver.Mutation }; } // 支持其他类型比如Subscription if (resolver.Subscription) { merged.Subscription = { ...merged.Subscription, ...resolver.Subscription }; } return merged; }, { Query: {}, Mutation: {}, Subscription: {} } as IResolvers); } // 使用方式 const rootResolver = mergeResolvers([ getUserResolver, userCreateResolver, userUpdateResolver ]); - 这个工具函数可以帮你自动合并所有Resolver的Query/Mutation,而且输出的是符合标准结构的对象,适配Mercurius的要求,同时避免手动操作的繁琐。
- 你当前直接展开Query/Mutation到根对象的写法,其实会让
关于工具包的问题
- graphql-tools的
mergeResolvers默认返回数组,确实不符合Mercurius的要求,但你可以用它的mergeResolvers配合自定义逻辑把数组转成对象。不过自己写一个简单的合并函数反而更轻量,不需要额外依赖。
- graphql-tools的
总结来说,你的核心思路是对的,只要调整合并后的结构保持标准的Resolver格式,并且合理控制拆分粒度,这种方式就是可维护的实践方案。
内容的提问来源于stack exchange,提问作者Jules Betfien
相关产品推荐
相关产品推荐

