Overview
GraphQL is a popular choice for building APIs as the data retrieval is faster and more efficient. In GraphQL, the parent entity fetches the child entities through various entity relationships. Based on the client's request, the GraphQL resolver decides whether to load a particular child entity or not. The depth of the graph is predictable as the client controls the data they get, not the server. The server can utilize the full advantage of lazy loading that can lead to better performance. Since performance is the key for fetching hierarchical data, graphQL is an ideal API framework for data-centric applications. However, loading certain entity relations is tricky in the query language as the parent entity should know the exact child that it fetches. This article is about solving one such problem.
Problem
Consider a parent entity having a one-to-one relationship with a set of child entities, and the parent entity maps with one child at a time. Here, the child has unique behaviors, and the parent is the common link between various child entities. To understand the problem in detail, let's consider the example below.
The entity diagram shows a typical structure of data in a healthcare domain. Here `Health Records` is the parent entity for all medical divisions having a one-to-one relationship between them. But a single health record can be associated with only one medical division at any time. Here UI need to fetch the patient details along with the health records (Top to down), then it expects GraphQL data in the below format:
{
"data": {
"patient": {
"patientId": 1001,
"name": "Smith",
"healthRecords": [
{
"id": 101,
"date": "01/01/2020",
"diagnosisDetail": {
// details of any one - Orthopaedics | Cardiology | Neurology | ...
}
}
]
}
}
}
The "diagnosisDetail" should have the information corresponding to the medical division. Since the `Health Records` entity only has one child entity per record, reverse mapping all the child entities would not be an optimal solution. Though this approach can fetch the desired child entity, all the remaining medical division entities will be null for a particular record. Consequently, it reduces the performance and readability of the graphql schema.
Solution
First, identify the child entity type in the parent to retrieve it without reverse mapping. The child entity needs to be loaded on demand using a custom GraphQL resolver for the parent entity. Here the context of Java and hibernate is used to demonstrate the solution.
Step 1
In the `Health Records` entity, add a field called `diagnosisType` (define as ENUM), which will be helpful for the custom resolvers to identify the child entity.
Step 2
Define a custom marker interface called `DiagnosisDetail` which will be implemented by all the child entities. This is to store the child's reference using a common parent entity.
Step 3
In the `Health Records` entity, define a Transient object as `DiagnosisDetail` which will be fetched using the custom resolver.
Step 4
In the graphQL entity, the definition uses the `Union` type to define the `DiagnosisDetail`.
union DiagnosisDetail = Orthopaedics | Cardiology | Neurology | Laboratory
Step 5
As a result of using Union, GraphQL queries are written differently. By default, graphQL provides a field called `__typename` to differentiate the type of entity.
fetchHealthRecords(patientId: 1001) {
name
healthRecords {
id
date
diagnosisDetail {
__typename
id
... on Orthopaedics {
#fields in Orthopaedics entity
}
... on Cardiology {
#fields in Cardiology entity
}
... on Neurology {
#fields in Neurology entity
}
... on Laboratory {
#fields in Laboratory entity
}
}
}
}
Step 6
Add a custom graphQL resolver for the `Health Records` entity, which will handle the get query for the `DiagnosisDetail` type. Using the `diagnosisType` value resolver identifies the child and fetches the corresponding child.
public class HealthRecordResolver implements GraphQLResolver<HealthRecord> {
public DiagnosisDetail getDiagnosisDetail(HealthRecord record) {
DiagnosisDetail diagnosisDetail = null;
DiagnosisType diagnosisType = record.getDiagnosisType();
String recordId = record.getId();
Switch(diagnosisType) {
case ORTHOPAEDIC:
diagnosisDetail = orthopaedicRepository.findOneByHealthRecord(recordId);
// Handle other medical divisions
}
record.setDiagnosisDetail(diagnosisDetail);
}
}
Step 7
Let’s assume that the health record belongs to the Orthopaedics department then the UI will receive the `diagnosisDetail` in the below format.
{
"healthRecords": [
{
"id": 101,
"date": "01/01/2020",
"diagnosisDetail": {
"__typename": "Orthopaedics",
"id": 1437,
"doctor": {
"id": 1437,
"name": "Dr. David John"
}
}
}
]
}
Conclusion
GraphQL's declarative approach to data fetching offers significant benefits over REST APIs in terms of performance and developer experience. Effective resolution of child entities is the key to optimizing GraphQL performance since it follows a hierarchical structure. The above approach provides better clarity and consistency for the GraphQL schema though the way of querying is different. Moreover, it gives an abstraction on top of entity relationships.
Talk to us for more insights
What more? Your business success story is right next here. We're just a ping away. Let's get connected.