graphql - GraphQL DataLoader 应该将请求包装到数据库还是将请求包装到服务方法?

标签 graphql nestjs dataloader

我有非常常见的 GraphQL 架构,如下所示(伪代码):

Post {
  commentsPage(skip: Int, limit: Int) {
    total: Int
    items: [Comment]
  }
}

因此,为了避免在请求多个 Post 对象时出现 n+1 问题,我决定使用 Facebook 的 Dataloader。

由于我正在开发 Nest.JS 3 层分层应用程序(Resolver-Service-Repository),所以我有一个问题:

我应该使用 DataLoader 包装我的存储库方法还是应该使用 Dataloder 包装我的服务方法?

下面是我的服务方法的示例,该方法返回 Comments 页面(即从 commentsPage 属性解析器调用此方法)。在服务方法内部,我使用 2 个存储库方法(#count#find):

@Injectable()
export class CommentsService {
    constructor(
        private readonly repository: CommentsRepository,
    ) {}

    async getCommentsPage(postId, dataStart, dateEnd, skip, limit): PaginatedComments {
        const counts = await this.repository.getCount(postId, dateStart, dateEnd);
        const itemsDocs = await this.repository.find(postId, dateStart, dateEnd, skip, limit);
        const items = this.mapDbResultToGraphQlType(itemsDocs);
        return new PaginatedComments(total, items)
    }
}

那么我应该为每个存储库方法(#count#find 等)创建 Dataloader 的单独实例,还是应该用 Dataloader 包装整个服务方法(所以我的 commentsPage 属性解析器只能与 Dataloader 一起使用,而不是与服务一起使用)?

最佳答案

免责声明:我不是 Nest.js 方面的专家,但我编写了很多数据加载器,并使用过自动生成的数据加载器。尽管如此,我还是希望能够提供一些见解。

实际问题是什么?

虽然您的问题似乎是一个相对简单的“非此即彼”问题,但它可能比这要困难得多。我认为实际的问题如下:是否对特定字段使用数据加载器模式需要根据每个字段来决定。另一方面,存储库+服务模式试图通过公开抽象且强大的数据访问方式来抽象化此决策。一种解决方法是简单地“数据加载器化”服务的每个方法。不幸的是,在实践中这并不真正可行。让我们探讨一下原因!

数据加载器是为键值查找而设计的

Dataloader 提供了一个 Promise 缓存来减少对数据库的重复调用。要使此缓存正常工作,所有请求都需要是简单的键值查找(例如 userByIdLoaderpostsByUserIdLoader)。这很快就变得不够了,就像在您的一个示例中,您对存储库的请求有很多参数:

this.repository.find(postId, dateStart, dateEnd, skip, limit);

当然,从技术上讲,您可以将 { postId, dateStart, dateEnd,skip, limit } 设置为您的 key ,然后以某种方式对内容进行哈希处理以生成唯一的 key 。

编写 Dataloader 查询比普通查询困难一个数量级

当您实现数据加载器查询时,它现在突然必须处理初始查询所需的输入列表。这是一个简单的 SQL 示例:

SELECT * FROM user WHERE id = ?
-- Dataloaded
SELECT * FROM user WHERE id IN ?

现在是上面的存储库示例:

SELECT * FROM comment WHERE post_id = ? AND date < ? AND date > ? OFFSET ? LIMIT ?
-- Dataloaded
???

我有时会编写适用于两个参数的查询,它们已经成为非常困难的问题。这就是为什么大多数数据加载器只是通过 id 查找加载This tread on twitter讨论 GraphQL API 应如何仅公开可有效查询的内容。如果您使用强过滤器方法创建服务方法,即使您的 GraphQL API 不公开这些过滤器,也会遇到同样的问题。

好的,那么解决方案是什么?

据我了解,Facebook 所做的第一件事就是非常紧密地匹配字段和服务方法。你也可以这样做。这样您就可以在服务方法中决定是否要使用数据加载器。例如,我不在根查询中使用数据加载器(例如 { getPosts(filter: {createdBefore: "...", user: 234 }) { .. }),但在类型的子字段中出现在列表中{ getAllPosts { comments { ... } }。根查询不会在循环中执行,因此不会遇到 n+1 问题。

您的存储库现在公开了可以“有效查询”的内容(如 Lee 的推文中所示),例如外键/主键查找过滤查找所有查询。然后,该服务可以将例如关键查找包装在数据加载器中。通常我最终会在业务逻辑中过滤小列表。我认为这对于小型应用程序来说非常好,但当您扩展时可能会出现问题。当您使用 connectionFromArray 函数时,JavaScript 的 GraphQL Relay 助手会执行类似的操作。分页不是在数据库级别完成的,这对于 90% 的连接来说可能没问题。

一些需要考虑的来源

关于graphql - GraphQL DataLoader 应该将请求包装到数据库还是将请求包装到服务方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57203022/

相关文章:

javascript - 如何在 gatsby 的 graphql 查询中使用正则表达式

python - 如何解决错误 405 方法不允许,对于 django graphql 服务器和前端 react axios

nestjs - 如何在 Nestjs/TypeORM 应用程序中测试自定义 Repository

jestjs - NestJS开 Jest 错误: TypeError: Cannot read properties of undefined (reading '[any variable from required config]' )

node.js - 如何让等待 Action 完成,然后收到新消息?

python - 将 pytorch 数据加载器加载到 GPU

javascript - 尝试使用 react-apollo-hooks 在函数中调用 useQuery

node.js - NodeJS : Get File, 不是缓冲区

pytorch - 如何使用 PyTorch DataLoader 进行强化学习?

PyTorch:在 random_split 之后对训练数据应用数据增强