在写作this question时,我意识到我希望能在GraphQL中做一些非常具体的事情,而且我看不到实现它的好方法 . 这个想法是这样的:
GraphQL的一个好处是它允许您进行灵活的查询 . 例如,如果我想在特定 forum
中的每个 user
的所有 posts
上找到所有 comments
,那么我可以进行查询
query{
findForum(id:7){
users{
posts{
comments{
content
}
}
}
}
}
这很棒 . 通常,您希望收集数据以改变数据 . 所以在这种情况下,也许我不想获取所有这些评论,而是我想删除它们 . 一个天真的建议是在 comment
类型上实现一个 deleteComment
字段,该字段会改变它所调用的对象 . 这是 bad 因为请求被标记为 query
,所以它不应该改变数据 .
由于我们正在改变数据,我们绝对应该将其标记为 mutation
. 但是我们失去了进行查询的能力,因为 findForum
是一个查询字段,而不是一个变异字段 . 解决这个问题的方法可能是重新定义突变类型中所需的所有查询字段 . 这显然不是一个好主意,因为你重复了很多代码,并且使 query
的功能成为 mutation
的严格子集 .
现在,我认为是'conventional'解决方案是制作一个突变字段来完成这项工作而不是其他任何东西 . 因此,您定义一个带有参数的变异字段 deleteAllUserPostCommentsByForum
,并以明显的方式实现它 . 但现在你失去了灵活性!如果您决定明确地找到 user
并删除他们的所有帖子,或者如果您只想删除他们的一些帖子,则需要一个全新的变异字段 . 这感觉恰恰相反,我认为GraphQL与REST相比是有用的 .
那么,有没有一种方法可以同时避免这些问题呢?
2 回答
在引擎盖下,查询和突变之间唯一真正的区别在于,如果单个操作包含多个突变,则它们将按顺序(一次一个)而不是同时解析 . 查询和所有其他字段同时解析 . 这意味着对于这样的操作:
editComment
突变将在deleteComment
突变之前解决 . 如果这些操作是查询,则它们将同时运行 . 同样,考虑是否有一个返回对象的变异,如下所示:在这种情况下,
id
和name
字段也会同时被解析(因为即使它们作为突变的一部分返回,字段本身也不是突变) .查询和突变之间的这种行为差异凸显了为什么按照惯例我们为每个操作定义一个单独的突变并避免像您的问题所暗示的“嵌套”突变 .
使您的突变更加灵活的关键在于如何将输入传递给您的突变,然后如何处理解析器内的输入 . 不要进行
deleteAllUserPostCommentsByForum
变异,只需创建一个接受更强大的InputType的deleteComments
变异,例如:然后你的解析器只需要处理可以传入的任何输入字段组合 . 如果你使用的是db,这种输入很容易转换为
WHERE
子句 . 如果您意识到需要其他功能,例如在特定日期之前或之后删除注释,则可以将这些字段添加到输入类型并相应地修改解析器 - 无需创建新的突变 .您实际上可以类似地处理创建和编辑,并使事情稍微干一些 . 例如,您的架构可能如下所示:
然后,您的解析程序可以检查是否包含ID - 如果是,则将操作视为更新,否则将操作视为插入 . 当然,在这种情况下使用非空值可能会变得棘手(创建时可能需要
userId
而不是更新),因此对于每种操作都有单独的输入类型可以说 . 但是,希望这仍然说明了如何利用输入类型来使您的突变更加灵活 .恕我直言,你失去了许多间接方面
尝试创建“灵活”查询可能会导致高度未经优化的服务器操作 .
查询在结构上逐级解析,这可能导致处理许多不必要的数据(高内存使用) . 它无法在较低层(f.e.sql server)上进行优化 - 它将导致一个简单的实现(处理),就像许多“手动触发”的SQL查询与一个更复杂的条件查询一样 .
在这种情况下,f.e . 服务器没有't need all users at all while user'的帖子/评论通常包含
user_id
(和论坛/主题/帖子ids)字段 - 它可以直接在一张 table 上处理(加入帖子) . 您不需要整个结构只影响某些元素 .The real power and flexibility of graphQL are placed on the resolvers.
请注意,删除所有或仅一些注释可以完全不同的实现 . 解析器可以选择更好的方式(通过Daniel写的参数)但是为了简单(API的可读性),最好有一个单独的突变 .