Often my mutations involve the creation of related resources which have already their own creation mutations. I'd rather not duplicate my logic.
Is there a way to call the resolve method of a mutation from the resolve method of a different mutation?
Example
var UserInputType = new GraphQLInputObject({
name: 'UserInputType',
fields: {
name: { type: GraphQLString },
avatar: { type: PictureInputType }
}
});
var MutationA = new GraphQLObject({
type: Picture,
args: {
picture: { type: PictureInputType }
},
resolve: (_, args) => {
// add picture...
return new_picture;
}
});
var MutationB = new GraphQLObject({
type: User,
args: {
user: { type: UserInputType }
},
resolve: (_, args) => {
// ...
// user-specific stuff
// ...
// something like this
MutationA.resolve(_, { picture: args.user.avatar });
// ...
return new_user;
}
});
A trivial workaround would be to provide the resolve method as a standalone function and call that, but it seems inelegant/hacky to me.
It generally seems to me like a bad idea to make your application logic dependant on the delivery mechanism. Separating it not only into separate function but perhaps also a separate file will in the end bring you more reusability.
Either way looking on the GraphQLObjectType you might be able to do something like
MutationA._typeConfig.resolve(_, { picture: args.user.avatar });
I haven't tested it but looking on the code this should invoke your resolve function.
@mkmarek maybe yes, I could decouple more, but now 90% of my resolvers are pretty much validation + forwarding to the ORM, which amounts to less than 10 lines of code usually.
However, I'd like to keep the "add object + relations" and "add related object" as much consistent as possible, possibly with the same signature so that adding related object while creating the "main" one is just a shortcut for adding the main object + adding related ones separately.
BTW the resolver method is actually MutationA.resolve.
This might be a good use case for the repository pattern. Here's what it would look like:
//PictureRepository.js
export function addPicture(picture) {
...ORM logic
}
//MutationA.js
import {addPicture} from 'PictureRepository';
...
resolve: (_, args) => {
return addPicture(args.input);
}
The dependency graph then becomes: MutationA and MutationB depend on addPicture.
Yes that's exactly what I'm doing now.
However serving the mutation some other concerns arises which are not well addressed by the ORM.
As of now the case is isolated so I feel adding a whole new layer between the ORM and my already scarce GraphQL layer is overkill. In future, it might be a good thing to do.
Yeah, I think separating out the core mutation business logic into a layer between GraphQL and the ORM is the right call here; there's no first-class way to call another mutation's resolver because adding the additional layer effectively gives us the same effect, and is generally the encouraged way of doing it.
As of now the case is isolated so I feel adding a whole new layer between the ORM and my already scarce GraphQL layer is overkill. In future, it might be a good thing to do.
I'd definitely recommend adding that middle layer long-term; the fact that there's common validation logic that needs to happen in two different GraphQL mutations makes me think the time might even be right to do it now. To be honest, in my side projects I introduce that layer with my first GraphQL type. I never have a thick GraphQL layer even in the earliest development stage, I always put all of my business logic in the middle layer and have trivial GraphQL resolvers.
I talked about this middle business logic layer (in the context of how we use it at Facebook) in my React Europe talk this year, starting at https://youtu.be/etax3aEe2dA?t=8m42s. I mostly talk about it in the context of reads, but our use of it at Facebook applies to writes as well.
As of now the case is isolated so I feel adding a whole new layer between the ORM and my already scarce GraphQL layer is overkill. In future, it might be a good thing to do.
Although splitting your functionality into separate layers with that layers just recalling different layers might seem like an overkill from the start, with increasing amount of functionality you would end up in a scenario when you would have to rewrite lots of existing code.
There is this principle called the open closed principle which says that your code should be opened for addition but closed for modification. Of course you can't conform with it in all scenarios that will occur during development but you can prepare for those changes that are already expected.
This is important because when you make a change that enforces other kinds of functionality to be changed those areas would have to be retested and if you don't retest them all and something brakes in a module that's not related to the change that you promised to your customers or users then your software will seem to be unpredictive. This is a scary thing to happen. (Personal experience)
You convinced me folks 馃憤 thanks a lot
Most helpful comment
Yeah, I think separating out the core mutation business logic into a layer between GraphQL and the ORM is the right call here; there's no first-class way to call another mutation's resolver because adding the additional layer effectively gives us the same effect, and is generally the encouraged way of doing it.
I'd definitely recommend adding that middle layer long-term; the fact that there's common validation logic that needs to happen in two different GraphQL mutations makes me think the time might even be right to do it now. To be honest, in my side projects I introduce that layer with my first GraphQL type. I never have a thick GraphQL layer even in the earliest development stage, I always put all of my business logic in the middle layer and have trivial GraphQL resolvers.
I talked about this middle business logic layer (in the context of how we use it at Facebook) in my React Europe talk this year, starting at https://youtu.be/etax3aEe2dA?t=8m42s. I mostly talk about it in the context of reads, but our use of it at Facebook applies to writes as well.