Java GraphQL client using Netflix DGS Framework

In this blog post I will explain how to use GraphQL client provided by Netflix DGS framework to call external GraphQL services

In my previous blog posts I have extensively covered on developing GraphQL API with Netflix DGS Framework (part-Ipart-IIpart-III, part-iv). But some times while fulfilling the request, You may need to call Internal/External GraphQL server to get the required data. I will show you how to use GraphQL client provided by Netflix DGS framework

External GraphQL API

For demonstration purpose, I am going to use external public GraphQL service (https://graphql-weather-api.herokuapp.com/) which is returns the temperature for any given city.

Weather GraphQL API provides following 2 methods

  • getCityByName(name: String!, country: String, config: ConfigInput ) : City
  • getCityById(id: [String!], config: ConfigInput) : [City]

Let’s see how you can retrieve the temperature for a city by running a query in GraghiQL client

GraphQL Client

Developing GraphQL client

The DGS framework provides a GraphQL client that can be used to retrieve data from a GraphQL endpoint.

The framework provides following multiple client interfaces and implementations for different needs.

  • GraphQLClient
  • MonoGraphQLClient 
  • ReactiveGraphQLClient 

GraphQLClient – Client interface for blocking implementations. Only recommended when using a blocking HTTP client.

There is no out-of-box implementation for this interface. You have to use custom HTTP client implementation.

MonoGraphQLClient – The same as GraphQLClient, but based on the reactive Mono interface meant for non-blocking implementations. Comes with an out-of-the-box WebClient implementation.

You can use below classes to instantiate MonoGraphQLClient

  • MonoGraphQLClient

ReactiveGraphQLClient – Client interface for streaming responses such as Subscriptions, where multiple results are expected. Based on the reactive Flux interface.

You can use below classes to instantiate Reactive GraphQL client

  • SSESubscriptionGraphQLClient 
  • WebSocketGraphQLClient.

To demonstrate Java GraphQL client example, When ever employee data is retrieved, I am going to return the temperature of city in the response where employee is residing.

I am going to use the previously developed Netflix DGS GraphQL API project from Github .

Step 1 ) Update the Netflix DGS bom version to 4.9.X in pom.xml

             <dependency>
				<groupId>com.netflix.graphql.dgs</groupId>
				<artifactId>graphql-dgs-platform-dependencies</artifactId>				
				<version>4.9.17</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>Code language: HTML, XML (xml)

Step2 ) Include following dependencies in pom.xml

         <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>Code language: HTML, XML (xml)

Step 3) Adding city, country and temperature fields to Employee type in schema.

type Employee {
	id:Int!
	first_name: String!
	last_name: String!
	gender: Gender
	birth_date: Date
	hire_date: Date
	city: String
    country: String
    temperature : Float
    department: Department   
}Code language: JSON / JSON with Comments (json)

Step 5) Add setter and getter methods for new fields in Employee bean

public class Employee {

	private Integer id;
	....
	private String city;
	private String country;
	private Double temperature;
}Code language: Java (java)

Step 6) Developing the GraphQL client.

Th Spring framework’s WebClient implementation is the simplest way to use the DGS GraphQL Client. WebClient is Spring’s suggested HTTP client, and it’s the best option for most scenarios.

Because WebClient is Reactive, the client returns a Mono for all operations.

Since I am only interested in temperature value in the response , in below function I am extracting temperature value based on the json path. If the response is null, extraction throws exception and temperature is not set in emp object.

@DgsQuery
	public Optional<Employee> employee(@InputArgument Integer id) {

		Optional<Employee> employee = emps.stream().filter(e -> e.getId().equals(id)).findFirst();
		
		if( employee.isPresent()) {
			setEmpCityTemp(employee.get());
		}

		return employee;
	}

