I continued to work on the GraphQL API from the last article and came across another issue involving recursive relationships between the same type of objects. I couldn’t find a way to inspect the incoming query and came up with an alternative solution to prevent having to unnecessarily pre-compute the relationships.
I’m not sure if recursive relationship is the correct terminology, but in other words an object of type A has a property which is another object of type A. Here is a sample of what that would look like in GraphQL:
And we would expect a user to be able to query this property as if it was another unit.
Notice how the user can continually request more data about the related units as if they were at the root unit. This is a good example of the power and clarity GraphQL offers. Imagine alternative REST API paradigms, such as OData, using special query parameters such as
$expand=strengths&$select=strengths/id . The user has to know about the parameter and how to format the paths for the nested properties which is up to the implementer to support. With GraphQL we get this very powerful feature out of the box and the user experience for querying properties of related objects is the same as they would for the primary object which means there is less of a learning/discovery barrier for the user to get the most out of your API. Also, since this idea of traversing the relationships is rare in REST API’s the client would often have to make separate requests using links such as those returned in JSON API response. This means more HTTP round trips and latency which can slow down the responsiveness of your application.
If I still haven’t explained this well you can see another example on the official Facebook QraphQL repository here:
Scroll down to the part about nested queries:
One of the key aspects of GraphQL is its ability to nest queries. In the above query, we asked for R2-D2’s friends, but we can ask for more information about each of those objects. So let’s construct a query that asks for R2-D2’s friends, gets their name and episode appearances, then asks for each of their friends.
In summary, it’s great that GraphQL offers an easily way to design these types of relationships; however, it’s still not free to compute these relationships and below I’ll show what solution I came up with on the server implementation.
On the server there is an internal
Unit type object and the
strengths property is actually a list of ids. It finds units matching those ids and then replaces the list of ids with the actual list of units so that the returned object is the complete GraphQL object. How do we optimize the processing to only compute the relationships needed?
Ideally I would inspect the incoming request for the query object and only compute the relationships if the user requested those properties such as:
weaknesses in the query. As far as I can tell this is not easily done. The resolve function does take in the AST (Abstract Syntax Tree) representation of the query, but after debugging and inspecting the object I didn’t see an easy way to determine if the properties I care about are included. (Maybe someone else has a solution for this or knows if that is actually intended use for the AST?)
I ended up using two different changes to solve this.
depthargument to the query with default value of
0and only build relationships up to the depth requested. This isn’t as great because it means users have to opt in to retrieving these and means they must know to set the depth to the level matching their query. (If the query request more levels than the
depththen the properties will just return null)
- Save relationships so they can be recalled on subsequent requests without re-processing. (This is only possible because my entire dataset is in memory and I can modify it so that once the relationship is computed it is saved back to the dataset)
In order to respect the
depth there is a recursive function that calls itself with a decremented depth value until it reaches 0.
Initial call would use the argument from the incoming query such as:
And the function implementation:
Notice that we only compute the relationship if the the property still contains a list of ids and has not already been transformed into a list of objects.
In a normal server implementation where data is stored in external database then this function would still be relatively the same but skip all the
if statements and destructive operations.
It might look like this:
It’s not the most elegant solution since there is coupling between the
depth parameter and the query format, but adding the recursion limit does offer some protection against unnecessarily pre-computing relationships.
Let me know what you think, or what solution you came up with to inspect the incoming query.