call突变_再谈如何组织GraphQL突变

call突变

背景 (Background)

Some time back I wrote an article about how to organize GraphQL mutations in a heirarchy, much like how it is sometimes done for queries.

一段时间以前,我写了一篇有关如何在层次结构中组织GraphQL突变的文章 ,就像有时为查询所做的那样

A while later, Anders Ringqvist informed me of a problem with my approach: it didn't guarantee that mutations will be executed in order. This is important because, unlike queries, mutations modify state, and one mutation may require the completion of a previous mutation to operate correctly.

过了一会儿,安德斯·林格维斯特(Anders Ringqvist)告诉我我的方法存在问题:它不能保证突变会按顺序执行。 这很重要,因为与查询不同,突变会修改状态,并且一个突变可能需要完成先前的突变才能正确运行。

After verifying this, I wrote a follow-up article as a retraction of sorts.

验证了这一点之后,我写了一篇后续文章作为对各种文章的撤消。

永远不要把话说绝了 (Never say never)

Matthew Lanigan recently contacted me and suggested a very simple Promise-based mechanism for ensuring order-of-execution in "nested" mutations. It's quite elegant and seems to work without introducing any side-effects.

马修·拉尼根(Matthew Lanigan)最近与我联系,并提出了一种非常简单的基于Promise的机制来确保“嵌套”突变的执行顺序。 它非常优雅,而且似乎在不引入任何副作用的情况下也可以正常工作。

机制 (The mechanism)

Here's an example of a flat set of mutations that operate on a collection of books:

这是在一组书籍上进行操作的一组平面突变的示例:

mutation {
  addBook(ISBN: "978-3-16-148410-0", title: "Schematics of the Illudium Q-36 Explosive Space Modulator", author: "Martian, Marvin") {
    references {
      title
    }
  },
  updateBook(ISBN: "978-3-16-148410-0", title: "Overview of the Illudium Q-36 Explosive Space Modulator") {
     success
  }
}

Here are those same operations rewritten to use a single Book entity:

以下是为使用单个Book实体而重写的相同操作:

mutation {
  Book(ISBN: "978-3-16-148410-0") {
    add(title: "Schematics of the Illudium Q-36 Explosive Space Modulator", author: "Martian, Marvin") {
      references {
        title
      }
    },
    update(title: "Overview of the Illudium Q-36 Explosive Space Modulator") {
     success
    }
}

The problem with the latter example is that I can't be sure that add will execute before update. Since I can update a book that hasn't been added yet, that's a problem.

后一个示例的问题在于,我无法确定add将在update之前执行。 由于我可以更新尚未添加的书,因此这是一个问题。

解决方案 (The solution)

Mathew's elegant and simple approach encapsulates mutation operations inside a parent class. That class, when constructed, creates a promise field that holds a Promise. The Promise is immediately resolved, so that any subsequent then clause will be invoked immediately.

Mathew简洁而优雅的方法将变异操作封装在父类中。 这个类,构造时,创建了拥有一个承诺承诺 。 Promise会立即解决,因此任何后续的then子句都会立即被调用。

一个例子 (An example)

class Sequential {  
   constructor() { this.promise = Promise.resolve() }
}

The Sequential class is then encapsulating mutation. It doesn't actually mutate anything, it is just a container for mutation operations. Let's next add a mutation operation:

然后,顺序类封装了突变。 它实际上并没有任何突变,它只是进行突变操作的容器。 接下来让我们添加一个变异操作:

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)
})

class Sequential {
  constructor() {
    this.promise = Promise.resolve()
  }

  message({id, wait}) {
    this.promise = this.promise.then(() => msg(id, wait))
    return this.promise
  }}

Notice that the message mutation waits for the current Promise to resolve. Since the Promise invoked in the constructor is resolved immediately, the this.promise.then(...) statement will execute instantaneously if it is the first mutation invoked. If it is not the first mutation, then it will wait for the resolution of the previous mutation. The code to follow will make this clear.

请注意, 消息突变等待当前的Promise解析。 由于在构造函数中调用的Promise会立即解决,因此如果this.promise.then(...)语句是第一个调用的变体,它将立即执行。 如果不是第一个突变,则它将等待先前突变的解决。 遵循的代码将使这一点变得清楚。

Note that the outer msg() function also returns a Promise. It is written to behave asynchronously, resolving only when the passed-in wait time has expired. This method of delaying execution will be handy during testing.

