【问题标题】:Defining mutations in GraphQL via fields: Is this bad practice?通过字段在 GraphQL 中定义突变:这是不好的做法吗?
【发布时间】:2019-02-18 10:48:16
【问题描述】:

假设你有一个user 类型,而一个user 有很多posts。然后想象你想找到一个用户,并删除他们所有的帖子。一种方法是实现以下mutation 字段:

field deleteAllPosts, types[Types::PostType] do
  argument :user_id, types.String

  resolve -> (obj,args,ctx){ 
    posts = Posts.where(user_id:args[:user_id])
    posts.each{|post| post.destroy}
  }
end

然后查询

mutation {
  deleteAllPosts(user_id:1)
}

将删除 id 为 1 的用户的所有帖子。

在我这样做之前,我曾想过以一种不同的方式来做这件事,我还没有看到其他人这样做过。我想检查这种不同的方式是否没有任何陷阱,或者我不应该使用它的原因。

这个想法是为PostType 放置一个deletePost 字段,并为突变放置一个findUser 字段(通常是一个查询字段)。假设这些字段的定义方式很明显,那么我将进行查询

mutation{
  findUser(id:1){
    posts{
      deletePost{
      id
      }
    }
  }
}

这是个坏主意吗?

根据反馈进行编辑:我担心的一件事是用户可以原则上将deletePost 选择放在询问。但我很想说那是“他们的错”。我喜欢说“只有在突变查询中才能进行此选择”,但我认为这在 GraphQL 中是不可能的。

为了避免XY问题,这就是为什么我热衷于使用这个想法而不是最初的想法。它感觉更有表现力(换句话说,它感觉不那么多余)。假设一段时间后,您决定删除属于特定groupusers 的所有posts。然后在我认为的“约定”中,您应该创建一个全新的突变字段:

field deleteAllPostsInGroup, types[Types::PostType] do
  argument :group_id, types.String

  resolve -> (obj,args,ctx){ 
    posts = Group.find_by(args[:group_id]).users.map{|u| u.posts}.flatten
    posts.each{|post| post.destroy}
  }
end

而在我建议的约定中,您只需定义一个简单的 findGroup 字段(但您必须在不属于它的突变上定义它),然后进行查询:

mutation{
  findGroup(id:1){
    users{
      posts{
        deletePost{
        id
        }
      }
    }
  }
}

我想我真正想做的是使用查询查找一些数据,然后改变我找到的数据。我不知道如何在 GraphQL 中做到这一点。

第二次编辑:这个问题似乎有一个明确的组成部分,我已经问过here。事实证明,这些问题中的一个可以回答另一个问题,并且可以关闭,但我还不知道是哪种方式。

【问题讨论】:

    标签: graphql


    【解决方案1】:

    这基本上是代码质量问题,类似于询问DRY原则或封装的要点。

    https://graphql.org/learn/queries/ 的引述如下:

    在 REST 中,任何请求最终都可能对服务器造成一些副作用,但按照惯例,建议不要使用 GET 请求来修改数据。 GraphQL 是类似的——从技术上讲,任何查询都可以实现来导致数据写入。但是,建立一个约定非常有用,即任何导致写入的操作都应通过突变显式发送。

    这是一个很好的约定,因为它使维护、测试和调试更容易。副作用,无论是否有意,都很难追踪和理解。特别是如果您在 GraphQL 查询中使用它们,这可能是任意大且复杂的。没有什么可以阻止您同时查询和修改同一个对象及其同级对象,并通过简单的嵌套在一个查询中多次执行此操作。这个很容易出错。

    即使您成功了,代码的可读性和可维护性也会受到影响。例如。如果您知道只有您的突变修改了数据,并且查询对其没有影响,您将立即知道从哪里开始寻找特定行为的实现。推断您的程序的总体运行方式也容易得多。

    如果您只编写小的、正确命名的、细粒度的突变,您可以比如果您有一个在不同点更新不同数据的复杂查询更容易推断出它们的作用。

    最后但并非最不重要的一点是,如果您需要将您的工作转移给其他人,遵守惯例很有用。

    简而言之 - 一切都是为了让自己和他人的生活在未来变得更轻松。


    编辑

    好的,所以我知道您将如何处理 - 您想为突变提供 GraphQL 查询的灵活性。当然,这个特定的例子会起作用。不走这条路只会是关于未来。如果 deletePost 是您将定义的唯一操作,那么讨论这个是没有意义的。

    如果不是这样,那么如果您想删除 5 个特定用户的帖子怎么办?您会给findGroup 提供额外的参数,然后将它们传递到树中吗?但是为什么findGroup 方法必须知道你将如何处理它的结果呢?这违背了灵活查询本身的想法。如果您还想对用户执行突变怎么办? findGroup 的更多参数?如果用户和帖子可以以不同的方式进行查询,例如按域查询用户、按类别发布帖子等,该怎么办?在那里也定义相同的参数?您将如何确保每次操作(尤其是同时执行其中几个操作)时,数据库中的所有关系链接都被正确删除?您必须想象查询和查询突变的每一种可能组合,并为它们适当地编写代码。由于查询大小是无限的,因此最终可能很难做到。即使单个查询突变 (deletePost) 的目的清晰且易于掌握,整个查询也不会。很快,您的查询将变得过于复杂,即使您也无法理解,您可能会开始将它们分解为更小的查询,这只会进行特定的突变。这样你会回到原来的约定,但它是一个更复杂的版本。您可能还会最终定义一些常规突变。例如,您将如何更新或添加帖子?这会将你的逻辑传播到所有地方。

    如果您正在编写突变,则不会出现这些问题。这是为了换取更好的可维护性而做的更多工作。

    这些都是未来潜在的问题(可能还有更多)。如果这些不关心你,那么请继续实施。我个人会逃避一个这样做的项目,但如果你真的很聪明,我看不出有什么会在技术上完全阻止你实现你想要的:]

    【讨论】:

    • 感谢您的回答。我很欣赏你的一般观点,但我认为它们不适用于这个例子(不清楚你是否这样做;如果你这样做,请说)。在我看来,我定义的突变deletePost is 可读/小/命名良好。我在 OP 中添加了一些细节来解释。
    • 您是否使用过 MongoDB 或类似的 NoSQL'y?
    • 不,这两个我都不熟悉。
    • 添加了一些想法,希望对您有所帮助。很高兴听到其他人要说的话。 : ]
    • 谢谢! 100% 同意这是前瞻性的(从某种意义上说,所有“最佳实践”都是)。如果你想删除 5 个特定的帖子(假设你知道他们的 ID),那么这正是你会以通常的方式为突变编写 deletePostByID 字段的那种事情。如果“您要删除的帖子”的描述在语义上以某种方式与组相关,您只会使用findGroupgroup{users{posts}} 等选择将已经在这些类型上定义,因为它们是您可能想要查询的语义内容。
    猜你喜欢
    • 2011-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-22
    • 1970-01-01
    相关资源
    最近更新 更多