Using a GraphQL API is a fantastic way to accelerate development. But GraphQL was developed for JavaScript, a non-typed language. .NET app developers can, and should, use GraphQL. HotChocolate is an open source GraphQL library to make it easy to develop a GraphQL endpoint.
But what should you use for developing a dotnet Client to a GraphQL API?
Strawberry Shake
Strawberry Shake is an open-source automated code generation tool from the makers of HotChocolate. Strawberry Shake generates a Client class using your API’s GraphQL schema and your project’s queries/mutations.
The Strawberry Shake generated client is called by a Blazor app, translates those calls to GraphQL, and sends them on to the GraphQL endpoint. The instructions for setting this up are simple and will take less than 10 minutes.
There are many advantages to Strawberry Shake.
- GraphQL queries and mutations are stored in .GraphQL files preserving their whitespace and isolating them away from C#
- The generated client will update automatically with changes to your queries/mutations.
Let’s look a bit deeper into the generated client to see the potential problems of using Strawberry Shake. In some cases, Strawberry Shake will introduce unexpected hurdles. Let’s look at a real example to see the hurdles and solutions.
Beyond The Basic App
My team was updating our FHIR Blaze app from REST to GraphQL. FHIR Blaze is a sample Blazor app that works with HL7 FHIR APIs. FHIR Blaze uses Firely to model the complex HL7 FHIR objects. Our GraphQL provider for HL7 FHIR is Graphir.
Graphir’s GraphQL schema contains this section for getting a Patient:
Patient(id: String!): Patient!
where the complex Patient type is defined as:
type Practitioner {
id: String
identifier: [Identifier]
active: Boolean
name: [HumanName]
language: String
gender: AdministrativeGender
birthDate: String
telecom: [ContactPoint]
address: [Address]
photo: [Attachment]
communication: [CodeableConcept]
qualification: [QualificationComponent]
}
Now let's say our app stores all its Patient Queries in Patient.GraphQL that looks like
query getPatientID:Patient(id:"123"){
id
}
query getPatientIDGender:Patient(id:"123"){
id gender
}
}
Strawberry Shake generates a 1,128 line Client for us. But there's a problem.
Calling Client.getPatientID("123"), we don't get back a Patient object. Instead we get back a custom result object with an id field of type string.
Similarly Client.getPatientIDGender(123) returns a different custom object with two string fields: id and gender.
Our Blazor App has components that need an HL7 FHIR Patient object as input. We need a Patient object..
But don’t worry we have some solutions!
Custom GraphQL Request/Response
We can create custom GraphQL Request / Response objects.
Example GraphQL Request Object.
Example GraphQL Response Object.
Now from our custom client we can generate a Request object with a GraphQL query.
public async Task<Patient> GetPatientAsync(string id)
{
GraphQLRequest request = new GraphQLRequest(_httpClient)
{
OperationName = "PatientList",
Query = @"query getPatientID{
Patient(id:"123"){
id
}
}"
};
GraphQLResponse response = await request.PostAsync();
return _fhirParser.Parse<Patie(response.RootElement.ToString()));;
}
Now when we call getPatient("123") we get back a Patient object!
There are advantages to this approach:
- Reuse the complex objects you already made. In our case we’re reusing the HL7 FHIR objects
- No reliance on black box code.
- Source tree is cleaner. Strawberry Shake is determensitic, but if you make a small change to your graphql queries you can expect hundreds of lines changed in the generated client. This will make diffs between versions giant.
There are also disadvantages:
- You own the code you wrote. You now support it.
- GraphQL queries / mutations are sprinkled throughout your code.
Hybrid approach.
A final approach is to use Strawberry Shake to retrieve fields and then rehydrate complex objects with the contents.
For example:
public Patient getPatient(string id){
var psuedoPatient= Client.getPatient(id);
string json=System.Text.JsonJsonSerializer.Serialize(psuedoPatient);
return _fhirParser.Parse<Patient>());
}
This might seem like the obvious solution, but this may not work for complex objects. For example, a Patient object can have a Practitioner object embedded in it. To rehydrate the Patient, you’d first have to rehydrate the embedded Practiioner object, and any complex objects under it. An example we ran into during FHIR Blaze we cannot rehydrate a list of Patients directly, instead we need access to the underlying json. You can see the code here.
What should I use?
If you’re developing an app and you have the code that represents the complex objects you’ll retrieve from your API: use custom coded classes.
Strawberry Shake also has other features that we did not cover here (including caching, support for subscriptions, etc). There are advantages and disadvantages of this approach. You should use which ever approach increases your agility, after all that’s why you’re using GraphQL.