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.
另外,我想摆脱upvotePost
和downvotePost
命名,而是依靠诸如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:
您可能会注意到实际上有一个子上下文在起作用。 注意,我们有名为addBook
, updateBook
, removeBook
。 我可以在模式中反映出这一点:
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突变