GraphQL Integration Microservice/Monolith Architecture

Introduction

 
GraphQL is an open-source data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data. GraphQL was developed internally by Facebook in 2012 before being publicly released in 2015.
 
It is mostly useful in scenarios where we have rapidly changing entities and it is a huge task to change the whole flow, the entities in the front-end, entities in the Web API end, Web API controller, Repository classes. Many times, developers spend a lot of time figuring out why they are not able to receive data to the UI layer from the service layer, even though the database connectivity is fine and data is coming up to the Data Layer. The main reason most of the time is that the model definition is mismatched between the service layer and the UI layer. To avoid such issues, GraphQL is a perfect fit and almost a blessing for developers.
 
In this article, I will try to explain GraphQL integration in a microservice architecture, but the same code can be used for monolith architecture as well.
 
To build the basic microservice architecture using .NET core (ASP.NET core MVC, ASP.Net Core Web API) refer to this tutorial,
 
To integrate GraphQL in a .NET core project, refer here.
 
This is how my microservice project looks:
 
 
I have integrated GraphQL in the ProductService:
 
 
 
Please ignore GraphQLParameter folder in the above screenshot as of now, I will be explaining about it later in the article.
 
I have created two GraphQL types which are referring to the two entity framework entities in my project:
 
 
When the ProductService WebAPI is run, the following is how the GraphQL playground looks:
 
 
 
 
If you wish to see the fields in each type, just click on the small arrow on the right,
 
Many times, it so happens that we don’t want to use the property names that GraphQL generates for the type, in such a scenario, you can provide your own name:
  1. namespace ProductService.GraphQL.GraphQLTypes {  
  2.     public class ProductReviewType: ObjectGraphType < ProductReview > {  
  3.         public ProductReviewType() {  
  4.             Field(x => x.ProductReviewId, type: typeof(IdGraphType)).Description("some desc here");  
  5.             Field(x => x.ProductId).Description("some desc here");  
  6.             Field("reviewername", x => x.ReviewerName).Description("some desc here");  
  7.             Field("reviewdate", x => x.ReviewDate).Description("some desc here");  
  8.             Field("emailaddress", x => x.EmailAddress).Description("some desc here");  
  9.             Field("rating", x => x.Rating).Description("some desc here");  
  10.             Field("comments", x => x.Comments).Description("some desc here");  
  11.             Field("modifieddate", x => x.ModifiedDate).Description("some desc here");  
  12.         }  
  13.     }  
  14. }  
The above type would look like the following in the playground:
 
 
 
It is easy to write a query in the playground and get the result, but that is not what we want in a practical scenario. We need an endpoint where the query can be sent to and results can be received. The following ActionResult can help us getting that done:
  1. [HttpPost]  
  2. [Route("GraphQLPostQuery")]  
  3. public async Task < IActionResult > GraphQLPostQuery([FromBody] GraphQLParameter query) {  
  4.     if (query == null) {  
  5.         throw new ArgumentNullException(nameof(query));  
  6.     }  
  7.     var executionOptions = new ExecutionOptions {  
  8.         Schema = _schema,  
  9.             Query = query.Query  
  10.     };  
  11.     var result = await _documentExecuter.ExecuteAsync(executionOptions).ConfigureAwait(false);  
  12.     if (result.Errors?.Count > 0) {  
  13.         return BadRequest(result.Errors);  
  14.     }  
  15.     return Ok(result);  
  16. }  
Where GraphQLParameter is the class that is implemented to receive the query string:
  1. public class GraphQLParameter {  
  2.     public string Query {  
  3.         get;  
  4.         set;  
  5.     }  
  6. }  
Now if you run postman and send the following post request:
 
 
You should get back the result:
 
 
Now we want to send this request from the UI layer. In my case, I have created an ASP.NET core MVC project. Refer here to see how to consume Web API in an ASP.NET core MVC project.
 
