Apollo 条件数据源和初始化生命周期

Apollo conditional data sources & initialization lifecycle

我有一个特定的用例,其中用户的数据源是有条件的 - 例如,基于为每个特定用户保存在数据库中的数据源。

这也意味着每个数据源对每个用户都有唯一的凭据,这对 RESTDataSource 很好,因为我可以使用 willSendRequest 在每个请求之前设置身份验证 headers。

但是,我有具有专有客户端的自定义数据源(例如 JSForce for Salesforce)- 并且它们有自己的获取机制。

截至目前 - 我有一个自定义的 t运行sformer 指令,它从数据库中获取标记并将其添加到上下文中 - 但是,指令是 运行 在 dataSource.initialize() 方法 - 这样我就不能在那里使用凭据,因为上下文仍然没有它。

我也不想为每个用户初始化所有数据源,即使他没有在此请求中使用所述数据源 - 但 dataSources() 函数不接受任何参数并且不与上下文相关。

底线是 - 是否可以根据 Express 请求有条件地传递数据源?什么时候是将令牌和凭据传递给数据源的合适时间?也许添加我自己的自定义初始化函数并从指令中调用它?

所以你有选择。这里有 2 个选择:

1。只需添加您的数据源

如果您只是初始化所有数据源,它可以在内部检查用户是否有访问权限。您可以有一个 getClient 函数在客户端解析或抛出 UnauthorizedError,具体取决于。

2。不要只添加你的数据源

因此,如果您真的根本不想初始化数据源,您完全可以通过自己添加“数据源”来完成此操作,就像 Apollo 所做的那样。

const server = new ApolloServer({
  // this example uses apollo-server-express
  context: async ({ req, res }) => {
    const accessToken = req.headers?.authorization?.split(' ')[1] || ''
    const user = accessToken && buildUser(accessToken)

    const context = { user }

    // You can't use the name "dataSources" in your config because ApolloServer will puke, so I called them "services"
    await addServices(context)
    return context
  }
})

const addServices = async (context) => {
  const { user } = context;
  const services = {
    userAPI: new UserAPI(),
    postAPI: new PostAPI(),
  }

  if (user.isAdmin) {
    services.adminAPI = new AdminAPI()
  }

  const initializers = [];
  for (const service of Object.values(services)) {
    if (service.initialize) {
      initializers.push(
        service.initialize({
          context,
          cache: null, // or add your own cache
        })
      );
    }
  }

  await Promise.all(initializers);

  /**
   * this is where you have to deviate from Apollo.
   * You can't use the name "dataSources" in your config because ApolloServer will puke
   * with the error 'Please use the dataSources config option instead of putting dataSources on the context yourself.'
   */
  context.services = services;
}

一些注意事项:

1。您不能称它们为“dataSources”

如果你return一个属性在你的上下文对象上调用“dataSources”,Apollo 不会很喜欢它[meaning it throws an Error]。在我的示例中,我使用了名称“services”,但您可以随心所欲...除了“dataSources”。

使用上面的代码,在您的解析器中,只需引用 context.services.whatever 即可。

2。这就是 Apollo 所做的

此模式直接复制自 Apollo 已经为数据源所做的工作 [source]

3。我建议您仍然将它们视为 DataSources

我建议您坚持使用 DataSources 模式,并且您的“服务”都扩展了 DataSource。对于所有相关人员来说,这将变得更加容易。

4。类型安全

如果您使用的是 TypeScript 或其他东西,您将失去一些类型安全性,因为 context.services 要么是一种形状,要么是另一种形状。即使你没有,如果你不小心,你最终可能会抛出“无法读取 属性 未定义的用户”错误而不是“未经授权”错误。您最好创建反映相同对象形状但只是抛出未授权的“虚拟服务”。