我有一个非常常见的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一起使用,而不能与服务一起使用)?
答案 0 :(得分:1)
免责声明:我不是Next.js的专家,但是我写了很多数据加载器,并且使用了自动生成的数据加载器。希望我能提供一些见识。
尽管您的问题似乎是一个相对简单的问题,或者问题可能比这困难得多。我认为实际的问题如下:是否需要针对特定字段使用数据加载器模式?另一方面,存储库+服务模式试图通过公开抽象且强大的数据访问方式来抽象化此决策。一种解决方法是简单地“数据装载器化”服务的每种方法。不幸的是,在实践中这并不是真正可行的。让我们探究原因!
Dataloader提供了一个Promise缓存,以减少对数据库的双重调用。为了使此缓存正常工作,所有请求都必须是简单的键值查找(例如userByIdLoader
,postsByUserIdLoader
)。很快这还不够用,例如在您的一个示例中,您对存储库的请求有很多参数:
this.repository.find(postId, dateStart, dateEnd, skip, limit);
从技术上讲,您可以将{ postId, dateStart, dateEnd, skip, limit }
用作密钥,然后以某种方式对内容进行哈希处理以生成唯一的密钥。
当实现数据加载器查询时,它现在突然必须工作以获取初始查询所需的输入列表。这是一个简单的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%的连接来说可能是可以的。