Following is my controller at the UI layer:
  1. public class ProductReviewController: Controller {  
  2.     private IApiClient apiProxy;  
  3.     public ProductReviewController(IApiClient apiClient) {  
  4.         apiProxy = apiClient;  
  5.     }  
  6.     public async Task < IActionResult > Index() {  
  7.         List < Review > lstreview;  
  8.         Review obj = new Review();  
  9.         string gqlQuery = GraphQLQueryBuilder.QueryBuilder(obj);  
  10.         lstreview = await apiProxy.PostAsyncGraphQL < Review > ("product/GraphQLPostQuery"new StringContent(gqlQuery, Encoding.UTF8, "application/json"));  
  11.         return View(lstreview);  
  12.     }  
  13. }  
GraphQLQueryBuilder is the class implemented that will create queries for GraphQL from the metadata of your model object:
  1. public static class GraphQLQueryBuilder {  
  2.     public static string QueryBuilder(object obj) {  
  3.         string objName = obj.GetType().Name.ToString().ToLower();  
  4.         var props = obj.GetType().GetProperties().ToList();  
  5.         string strQuery = "{\"query\": \"{" + objName + "{";  
  6.         foreach(PropertyInfo prop in props) {  
  7.             strQuery = strQuery + prop.Name.ToString().ToLower() + ",";  
  8.         }  
  9.         strQuery = strQuery.TrimEnd(',');  
  10.         strQuery = strQuery + "}}\"}";  
  11.         return strQuery;  
  12.     }  
  13. }  
So if the following is the definition of your model object:
  1. public class Review: BaseModel {  
  2.     [JsonProperty(PropertyName = "reviewerName")]  
  3.     public string reviewerName {  
  4.         get;  
  5.         set;  
  6.     }  
  7.     public DateTime reviewDate {  
  8.         get;  
  9.         set;  
  10.     }  
  11.     public string Comments {  
  12.         get;  
  13.         set;  
  14.     }  
  15.     public string EmailAddress {  
  16.         get;  
  17.         set;  
  18.     }  
  19. }  
Then with the help of GraphQLQueryBuilder, the following query is built:
 
 
If you want to add new properties to the above query, just add the properties to the model class (both in UI layer and service layer) and GraphQLQueryBuilder and GraphQL will take care of the rest.
 
Below is the implementation of the PostAsyncGraphQL method:
  1. public async Task < List < T >> PostAsyncGraphQL < T > (string requestUrl, StringContent stringContent) {  
  2.     //addHeaders();  
  3.     var response = await _httpClient.PostAsync(BaseEndpoint + requestUrl.ToString(), stringContent);  
  4.     response.EnsureSuccessStatusCode();  
  5.     var data = await response.Content.ReadAsStringAsync();  
  6.     var data_actual = data.Substring(data.LastIndexOf('[')).Trim('}');  
  7.     return JsonConvert.DeserializeObject < List < T >> (data_actual);  
  8. }  

API Gateway changes to support GraphQL queries

 
This section is valid only if you are using GraphQL in a microservice architecture. In order to send the GraphQL query to API Gateway first and then to the ProductService, make the following change to ocelot.json file:
  1. {  
  2.     "DownstreamPathTemplate""/api/product/{everything}",  
  3.     "DownstreamScheme""http",  
  4.     "DownstreamHostAndPorts": [{  
  5.         "Host""localhost",  
  6.         "Port": 54081  
  7.     }],  
  8.     "UpstreamPathTemplate""/api/product/{everything}",  
  9.     "UpstreamHttpMethod": ["Post"]  
  10. }  
The code for this project is available in the following GitHub repository.
 
A Word of Caution:
 
Though GraphQL is flexible, when used with entity framework, a drawback is that even though I am selecting 2 properties out of 10 properties in the model class, EF will still select all the properties from the database. So it is recommended that once the model definitions are finalized over the course of development, make sure the definitions are uniform across the layers.