Thank 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?
You're approach is the right one, but you have to define a resolve_UserOps on the root, to let know graphene that it should go forward
Thank you so much! That actually worked. My resolve method looks like this
def resolve_UserOps(self, *_):
return {
'create_user': True
}
Could you possibly think of a cleaner way to implement the method?
Could you please show me your code @vladtsf , i can't figure out how to do this, been a week now.. Thank you
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.
mutation changeTheName {
first: updateAuthor(name: "Jim") { author { name } }
second: updateAuthor(name: "Bob") { author { name } }
third: updateAuthor(name: "Joe") { author { name } }
}
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.
mutation nestedChangeTheName {
author {
first: update(name: "Jim") { author { name } }
second: updateAuthor(name: "Bob") { author { name } }
third: updateAuthor(name: "Joe") { author { name } }
}
}
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.
class StoryOperations(graphene.ObjectType):
create = CreateStory.Field()
update = UpdateStory.Field()
delete = DeleteStory.Field()
class Mutation(graphene.ObjectType):
create_story = CreateStory.Field()
update_story = UpdateStory.Field()
delete_story = DeleteStory.Field()
story = graphene.Field(StoryOperations)
@staticmethod
def resolve_story(root: None, info: graphene.ResolveInfo):
return StoryOperations()
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
This answer should be copied to the FAQ.
@dvndrsn thank you for taking your time to write such a detailed answer! This is super helpful.
From my understanding this is server specific behavior? Graphene does not have the mentioned issue because it is not an async implementation. I'm too lazy to read up the GraphQL spec but I believe this behavior is not defined in the spec so it should be safe to use nested mutations.
@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:
mutation {
createUser(uid: "b53a20f1b81b439", username="new user", password="secret"){
uid
username
}
updateGroup(gid="group id", userIds=["b53a20f1b81b439", ...]){
gid
name
}
}
You noticed that I provide the user id in the input of the createUser mutation. My problem is that to make the updateGroup 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 resolving updateGroup, 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:
query {
uuid
group (gid: "group id") {
gid
name
}
}
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.
Most helpful comment
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.Fieldto 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