Nested Mutations
See original GitHub issueThank you so much for all the work you’ve done to build Graphene!
I’m currently building my GraphQL API using Graphene and one of the problems I’m trying to solve is mutation organization. I expect my application to have hundreds of mutations and naturally there’s a need to break them all down into groups somehow. I read this article (https://medium.freecodecamp.org/organizing-graphql-mutations-653306699f3d) that suggests creating “ops” objects that would work as services that cover particular areas of the application. So, I’d like to be able to structure my queries like this:
mutation myMutation {
UserOps {
createUser(input: {
firstName: "Foo",
lastName: "Bar",
}) {
user {
id
}
}
}
}
I can’t find a way to implement this. I tried defining userOps
as an ObjectType
and adding createUser
and updateUser
as fields, but this approach doesn’t seem to work. Would you have any suggestions on how to approach this problem?
Issue Analytics
- State:
- Created 4 years ago
- Comments:8 (2 by maintainers)
Top GitHub Comments
This is a clever idea, but is not recommended in GraphQL best practices.
In GraphQL spec, the Root Mutation is treated differently than the Root Query and nesting fields break some of the promises that mutations should adhere to. I believe nesting fields under the root mutation actually break the GraphQL spec itself (your API may not be smarter compatible with GraphQL clients like Relay). I’ll try to convince you to not do this, then show you how to do it anyway lol.
In the Root Query, there is no guarantee of order that fields will be resolved in - everything is asynchronous. This is fine though because we promise not to change any data in our resolvers, so we won’t face any problems - we’re just retrieving data that already exists.
In the Root Mutation, GraphQL guarantees that each field will be resolved in the order which they are requested. This is super important while changing data because we may have many dependent mutation fields being requested in one mutation operation. This is important for maintaining consistency between our server and the clients caching responses from our server.
In this example, we know the author name will always be updated with ‘Joe’ since it is the last being requested in a series of fields requested from the Root Mutation.
In this example, the quickest update will win! If resolved asynchronously, we have no idea which will be set because we are outside of the guarantee of the root mutation.
You can ignore this best practice, but without that guarantee of order of execution you may end up in a hard to debug situation in the future. Your API may be breaking GraphQL spec promises and may not work properly with some clients. That being said, you may be OK if you’re only requesting one mutation field per operation. You also may be OK if your mutation code is blocking, but I’m not sure how Graphene will resolve these in practice.
Ok, now that I’ve tried to convince you not to do this, this is how you can do this, if you accept the risks. Pretty simple. No need to use a dict, but you do need a resolver. If you want to skip writing the resolver, you could subclass
graphene.Field
to return the instance of your object type but I’ll leave that to you if you want to go down this path.Here’s a blog post that goes into some of the gory details about potential problems you may face (Js-centric):
https://medium.freecodecamp.org/beware-of-graphql-nested-mutations-9cdb84e062b5
@dvndrsn thanks a lot for your answer. I wasn’t sure if I should comment a closed issue, please let me know if I should do otherwise.
From your answer I understood why we shouldn’t use nested mutations. Please could you give us a hint on how to satisfy a use case where nested mutations were considered (using instead sequential mutations) ? If you allow me, I would like to present a use case, then the solution I imagine, hoping you could tell me if it’s good practice, and if not what you recommend.
Let’s image I have User and Group entities, and I want, from the client form to update a group, to be able to not only add a user, but also create a user to be added in a group if the user does not exist. The users have ids named uid (user id) and groups gid (groupd id), just to highlight the difference. So using root mutations, I imagine doing a query like:
You noticed that I provide the user id in the input of the
createUser
mutation. My problem is that to make theupdateGroup
mutation, I need the ID of the newly created user. I don’t know a way to get that in graphene inside the mutate methods resolvingupdateGroup
, so I imagined querying a UUID from the API while loading the client form data. So before sending the mutation above, at the initial loading of my client, I would do something like:Then I would use the uuid from the response of this query in the mutation request (the value would be
b53a20f1b81b439
, as in the the first scriptlet above).What do you think about this process ? Is there a better way to do that ? Is Python
uuid.uuid4
safe to implement this ?Thanks in advance, Kind regards.