请注意,外部msg()函数还返回Promise。 它被编写为异步运行,仅在传入的等待时间到期时才解决。 这种延迟执行的方法在测试期间会很方便。

模式 (The Schema)

The GraphQL schema is pretty basic:

GraphQL模式非常基本:

type Query {
  noop: String!
}

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

type Mutation {
  Sequential: MessageOps
}

I can add as many operations to MessageOps as desired, with a corresponding implementation of those operations in the Sequential class as previously shown. The GraphQL server requires at least one Query definition, so the noop query, which does nothing, fulfills that obligation.

我可以根据需要向MessageOps添加任意数量的操作,并在Sequential类中对这些操作进行相应的实现,如先前所示。 GraphQL服务器至少需要一个查询定义,因此不执行任何操作的noop查询将履行该义务。

解析器 (The Resolver)

The code for the resolvers is straightforward:

解析器的代码很简单:

const resolvers = {
  Mutation: {
    Sequential: () => new Sequential(),
  }
}

执行 (The execution)

Now we are able to execute my mutation operations and see what happens. There are two things to watch for: 1) the response from the mutation call, and 2) the console output.

现在我们可以执行我的变异操作,看看会发生什么。 有两点需要注意:1)突变调用的响应,以及2)控制台输出。

This latter is important, because only the console output will indicate the order in which the mutations were invoked and completed.

后者很重要,因为只有控制台输出会指示突变被调用和完成的顺序。

Here's the test:

这是测试:

mutation sequential {
  Sequential {
    message1: message(id: 1, wait: 3000)
    message1a: message(id: 11, wait: 2500)
  }
  Sequential {
    message2: message(id: 2, wait: 1000)
    message2a: message(id: 22, wait: 750)
  }
  Sequential {
    message3: message(id: 3, wait: 500)
    message3a: message(id: 33, wait: 250)
  }
  Sequential {
    message4: message(id: 4, wait: 100)
    message4a: message(id: 44, wait: 50)
  }
}

The response is as follows:

响应如下:

{
  "data": {
    "Sequential": {
      "message1": "response to message 1, wait is 3000 seconds",
      "message1a": "response to message 11, wait is 2500 seconds",
      "message2": "response to message 2, wait is 1000 seconds",
      "message2a": "response to message 22, wait is 750 seconds",
      "message3": "response to message 3, wait is 500 seconds",
      "message3a": "response to message 33, wait is 250 seconds",
      "message4": "response to message 4, wait is 100 seconds",
      "message4a": "response to message 44, wait is 50 seconds"
    }
  }
}

This looks like everything occurred in order, but that can be deceiving: The order of results in the returned JSON matches the order of the mutation calls, but that order can be different than that in which the mutations completed. It is the console output from each invocation of msg() that lists the actual order-of-execution:

看起来一切都按顺序发生,但这可能是骗人的:返回的JSON中的结果顺序与变异调用的顺序匹配,但是该顺序可以与变异完成的顺序不同。 每次调用msg()的控制台输出都列出了实际的执行顺序:

{ id: '1', wait: 3000 }
{ id: '11', wait: 2500 }
{ id: '2', wait: 1000 }
{ id: '22', wait: 750 }
{ id: '3', wait: 500 }
{ id: '33', wait: 250 }
{ id: '4', wait: 100 }
{ id: '44', wait: 50 }

Thus we have proof that the mutations are actually occurring in the correct sequence: msg #1, which takes 3000 milliseconds, finishes execution well before msg #44, which only takes 50 milliseconds to finish. This is because each mutation operation is only invoked when the previous mutation has finished. Voilà!

因此,我们有证据证明突变实际上是按照正确的顺序发生的:需要花费3000毫秒的味精#1在需要花费50毫秒的味精#44之前就完成了执行。 这是因为每个变异操作仅在前一个变异完成时才被调用。 瞧!

As I await to be informed of the next "gotcha", please feel free to examine the complete github project here. It contains some extra goodies that you can run your own GraphQL mutations against (see testMutations.gql to get started).

当我等待下一个“陷阱”的通知时,请随时在此处检查完整的github项目。 它包含一些额外的功能,您可以针对它们运行自己的GraphQL突变(请参阅testMutations.gql入门)。

翻译自: https://www.freecodecamp.org/news/organizing-graphql-mutations-revisited/

call突变

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值