【问题标题】:GraphQL: Can you mutate the results of a query?GraphQL:你能改变查询的结果吗?
【发布时间】:2019-02-19 03:38:20
【问题描述】:

在写this question 时,我意识到我希望能够在 GraphQL 中做一些非常具体的事情,但我看不到实现它的好方法。思路是这样的:

GraphQL 的优点之一是它允许您进行灵活的查询。例如,如果我想在特定forum 中的每个user 的所有posts 上查找所有comments,那么我可以进行查询

query{
  findForum(id:7){
    users{
      posts{
        comments{
          content
        }
      }
    }
  }
} 

这很棒。通常,您希望收集数据以对其进行变异。所以在这种情况下,也许我不想获取所有这些 cmets,而是想删除它们。一个天真的建议是在 comment 类型上实现一个 deleteComment 字段,它会改变调用它的对象。这是不好的,因为请求被标记为query,所以它不应该改变数据。

由于我们正在改变数据,我们绝对应该将其标记为mutation。但是随后我们失去了进行我们想要进行的查询的能力,因为findForum 是一个查询字段,而不是一个变异字段。 this 的一种解决方法可能是在突变类型中重新定义您需要的所有查询字段。这显然不是一个好主意,因为您重复了很多代码,并且还使query 的功能成为mutation 的严格子集。

现在,我认为“传统”的解决方案是制作一个突变场来完成这项工作而不是其他任何事情。所以你定义了一个突变字段deleteAllUserPostCommentsByForum,它接受一个参数,并以显而易见的方式实现它。但是现在你已经失去了灵活性!如果您决定要明确查找 user 并删除他们的所有帖子,或者如果您只想删除他们的 部分 帖子,则需要一个全新的变异字段。这感觉正是我认为 GraphQL 与 REST 相比有用的东西。

那么,有没有一种同时避免这些问题的好方法呢?

【问题讨论】:

    标签: graphql


    【解决方案1】:

    在底层,查询和突变之间唯一真正的区别是,如果单个操作包含多个突变,它们会按顺序(一次一个)而不是同时解决。查询和所有其他字段同时解析。这意味着对于这样的操作:

    mutation myOperation {
      editComment(id: 1, body: "Hello!")
      deleteComment(id: 1)
    }
    

    editComment 突变将在 deleteComment 突变之前解决。如果这些操作是查询,它们将同时运行。同样,考虑是否有一个返回对象的突变,如下所示:

    mutation myOperation {
      deleteComment(id: 1) {
        id
        name
      }
    }
    

    在这种情况下,idname 字段同时解析(因为,即使它们作为突变的一部分返回,字段本身也不是突变)。

    查询和突变之间的这种行为差异凸显了为什么按照惯例我们为每个操作定义一个突变,并避免像您的问题所暗示的那样“嵌套”突变。

    使您的突变更加灵活的关键在于您如何将输入传递给您的突变,然后您如何在解析器中处理这些输入。与其制作deleteAllUserPostCommentsByForum 突变,不如制作一个接受更健壮的InputType 的deleteComments 突变,例如:

    input DeleteCommentsInput {
      forumId: ID
      userId: ID
    }
    

    然后,您的解析器只需要处理可能传入的输入字段的任何组合。如果您使用的是数据库,这种输入很容易转换为WHERE 子句。如果您意识到您需要其他功能,例如在某个日期之前或之后删除 cmets,您可以将这些字段添加到您的输入类型并相应地修改您的解析器——无需创建新的突变。

    您实际上可以类似地处理创建和编辑,并使事情变得有点干燥。例如,您的架构可能如下所示:

    type Mutation {
      createOrUpdateComment(comment: CommentInput)
    }
    
    input CommentInput {
      id: ID
      userId: ID
      body: String
    }
    

    然后,您的解析器可以检查是否包含 ID——如果是,则将操作视为更新,否则将操作视为插入。当然,在这种情况下使用非空值可能会变得很棘手(创建时可能需要userId,但更新时不需要),因此对于每种操作都有单独的输入类型是有好处的。但是,希望这仍能说明您如何利用输入类型使您的突变更加灵活。

    【讨论】:

    • 这是一个有趣的回应,但我认为它与我正在寻找的东西有些脱节。您提出的想法(使用输入类型来指定要变异的内容)很好。我想我想说的是,有时“进行查询”然后“改变结果”感觉更自然——当然这就是我在服务器端所做的。由于 GQL 在进行查询方面非常强大,所以我觉得这种方法不太适合这种风格,这让我有点奇怪。 (我很感激这不是一个问题)
    【解决方案2】:

    恕我直言,您失去了许多间接方面。

    尝试创建“灵活”查询可能会导致服务器操作高度未优化。

    查询是在结构上逐级解析的,这可能会导致处理许多不必要的数据(高内存使用)。它无法在较低层(例如 sql 服务器)上进行优化 - 它会导致像许多“手动触发”SQL 查询与一个更复杂的带条件查询一样的幼稚实现(处理)。

    在这种情况下 f.e.服务器根本不需要所有用户,而用户的帖子/评论通常包含user_id(和论坛/线程/帖子ID)字段 - 它可以直接在一张桌子上处理(与加入的帖子)。您不需要整个结构来仅影响某些元素。

    graphQL 的真正功能和灵活性在于解析器。

    请注意,删除全部或仅部分 cmets 可以完全不同地实现。解析器可以选择更好的方式(通过 Daniel 所写的参数),但为了简单起见(API 的可读性),最好有一个单独的突变。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-01
      • 2019-12-29
      • 2018-10-08
      • 2019-01-26
      • 1970-01-01
      • 2014-12-26
      相关资源
      最近更新 更多