call突变_组织GraphQL突变

call突变

Cleaning up the CRUD.

清理CRUD。

Update (5/7/2018): Anders Ringqvist (comments) spotted an issue report that can cause problems when using this approach. Please see my follow up post.

更新(5/7/2018): Anders Ringqvist(评论)发现了一个问题报告 ,使用该方法可能会导致问题 。 请参阅我的后续帖子

-

The Great Divide in GraphQL schemas runs between Queries and Mutations. A query method reads data from a datasource, such as a SQL database or file system or even a remote service. Whereas queries can be executed concurrently, mutations cannot.

GraphQL模式的巨大鸿沟在查询和突变之间。 查询方法从数据源读取数据,例如SQL数据库或文件系统,甚至是远程服务。 可以同时执行查询,而不能进行变异。

Mutations have to execute sequentially because the next mutation operation may be dependent on data stored or updated by the previous mutation. For instance, a record has to be created before it can be updated. Therefore, mutations have to execute sequentially. This is why queries and mutations have their own namespace in GraphQL.

突变必须顺序执行,因为下一个突变操作可能取决于上一个突变存储或更新的数据。 例如,必须先创建一条记录,然后才能对其进行更新。 因此,突变必须顺序执行。 这就是为什么查询和变异在GraphQL中具有自己的名称空间的原因。

Queries are the ‘R’ in CRUD (Create, Read, Update, & Delete). The code in this article builds off of a Launchpad example. In the Launchpad code, there is a query defined that will return an Author’s Posts, given an author ID. I’ve extended this example once already on my post about testing GraphQL interfaces. In that post I added Books to the mix, and here I’ll extend that idea.

查询是CRUD中的“ R”(创建,读取,更新和删除)。 本文中的代码是基于Launchpad示例构建的。 在启动板代码中,定义了一个查询,该查询将返回给定作者ID的作者的帖子。 我已经在有关测试GraphQL接口的帖子中扩展了此示例。 在那篇文章中,我将Books添加到了组合中,在这里我将扩展这个想法。

作者职位 (Author Posts)

Mutations are the CUD in CRUD. The Launchpad example linked above has an upvotePost mutation that bumps up the vote count (an update operation) for a Post.

变异是CRUD中的CUD。 上面链接的Launchpad示例具有upvotePost突变,该突变增加了Post的投票计数(更新操作)。

Mutation: {
    upvotePost: (_, { postId }) => {
      const post = find(posts, { id: postId });
      if (!post) {
        throw new Error(`Couldn't find post with id ${postId}`);
      }
      post.votes += 1;
      return post;
    },
  },

To implement down vote also, I simply create a similar downvotePost mutation:

为了同样实现拒绝投票,我只创建了一个类似的downvotePost突变:

Mutation: {
...

  downvotePost: (_, { postId }) => {
      const post = find(posts, { id: postId });
      if (!post) {
        throw new Error(`Couldn't find post with id ${postId}`);
      }
      post.votes -= 1;
      return post;
    },
  },

This is not exactly a DRY way of doing it. The body of the logic could be put into one external function with a parameter to increment the vote up or down.

这不是完全干的方法。 逻辑主体可以通过参数增加到一个外部函数中,以增加或减少投票。

Also, I would like to get rid of the upvotePost and downvotePost naming and instead rely on a context, like Post.upvote() and Post.downvote(). That can be done by having the Mutation method return a set of operations that affect a given Post.

另外,我想摆脱upvotePostdownvotePost命名,而是依靠诸如Post.upvote()Post.downvote()类的上下文。 这可以通过使Mutation方法返回影响给定Post的一组操作来完成。

PostOps is a type defined as:

PostOps是一种类型,定义为:

type PostOps {
          upvote(postId: Int!): Post
          downvote(postId: Int!): Post
      }

The noun Post has been eliminated from the verb-noun name of the method as it is redundant. The resolver code operates in a Post context, via PostOps:

该方法的动词名词名称已消除了名词Post ,因为它是多余的。 解析程序代码通过PostOps在Post上下文中PostOps

You’ll notice I use a new Promise in the resolver, though technically it isn’t required for this example. Nonetheless, most applications fetch data asynchronously, so… force of habit?

您会注意到我在解析器中使用了一个新的Promise,尽管从技术上讲,此示例不是必需的。 尽管如此,大多数应用程序都是异步获取数据的,所以……习惯有力吗?

Now, instead of calling a mutation method directly at the root level, it is called within the context of a Post:

现在,它不是直接在根级别调用突变方法,而是在Post的上下文中调用它:

mutation upvote {
  Post {
    upvote(postId: 3) {
      votes
    }
  }
}

And this returns:

并返回:

{
  "data": {
    "Post": {
      "upvote": {
        "votes": 2
      }
    }
  }
}

So far, so good. The methods could be DRYed up further by moving the postId argument to the top level:

到目前为止,一切都很好。 可以通过移动postId进一步干燥这些方法 顶级参数:

extend type Mutation {
        Post
(postId: Int!): PostOps
}

type PostOps {
          upvote: Post
          downvote: Post
      }