private void setEmpCityTemp(Employee emp) {

		WebClient webClient = WebClient.create("https://graphql-weather-api.herokuapp.com/");

		WebClientGraphQLClient client = MonoGraphQLClient.createWithWebClient(webClient);

		Map<String, Object> variables = new HashMap<>();


		Map<String, Object> config = new HashMap<>();
		config.put("units","metric");

		variables.put("name" , emp.getCity()) ;
		variables.put("country" , emp.getCountry()) ;
		variables.put("config", config) ;

		String query  = 	 "query GetCityTemp ($name : String!, $country : String, $config: ConfigInput ){"+
									"getCityByName(name :$name, country : $country, config: $config){"+
										"name "+
										"weather { "+
										"temperature { "+
											"actual "+
										"} "+
									"}"+
								"} "+
							"}";

		try {
			Mono<GraphQLResponse> graphQLResponseMono =  client.reactiveExecuteQuery(query,variables);

			Mono<Double> temperature = graphQLResponseMono.map(r ->   r.extractValue("getCityByName.weather.temperature.actual"));
			temperature.subscribe();
			emp.setTemperature(temperature.block());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}Code language: Java (java)

Now let’s test the code. In below image you can observe the temperature in the response.

If you need extract entire response , you can write code like below and convert the json string to desired object type.

Mono<LinkedHashMap> obj = graphQLResponseMono.flatMap(r ->  {

				if(r.extractValue("getCityByName") == null) {
					return Mono.empty();
					
				} else {
					return Mono.just(r.extractValue("getCityByName"));

				}
			} );

			obj.subscribe();

		
			ObjectMapper mapper = new ObjectMapper();
			String jsonResult = "";
			if(obj.blockOptional().isPresent()) {
				

				 jsonResult = mapper.writerWithDefaultPrettyPrinter()
						.writeValueAsString(obj.block());

				System.out.println(jsonResult);
			}Code language: Java (java)

You can also convert the response to object while extracting it

Mono<T> temperature = graphQLResponseMono.map(r ->   r.extractValueAsObject("getCityByName",T.class));Code language: Java (java)

Adding Custom Headers

If the external GraphQL API requires authentication, you can add authentication token in the header . You can also add your own custom headers.

WebClientGraphQLClient client = MonoGraphQLClient.createWithWebClient(webClient, headers  ->  headers.add("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); headers.add("custom header", "custom value"););
Code language: Java (java)

Errors

The GraphQLClient checks both for HTTP level errors (based on the response status code) and the errors block in a GraphQL response. You can access API errors from response error property.

graphQLResponse.errorsCode language: Java (java)

Using your own HTTP client

Instead of using WebClient, you can also use your own HTTP client. This is useful if you already have a configured client for authentication .

There are two interfaces that you can use

  • GraphQLClient: For blocking HTTP clients
  • MonoGraphQLClient: For non-blocking HTTP clients.

Both interfaces return a GraphQLResponse for each query execution, but MonoGraphQLClient wraps the result in a Mono.

You can create client instances by calling factory methods on interface.

These factory methods returns a CustomGraphQLClient or CustomMonoGraphQLClient instance. The implementations are labelled Custom* to signify that you must handle the HTTP request yourself.

Using CustomGraphQLClient

{

		Map<String, Object> variables = new HashMap<>();


		Map<String, Object> config = new HashMap<>();
		config.put("units","metric");

		variables.put("name" , emp.getCity()) ;
		variables.put("country" , emp.getCountry()) ;
		variables.put("config", config) ;

		String query  = 	 "query GetCityTemp ($name : String!, $country : String, $config: ConfigInput ){"+
				"getCityByName(name :$name, country : $country, config: $config){"+
				"name "+
				"weather { "+
				"temperature { "+
				"actual "+
				"} "+
				"}"+
				"} "+
				"}";

		String graphql_endpoint = "https://graphql-weather-api.herokuapp.com/";

		try {
			CustomGraphQLClient client = GraphQLClient.createCustom(graphql_endpoint,  (url, headers, body) -> {
				HttpHeaders httpHeaders = new HttpHeaders();
				headers.forEach(httpHeaders::addAll);
				ResponseEntity<String> exchange = restTemplate.exchange(graphql_endpoint, HttpMethod.POST, new HttpEntity<>(body, httpHeaders),String.class);
				return new HttpResponse(exchange.getStatusCodeValue(), exchange.getBody());
			});

			GraphQLResponse graphQLResponse = client.executeQuery(query, variables, "GetCityTemp");
			Double temp = graphQLResponse.extractValueAsObject("getCityByName.weather.temperature.actual", Double.class);

			System.out.println(temp);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}Code language: Java (java)

Using CustomMonoGraphQLClient

{

		Map<String, Object> variables = new HashMap<>();


		Map<String, Object> config = new HashMap<>();
		config.put("units","metric");

		variables.put("name" , emp.getCity()) ;
		variables.put("country" , emp.getCountry()) ;
		variables.put("config", config) ;

		String query  = 	 "query GetCityTemp ($name : String!, $country : String, $config: ConfigInput ){"+
				"getCityByName(name :$name, country : $country, config: $config){"+
				"name "+
				"weather { "+
				"temperature { "+
				"actual "+
				"} "+
				"}"+
				"} "+
				"}";

		String graphql_endpoint = "https://graphql-weather-api.herokuapp.com/";

		try {
			CustomMonoGraphQLClient client = MonoGraphQLClient.createCustomReactive(graphql_endpoint, (requestUrl, headers, body) -> {
				HttpHeaders httpHeaders = new HttpHeaders();
				headers.forEach(httpHeaders::addAll);
				ResponseEntity<String> exchange = restTemplate.exchange(graphql_endpoint, HttpMethod.POST, new HttpEntity<>(body, httpHeaders),String.class);
				return Mono.just(new HttpResponse(exchange.getStatusCodeValue(), exchange.getBody(), exchange.getHeaders()));
			});


			Mono<GraphQLResponse> graphQLResponseMono  = client.reactiveExecuteQuery(query, variables);
			Mono<Double> temperature = graphQLResponseMono.map(r -> r.extractValueAsObject("getCityByName.weather.temperature.actual",Double.class));

			temperature.subscribe();
			//emp.setTemperature(temperature.block());

			System.out.println(temperature.block());
		} catch (Exception e) {
			e.printStackTrace();
		}

	}Code language: Java (java)

You can download source code for this post from GitHub

Reference

https://netflix.github.io/dgs/advanced/java-client/

Similar Posts