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 要么是一种形状,要么是另一种形状。即使你没有,如果你不小心,你最终可能会抛出“无法读取 属性 未定义的用户”错误而不是“未经授权”错误。您最好创建反映相同对象形状但只是抛出未授权的“虚拟服务”。
我有一个特定的用例,其中用户的数据源是有条件的 - 例如,基于为每个特定用户保存在数据库中的数据源。
这也意味着每个数据源对每个用户都有唯一的凭据,这对 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 要么是一种形状,要么是另一种形状。即使你没有,如果你不小心,你最终可能会抛出“无法读取 属性 未定义的用户”错误而不是“未经授权”错误。您最好创建反映相同对象形状但只是抛出未授权的“虚拟服务”。