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

时间:2019-07-25 13:24:01

标签: graphql nestjs dataloader

我有一个非常常见的GraphQL模式,如下所示(伪代码):

class TestOne(object):
    def setup_method(self, method, some_fixture): # this would give argument error
        print "okay"

    def test_one(self):
        pass

因此,为了避免在请求多个Post { commentsPage(skip: Int, limit: Int) { total: Int items: [Comment] } } 对象时出现n + 1问题,我决定使用Facebook的Dataloader。

由于我正在使用Nest.JS 3层分层应用程序(Resolver-Service-Repository),因此我有疑问:

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

下面是我的服务方法示例,该方法返回了Post页(即从Comments属性解析器调用的此方法)。内部服务方法中,我使用了两种存储库方法(commentsPage#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等)创建单独的Dataloader实例,还是只用Dataloader包装整个服务方法(所以我的#find属性解析器只能与Dataloader一起使用,而不能与服务一起使用)?

1 个答案:

答案 0 :(得分:1)

免责声明:我不是Next.js的专家,但是我写了很多数据加载器,并且使用了自动生成的数据加载器。希望我能提供一些见识。

实际问题是什么?

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

数据加载器用于键值查找

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

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

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

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

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

SELECT * FROM user WHERE id = ?
-- Datalaoded
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%的连接来说可能是可以的。

要考虑的一些来源