The PostOp resolvers would remain unchanged: they still take a postId parameter, but that parameter is passed from Post to PostOps. The next example will explain how this works in detail.

PostOp解析器将保持不变:它们仍采用postId参数,但是该参数从Post传递到PostOps 。 下一个示例将详细解释其工作原理。

作者和书籍 (Authors and Books)

The Authors in my application not only author Posts, but some have authored Books as well. I want to perform classical Create, Update, and Delete operations on the list of books authored. The AuthorOps are then:

在我的应用程序中,作者不仅是作者帖子,而且有些作者还撰写了书籍。 我想对创作的书籍清单执行经典的创建,更新和删除操作。 然后, AuthorOps是:

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
      }

In GraphQL, Mutations take their own Input types as parameters. This is commonly necessary for entities that have autogenerated IDs. In the Query type, Author ID may be required, but in the AuthorInput type, it isn’t nor can it be (the ID is generated).

在GraphQL中, 变异将其自己的输入类型作为参数。 对于具有自动生成ID的实体,这通常是必需的。 在“查询”类型中,可能需要“作者ID”,但在“作者输入”类型中,它不是也不是(已生成ID)。

In this case, ISBN is the non-generated Book ID, so is included in CreateBookInput. Books also have an Author. Where is that going to come from? It turns out that authorId gets passed to the addBook resolver from the context from which the create operation is called, namely AuthorOps:

在这种情况下,ISBN是未生成的Book ID,因此包含在CreateBookInput 。 书籍中也有作者。 那是哪里来的? 事实证明, authorId被传递到addBook从该创建操作的上下文解析器被调用,即AuthorOps

extend type Mutation {
        Post: PostOps
        Author(id: Int!): AuthorOps
      }

The resolver for AuthorOps looks like:

AuthorOps的解析器如下所示:

const addBook = (book, authorId) => {
    console.log("addBook", book, authorId)
    return new Promise((resolve, reject) => {
        book.authorId = authorId
        books.push(book)
        resolve(books.length)
    })
}

const removeBook = (book, authorId) => {
    return new Promise((resolve, reject) => {
        books = books.filter(b => b.ISBN !== book.ISBN && b.authorId === authorId);
        resolve(books.length)
    })
}

const updateBook = (book, authorId) => {
    return new Promise((resolve, reject) => {
        let old = books.find(b => b.ISBN === book.ISBN && b.authorId === authorId);
        if (!old) {
            reject(`Book with ISBN = ${book.ISBN} not found`)
            return
        }
        resolve(Object.assign(old, book))
    })
}

const AuthorOps = (authorId) => ({
    addBook: ({
        input
    }) => addBook(input, authorId),
    removeBook: ({
        input
    }) => removeBook(input, authorId),
    updateBook: ({
        input
    }) => updateBook(input, authorId)
})

Now let’s create a book and update it:

现在,让我们创建一本书并对其进行更新:

mutation addAndUpdateBook {
  Author(id: 4) {
    
addBook(input: {ISBN: "922-12312455", title: "Flimwitz the Magnificent"})
  }
  Author(id: 4) {
    
updateBook(input: {ISBN: "922-12312455", title: "Flumwitz the Magnificent"}) {
      authorId
      title
    }
  }
}

The response is:

响应为:

{
  "data": {
    "Author": {
      "addBook": 4,
      "updateBook": {
        "authorId": 4,
        "title": "Flumwitz the Magnificent"
      }
    }
  }
}
那“书”呢? (What about “Book”?)

You may notice that there is actually a subcontext at play. Notice that we have mutations named addBook, updateBook, removeBook. I could reflect this in the schema:

您可能会注意到实际上有一个子上下文在起作用。 注意,我们有名为addBookupdateBookremoveBook 。 我可以在模式中反映出这一点:

type AuthorOps {
     Book: BookOps
}

type BookOps {
     add(input: AddBookInput!): Int
     remove(input: RemoveBookInput! ): Boolean
     update(input: UpdateBookInput!): Book
}

Nothing stops you from adding contexts as deep as you like, but be aware that the returned results are nested deeper each time this technique is used:

没有什么可以阻止您添加任意深度的上下文,但是请注意,每次使用此技术时,返回的结果都嵌套得更深:

>>> RESPONSE >>>
{
  "data": {
    "Author": {
       "Book": {

          "add": 4,
          "update": {
             "authorId": 4,
             "title": "Flumwitz the Magnificent"
          }
        }
     }
  }
}

This is quite similar to the structure GraphQL queries return, but for mutation operations deep hierarchies can get in the way: you have to “dig deep” to figure out if your mutation operation was successful. In some cases, a flatter response may be better. Still, a shallow organization of mutations in a few high-level contexts seems better than none.

这与GraphQL查询返回的结构非常相似,但是对于突变操作,深层次结构可能会妨碍您:您必须“深入研究”以确定您的突变操作是否成功。 在某些情况下,平坦的响应可能会更好。 但是,在一些高级情况下,对突变的浅表组织似乎总比没有好。

Working source code for this post can be found on my Github account.

可以在我的Github帐户上找到此帖子的工作源代码。

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

call突变

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值