当心GraphQL嵌套突变!

“I have a cunning plan…”

“我有一个狡猾的计划……”

Once upon a time, I hit upon the notion of organizing GraphQL mutations by nesting operations in a return type. The idea was that these operations would then mutate the parent entity.

很久以前,我想到了通过在返回类型中嵌套操作来组织GraphQL突变的想法。 想法是这些操作随后将使父实体发生变异。

The basic idea was this:

基本思想是:

input AddBookInput {
            ISBN: String!
            title: String!
        }
        
input RemoveBookInput {
            bookId: Int!
        }
        
input UpdateBookInput {
          ISBN: String!
          title: String!
      }
      
type AuthorOps {
          addBook(input: AddBookInput!): Int
          removeBook(input: RemoveBookInput! ): Boolean
          updateBook(input: UpdateBookInput!): Book
      }
      
type Mutation {
        Author(id: Int!): AuthorOps
      }

And I’ve used this technique a few times without ill effect, but I’ve been lucky. Where’s the problem?

而且我已经多次使用了这种技术,但效果不佳,但是我很幸运。 哪里出问题了?

A reader pointed me to an issue on the GraphQL GitHub site where it was stated that the execution order of nested mutations is not guaranteed. Uh-oh. In the above case, I definitely want the addBook() mutation to occur before attempting an updateBook() operation on the same book. Alas, only so-called root mutations are guaranteed to execute in order.

读者我指出了GraphQL GitHub网站上的一个问题 ,该问题 指出不能保证嵌套突变的执行顺序。 哦哦 在上述情况下,我绝对希望在尝试对同一本书进行updateBook ()操作之前发生addBook ()突变。 las,只能保证按顺序执行所谓的 突变

问题说明 (An illustration of the problem)

Say I have a message queue where I want the messages stored in the order in which they were received. Some messages take longer to process, so I use a mutation to guarantee that messages are processed sequentially:

假设我有一个消息队列,我希望在其中按接收消息的顺序存储消息。 一些消息需要更长的时间来处理,因此我使用一种变体来保证消息是按顺序处理的:

type Query {
  noop: String!
}

type Mutation {
  message(id: ID!, wait: Int!): String!
}

The resolver logs when the message arrives, then waits a given time before returning the mutation result:

解析器记录消息到达的时间,然后等待指定的时间,然后返回变异结果:

const msg = (id, wait) => new Promise(resolve => {
  setTimeout(() => {
    
console.log({id, wait})
    let message = `response to message ${id}, wait is ${wait} seconds`;
    
resolve(message);
  }, wait)
})

const resolvers = {
  Mutation: {
    message: (_, {id, wait}) => msg(id, wait),
  }
}

Now for the trial run. I will want to ensure that the console log messages are in the same order as the mutation requests. Here’s the request:

现在开始试运行。 我将要确保控制台日志消息与变异请求的顺序相同。 这是请求:

mutation root {
  message1: message(id: 1, wait: 3000)
  message2: message(id: 2, wait: 1000)
  message3: message(id: 3, wait: 500)
  message4: message(id: 4, wait: 100)
}

The response is:

响应为:

{
  "data": {
    "message1": "response to message 1, wait is 3000 seconds",
    "message2": "response to message 2, wait is 1000 seconds",
    "message3": "response to message 3, wait is 500 seconds",
    "message4": "response to message 4, wait is 100 seconds"
  }
}

And the console log says:

控制台日志显示:

{ id: '1', wait: 3000 }
{ id: '2', wait: 1000 }
{ id: '3', wait: 500 }
{ id: '4', wait: 100 }

Great! The messages are processed in the order in which they are received, even though the second and subsequent messages take less time than the previous. In other words, the mutations are executed sequentially.

大! 即使第二条消息和后续消息比前一条消息花费的时间更少,也将按照接收消息的顺序来处理消息。 换句话说,变异是顺序执行的。

美中不足的苍蝇 (The fly in the ointment)

Now let’s nest these operations and see what happens. First I define a MessageOps type, then add a Nested mutation:

现在,让我们嵌套这些操作,看看会发生什么。 首先,我定义一个MessageOps类型,然后添加一个Nested突变:

const typeDefs = `
type Query {
  noop: String!
}

type MessageOps {
  message(id: ID!, wait: Int!): String!
}

type Mutation {
  message(id: ID!, wait: Int!): String!
  Nested: MessageOps
}`

My mutations now go through the Nested resolver, returning MessageOps, which I then use to execute my message mutation:

