Querying Interfaces in GraphQL
Here at Ansaro, we’re not shy about how much we love GraphQL and Relay. One of the biggest benefits that they bring to the table is the flexibility to alter the data you fetch as product requirements change.
We were recently working on a new feature and had to substantially change the structure of the data that the React front-end was fetching from the server. This led to some interesting learnings about GraphQL, as we explored the options for expanding our schema. Let’s dive in!
Previously, we had a concept of a Task
in our GraphQL schema, which represented a scheduled time and process for a user to interview a candidate. Our schema looked like this:
This made the query to fetch tasks simple enough. To get a list of tasks, we simply fetch for tasks that belong to the currently logged in user, represented in the schema by the viewer
object. To get one task, we perform a node query on our Task
and ask for the fields we need.
Then we began work on a new feature to have users complete a survey a few months after one of their candidates is hired. The purpose of this survey is to get a general idea of how that candidate is performing on the job. That way, we can do ✨ Data Science ✨ and 🔥 Machine Learning 🔥 to tie interviews to outcomes.
Emojis and buzzwords aside, we needed to figure out how to add this new data to our schema. We wanted these surveys to appear with upcoming interviews for a user, interleaved together in just one list.
So we had two choices. We could create a brand new Survey
type in our GraphQL schema and treat it as its own entity. Alternately, we could merge both concepts with a parent interface, and have two constructs implement that interface.
To keep them separate we would need to query for them separately, and then merge them into one list on the client side. By keeping them together, we would only need one query and less data massaging on the client side. We also noticed some similarities between the existing Task
concept and our new Survey
concept. That is, they both had some of the same fields, like an Applicant
field to keep track of who was being interviewed or evaluated.
Because of this, we decided to keep them together, inheriting from an interface. Here are the relevant parts of our new schema.
This led to an interesting outcome for our queries. To get a list of Task
objects, we can query for the current user’s tasks and break that down by sub-types. But to get a specific interview or survey, we have to query for them as a Node, not a Task. Inside this node query, we then specify the exact data for either by breaking up into sub-types.
Combining a SurveyTask
and InterviewTask
into one interface did have some drawbacks.
First, these queries ultimately return Task
and Node
objects respectively, and we don’t know which sub-type they are ahead of time. For some uses this doesn’t matter, since they share some fields. But for a React component that’s really only for one sub-type or the other, we have to disambiguate. Fortunately for us, we can use the built-in __typename
field to tell exactly which type the Task
object is!
Secondly, you’ll notice a difference between the list query and the node query. The list query can request the shared applicant
field outside the sub-type specific fields. However, the node query can’t do this; it has to request the applicant
field individually, within both sub-types. This is because a node query returns an object of the Node
interface, and Task
no longer implements Node
. That is, the following code sample will throw an error.
There are lots of factors to consider when making a decision about using an interface in your schema. How many fields do the sub-types share? How often will you treat the sub-types the same, compared to treating them differently? If you give them separate types in your schema, will you need computationally expensive data massaging on the front-end to combine them?
GraphQL gave us the flexibility to evaluate these concerns for our specific use case and to choose the one that worked best, without a ton of overhead.
Have thoughts on GraphQL and schema design? Write a response, we’d love to hear from you!