Are mutation methods required to be on the top level?

元气小坏坏 提交于 2019-11-27 05:22:25

It's possible but generally not a good idea because:

It breaks convention. By convention, mutations are always at the root. To distinguish between doing the same action on different types, you name your mutations, for example, editPost and editComment, instead of just edit.

Having the mutations at the root makes sense conceptually. Whatever action you're doing (liking a post, verifying an email, submitting an order, etc.) doesn't rely on GraphQL having to resolve additional fields before the action is taken. This is unlike when you're actually querying data. For example, to get comments on a post, we may have to resolve a user field, then a posts field and then finally the comments field for each post. At each "level", the field's contents are dependent on the value the parent field resolved to. This normally is not the case with mutations.

Under the hood, mutations are resolved sequentially. This is contrary to normal field resolution which happens in parallel. That means, for example, the firstName and lastName of a User type are resolved at the same time. However, if your operation type is mutation, the root fields will all be resolved one at a time. So in a query like this:

mutation SomeOperationName {
  createUser
  editUser
  deleteUser
}

Each mutation will happen one at a time, in the order that they appear in the document. However, this only works for the root and only when the operation is a mutation, so these three fields will resolve in parallel:

mutation SomeOperationName {
  user {
    create
    edit
    delete
  }
}

If you still want to do it, despite the above, this is how you do it when using makeExecutableSchema, which is what Apollo uses under the hood:

const resolvers = {
  Mutation: {
    post: () => ({}), // return an empty object,
  },
  PostMutation: {
    edit: () => editPost(),
  },
  // Other types here
}

Your schema defined PostMutation as an object type, so GraphQL is expecting that field to return an object. If you omit the resolver for post, it will return null, which means none of the resolvers for the returning type (PostMutation) will be fired. That also means, we can also write:

mutation {
  post
}

which does nothing but is still a valid query. Which is yet another reason to avoid this sort of schema structure.

Absolutely disagree with Daniel!

This is an amazing approach which helps to frontenders fastly understand what operations have one or another resource/model. And do not list loooong lists of mutations.

Calling multiple mutations in one request is common antipattern. For such cases better to create one complex mutation.

But even if you need to do such operation with several mutations you may use aliases:

await graphql({
  schema,
  source: `
  mutation {
    op1: article { like(id: 1) }
    op2: article { like(id: 2) }
    op3: article { unlike(id: 3) }
    op4: article { like(id: 4) }
  }
`,
});

expect(serialResults).toEqual([
  'like 1 executed with timeout 100ms',
  'like 2 executed with timeout 100ms',
  'unlike 3 executed with timeout 5ms',
  'like 4 executed with timeout 100ms',
]);

See the following test case: https://github.com/nodkz/conf-talks/blob/master/articles/graphql/schema-design/tests/mutations-test.js

Methods like/unlike are async with timeouts and works sequentially

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!