现在,我的变异将通过嵌套的解析器返回MessageOps,然后我将其用于执行消息变异:

mutation nested {
  Nested {
    message1: message(id: 1, wait: 3000)
    message2: message(id: 2, wait: 1000)
    message3: message(id: 3, wait: 500)
    message4: message(id: 4, wait: 100)
  }
}

Pretty similar to what we had before, and the response to the mutation request looks nearly the same as well:

与我们以前的非常相似,对变异请求的响应也几乎相同:

{
  "data": {
    "Nested": {
      "message1": "response to message 1, wait is 3000 seconds",
      "message2": "response to message 2, wait is 1000 seconds",
      "message3": "response to message 3, wait is 500 seconds",
      "message4": "response to message 4, wait is 100 seconds"
    }
  }
}

The only difference is the responses are packaged up in a Nested JSON object. Sadly, the console reveals a tale of woe:

唯一的区别是响应被打包在嵌套的JSON对象中。 可悲的是,控制台揭示了一个不幸的故事:

{ id: '4', wait: 100 }
{ id: '3', wait: 500 }
{ id: '2', wait: 1000 }
{ id: '1', wait: 3000 }

It reveals that the messages are processed out-of-sequence: the fastest-processing messages get posted first.

它揭示了消息是不按顺序处理的:处理最快的消息首先发布。

Alright. In the code from my original post, I actually did something more like the following:

好的。 我原始帖子的代码中 ,我实际上做了一些类似以下的事情:

mutation nested2 {
  Nested {
    message1: message(id: 1, wait: 3000)
  }
  Nested {
    message2: message(id: 2, wait: 1000)
  }
  Nested {
    message3: message(id: 3, wait: 500)
  }
  Nested {
    message4: message(id: 4, wait: 100)
  }
}

Maybe this works? Every mutation operation is in it’s own Nested root mutation, so we might expect the Nested mutations to execute sequentially. The response is identical to the one before:

也许行得通吗? 每个突变操作都在其自己的嵌套根突变中,因此我们可以期望嵌套突变会顺序执行。 响应与之前的响应相同:

{
  "data": {
    "Nested": {
      "message1": "response to message 1, wait is 3000 seconds",
      "message2": "response to message 2, wait is 1000 seconds",
      "message3": "response to message 3, wait is 500 seconds",
      "message4": "response to message 4, wait is 100 seconds"
    }
  }
}

But so is the console log:

但是控制台日志也是如此:

{ id: '4', wait: 100 }
{ id: '3', wait: 500 }
{ id: '2', wait: 1000 }
{ id: '1', wait: 3000 }
那么这是怎么回事? (So what’s going on here?)

The “problem” is that GraphQL executes a Nested mutation, returning an object with further mutation methods. Unfortunately, once that object is returned, GraphQL is off to the next mutation request, unaware that there are further mutation operations to be performed in the request.

“问题”是GraphQL执行Nested变异,并使用进一步的变异方法返回对象。 不幸的是,一旦返回了该对象,GraphQL便转到下一个突变请求,而没有意识到在该请求中还要执行进一步的突变操作。

GraphQL is elegantly simple, but simple comes at a cost. It’s conceivable that nested mutations could be supported, say by adding a mutator type (its corollary would be the input type), which GraphQL would treat as an extension of the mutation operation. As it stands, there’s just not enough information in the mutation request to know that nested operations are mutators, also.

GraphQL非常简洁,但简单却要付出代价。 可以想象可以支持嵌套的突变,例如,通过添加一个突变体类型(其推论是输入类型 ),GraphQL将其视为突变操作的扩展。 就目前而言,变异请求中没有足够的信息来知道嵌套操作也是变异者。

组织GraphQL突变,第2部分 (Organizing GraphQL Mutations, part 2)

You can still use the technique for operations that are not sequentially dependent, but that’s an assumption that’s likely to be brittle and hard to debug when violated. Perhaps schema stitching or weaving offers an answer. I hope to explore these approaches in a future post.

您仍然可以将该技术用于不依序依赖的操作,但这是一个假设,一旦违反,该假设可能会很脆弱且难以调试。 模式拼接编织可能会提供答案。 我希望在以后的文章中探讨这些方法。

The complete NodeJS application used for this post can be found here.

可以在此处找到用于此帖子的完整NodeJS应用程序。

翻译自: https://www.freecodecamp.org/news/beware-of-graphql-nested-mutations-9cdb84e062b5/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值