You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,
});

解答

你的这种拆分+合并的思路是合理且符合可维护性要求的,但可以从几个维度优化,让它更接近最佳实践:

  1. 拆分粒度更清晰

    • 建议按业务领域拆分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
        }
      }
      
  2. 合并方式的优化

    • 你当前直接展开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的要求,同时避免手动操作的繁琐。
  3. 关于工具包的问题

    • graphql-tools的mergeResolvers默认返回数组,确实不符合Mercurius的要求,但你可以用它的mergeResolvers配合自定义逻辑把数组转成对象。不过自己写一个简单的合并函数反而更轻量,不需要额外依赖。

总结来说,你的核心思路是对的,只要调整合并后的结构保持标准的Resolver格式,并且合理控制拆分粒度,这种方式就是可维护的实践方案。

内容的提问来源于stack exchange,提问作者Jules Betfien

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 19:52:36