Firstly thank you all who can lend a hand to me on this one.I don’t intend for this to be a vague question and I am sorry if this is not the right forum to ask it in.
I am having a little difficulty determining what the best practice/pattern is for mutation resolver logic. I have two entities: Question and Category supporting a many-to-many relationship.
I have extracted the bulk of my resolver logic firstly between relevant datasource classes (typeorm representing one such class, interfacing with a postgres db), and then furthermore between different service classes (currently mirroring entity/schema concerns). I have included an example of the orm
dataSource class here and one such consumed service class here.
In creating a new Category I want to support the establishing of this many-to-many relationship (see questions on CategoryInput
input type). But how best to do this?
There are a few concerns:
-
FIRST_POINT: If the input provided to the eventual
create
(category) call includes an array of questions, I want to verify that these questions are present in the database before proceeding (in creating said new Category). And in the event that any one of those questions is not present in the database, I want an error to bubble upwards and be caught here.
-
SECOND_POINT: Assuming the first point is satisfied, when I create the new Category I then want to invoke the QuestionService to update each question in the input.questions array with this new Category relationship.
I am aware that I might be extending the intended operation of the CategoryService.create
call beyond its own scope a little bit. But I am still curious if anyone has a solid proposal of how to best structure their resolver logic when greater extensibility is a concern/consideration.
resolver/CategoryService.js
async create(input) {
// FIRST_POINT
if (await this.repository.findOne({ title: input.title })) {
return {
errors: [
{
field: 'alreadyExisting',
message: `${input.title} already exists as a category`
}
]
}
} else {
const response = await this.repository.save(input);
// SECOND POINT
return { response }
}
}
schema/Category.js
input CategoryInput {
name: String
questions?: [String]
}
type CategoryPayload {
response: Category
errors: [Error]
}
mutations {
createCategory(input: CategoryInput): CategoryPayload
}
This sounds like less of a mutation / resolver design question and more of a database design question, though please correct me if I’m wrong. Your schema looks reasonable to me.
Updating each question doesn’t quite make sense to me. What do you expect to happen when a question is updated multiple times (since it presumably belongs to multiple categories?). You now need a dynamic number of columns per question (one for each category).
The correct approach to a many-to-many relationship is a join table, are you familiar with them? It’s something I would expect to be supported by your ORM as well.
If your Questions/Categories need to know about each other, either you are splitting your microservices too thin, or you need a service-to-service communication layer, such as a message broker or RPC. I would recommend a message broker because it would provide a central place for services to communicate without making said services call each other directly.
To me, it seems like it would be better to create your questions when you create your category, rather than create your questions first and then verify that they exist when you should have just created them a few milliseconds before creating the category. This would also prevent from having orphaned questions that aren’t in a category, which I imagine isn’t a desired state.
That, or you should create categories first and then assign questions to them when you create a record for the question, which to me seems more flexible.
As for your type CategoryPayload
, I wouldn’t break things out into response
and errors
because GraphQL already does that for you.
1 Like
Thanks so much for the reply @kevin-lindsay-1. I think I am going to go with your second recommendation of creating the categories and then questions. To be honest, I think I am overthinking it to begin with.
You have piqued my interest in a few other areas, though. I haven’t heard about message brokers before, do you have any resources for that pattern? I am also wondering if you could recommend any boilerplates/templates that you would consider solid examples of best practice GraphQL schema design ( Apollo
docs/templates have been excellent).
And, I thought CategoryPayload
was good practice here as a support for custom error handling?
Again, thanks for your help. It means a lot.
1 Like
Hi @trevor.scheer thanks for your reply! I am supporting a join table. I think, as I mentioned to @kevin-lindsay-1 below, that I have overthought this, haha. It is really as simple as: if creating a Category and the input args include Question(s) (as represented by id), then create these Question(s) too.
Thanks for taking the time to answer my question!
I haven’t heard about message brokers before, do you have any resources for that pattern?
AMQP 0.9 & 1.0, RabbitMQ, ActiveMQ (AMQP), AWS has a variety of pubsub options.
I am also wondering if you could recommend any boilerplates/templates that you would consider solid examples of best practice GraphQL schema design
Look up shopify’s schema design guide.
And, I thought CategoryPayload
was good practice here as a support for custom error handling?
Just throw an ApolloError
.
1 Like