Skip to content

client_exec_graphql_requests

etienne-sf edited this page Jun 22, 2024 · 23 revisions

Client mode usage description

The client mode makes it easy for a Java GraphQL client-side application, to execute queries/mutations/subscriptions against a GraphQL server. The graphql-maven-plugin generates all the necessary code, so that a Java application can call a GraphQL server by simply calling the relevant Java method.

The plugin manages two kinds of request:

  • The Full request: it's actually a standard GraphQL request, that you can test within graphiql
  • The Partial request: it's done through a java method that executes one of the queries/mutations/subscriptions defined in the schema. This java method accepts one parameter for each parameter of this query/mutation/subscription, and it returns directly the Java type that maps this query/mutation/subscription.

It manages two ways of executing the request:

  • The direct execution: you call the generated method with the GraphQL request (partial or full), and you receive the result into Java objects. This is simpler, but slower: for technical reasons, the plugin has two analyze the content of the request. And it will do that at each execution. The main reason for that is to allow proper deserialization of GraphQL interfaces and unions: the __typename is injected into the query, for all returned object, union and interface types.
  • The recommended prepared execution:
    • A GraphQLRequest object is created by the application. This allows to analyze the request only once. If you create these GraphQLRequest at application startup, then the syntax control is done once for every requests at startup. This avoids to have errors occurring later, during the app execution.
    • Each GraphQL request execution is executed from this object.
    • Note: the GraphQLRequest object has been created in the 1.6 release. The prepared object was before stored into a ObjectResponse. This ObjectResponse has been maintained when used with the withQueryResponseDef Builder method, and the code that uses will continue to work. Support for other Builder method has been removed. There is no plan yet to remove the ObjectResponse object and the withQueryResponseDef Builder method. But they should be avoided in new code.

Both kinds of requests, and both modes of execution allows to use:

  • Bind parameters into your queries/mutations/subscriptions (see below for more information on that). Bind parameters are an extension that is proper to this plugin. They allow to map a field's argument (including query/mutation/subscription) to a Java value. It can be mandatory (starting by '&') or optional (starting by '?').
  • GraphQL variables respect the GraphQL spec. This allow to map a field's argument (as bind parameters) or just a part of it (for instance a field of an Input Type). But it must respect strict syntax rules.
    • GraphQL variables are only available for full requests, as they must be declared with the request name

Full requests versus Partial requests

When the plugin generates the query/mutation/subscription java classes (one for each), it generates two families of methods: Full requests, and Partial requests, as introduced on the top of this page.

In either case, you can call query and mutation in the exact same way. You'll only find queries, in the samples below. But mutations work the same.

Full requests

Full requests are only valid for queries and mutations. Subscriptions work only with Partial Request. See the Client Subscription page.

For each query/mutation/subscription in the GraphQL schema, a Java class is generated.

The exec and execWithBindValues methods allows you to execute full GraphQL queries, that is: the full GraphQL request, as it would work in the graphiql interface. So you can test your request in graphiql, then parse the tested request into you code, as a Full Request (see below for sample).

This is interesting when:

  • You want to execute several queries into one call toward the server
  • You want to add directives to the query/mutation itself
  • You want to use GraphQL global fragments into your query (by global, we mean not inline fragment)

You'll find samples in the sample projects, in the test part of the client samples.

To use full request, you need to (sample follows):

  • Configure the GraphQLRequest
    • To do this, you must create a GraphQLConfiguration. This configuration is essentially defining the GraphQL endpoint
    • If you have only one GraphQL server, you can use the default GraphQLRequest, by calling its static setStaticConfiguration(GraphQLConfiguration) method
    • If you work with more than one GraphQL server, you can use the setStaticConfiguration(GraphQLConfiguration) to set a default configuration, and call the setInstanceConfiguration(GraphQLConfiguration) method for instance of GraphQLRequest that would call another server.
    • Create on GraphQLRequest with your GraphQL query
    • Then execute one or several time the query, possibly with different bind parameter values.
    • Retrieves from the query/mutation response, the field the contains the structure for the query(ies)/mutation(s) you called. All the server response is stored in POJOs that have been generated from the GraphQL schema, including object types, input types, enums, interfaces, unions (as Java interfaces)...

To execute the request, you must use the execQuery/execMutation method of your GraphQLRequest instance, to get the proper response. This response is the query/mutation response type. So, to get the real result from the server, you must call the getter that is link to your request, getBoards() in the above sample.

In the GraphQL query, you can use:

  • Fragments (inline or not)
  • Directives
  • Union, or interface types
  • input parameters for query/mutation and field input parameters, or in directives arguments. The given value for these input parameters may be:
    • Hard coded value in your GraphQL query. That is: the literal value for the input parameter, like for the memberName parameter in the above sample.
    • A GraphQL variable. It must be a valid identifier, prefixed with '$'. It must be declared (see the GraphQL spec or the above sample) with the exact GraphQL type, according to where it will be used later.
    • An optional bind parameter, if it's a valid identifier, prefixed with ?, like the memberIdParam parameter, here above. In this case, if you don't provide a value for this parameter at execution time, the parameter is not sent to the server. Of course, if this parameter is mandatory in your GraphQL schema, you'll get an error from the GraphQL server.
    • A mandatory bind parameter, if it's a valid identifier, prefixed with &, like the sinceParam parameter, here above. In this case, if you don't provide a value for this parameter at execution time, the plugin throws a GraphQLRequestExecutionException at execution time, as it expects a value for this parameter. A mandatory parameter in your query may be an optional one in the GraphQL schema: it's up to your use case to define if this parameter is mandatory or not.

Please note that the bind parameters and GraphQL variables can be provided by one of these two ways:

  • As a map, where the key is the parameter name, by using the xxxWithBindValues(GraphQLRequest, [xxx], Map), where [xxx] is the list of values for the query/mutation.
    • For instance: request.execQuery(params) (see below for a definition of this params)
  • As a list of pairs of name and values
    • For instance: request.execQuery("sinceParam", since, "memberName", memberName)

Here is a code sample:

// This class is loaded as Java Bean. So mark it with the @Component
@Component
public class MyClass {

	// Spring will autowired the query executor at initialization time
	@Autowired
	QueryExecutor myQuery;
	
	GraphQLRequest request;
	
	// Once Spring has autowired the executor, we can prepare the requests
	@PostConstruct
	void init() {
			// Create the GraphQL request, with a mix of :
			// - A GraphQL variable ($memberName) that must be declared, in the named query "query1"
			// - A mandatory Bind Parameter: &sinceParam
			// - An optional Bind Parameter: ?memberIdParam
			request = myQuery.getGraphQLRequest("" //
					+ "fragment topic on Topic {title posts(since: &sinceParam, memberId: ?memberIdParam, memberName: $memberName){id} author{id}} "
					+ "query query1($memberName: String){boards{id name topics {id ...topic}}}");
	}
	
	void exec(Date since, String memberName) {
		// This sample :
		// - gives a value for the GraphQL variable (as it starts with $) memberName
		// - gives a value for the mandatory bind parameter (as it starts with &) sinceParam
		// - gives no value for the optional bind parameter (as it starts with ?) memberIdParam
		// So only the since and the memberName bind parameters will be sent to the server. 
		List<Board> boards = request.execQuery("sinceParam", since, "memberName", memberName).getBoards();
	
		// Do something with boards
		...
	}
}

The Bind Parameters and GraphQL variables can also be provided as a map:

// This class is loaded as Java Bean. So mark it with the @Component
@Component
public class MyClass {

	// Spring will autowired the query executor at initialization time
	@Autowired
	QueryExecutor myQuery;

	@PostConstruct
	void init() {
		request = myQuery.getGraphQLRequest("" //
				+ "fragment topic on Topic {title posts(since: &sinceParam, memberId: ?memberIdParam, memberName: $memberName){id} author{id}} "
				+ "query query1($memberName: String){boards{id name topics {id ...topic}}}");
	}
	
	void exec(Date since, String memberName, String memberId) {
		Map<String, Object> params = new HashMap<>();
		params.put("sinceParam", since);
		params.put("memberName", memberName);
		
		// This sample :
		// - gives a value for the mandatory (as it starts with &) sinceParam bind parameter,
		// - gives no value for the optional (as it starts with ?) memberIdParam bind parameter.
		// So only the since and the memberName bind parameters will be sent to the server. 
		List<Board> boards = request.execQuery(params).getBoards();
	
		// Do something with boards
		...
	}
}

Partial requests

For each query/mutation/subscription class, the plugin also generates XxxxEXecutor classes, where Xxxx is the query/mutation/subscription GraphQL type name. These classes contain Yyyy and YyyyWithBindValues methods, where Yyyy is successively each query/mutation/subscription defined in this query/mutation/subscription object.

As subscriptions work differently, they are documented in the Client Subscription page.

These methods are easier to use as:

  • The query/mutation/subscription parameters (as defined in the GraphQL schema) become parameters of the relevant generated java methods. So you don't need to define and map bind parameters for them.
    • You can still use bind parameters for input parameters of the fields you request
    • You can not use GraphQL variables with Partial Requests, as these variables must be declared in the GraphQL query
  • The methods return directly the response for the query/mutation/subscription: you don't need to call a getter to retrieve it. The returned data is stored in the POJOs that have been generated from the GraphQL schema. This includes the object types, input types, enums, interfaces, unions (as Java interfaces)...

Below is a sample of the client code:

@Component
public class MyClass {

	@Autowired
	QueryExecutor queryExecutor;
	
	GraphQLRequest topicsRequest;

	@PostConstruct
	void init() {
		// This prepares a GraphQL Request that will execute: topics{id date author{name email alias id type} nbPosts}
		topicsRequest = queryExecutor.getTopicsGraphQLRequest("{id date author{name email alias id type} nbPosts}");
	}
	
	void exec() {
		// The topic query accepts one parameter. If you don't want to provide it, you can give it the null value
		List<Topic> topics = queryExecutor.topics(topicsRequest, "a board name");
	
		// Do something with topics
		...
	}
}

This method needs less code, in your application.

Directives can be defined for the requested fields, or their parameters. If you need to define directives at the query/mutation/subscription level, you need to execute a Full Request (see here above).

The bind parameters works as specified for the Full queries.

Of course, you can use input parameters for field or directives.

Below is a sample of a request, with bind parameters for a directive:

@Component
public class MyClass {

	@Autowired
	QueryExecutor queryExecutor;
	
	GraphQLRequest topicsRequest;

	@PostConstruct
	void init() {
		// ?memberId is an optional bind parameter, of name "memberId"
		// ?memberName is an optional bind parameter, of name "memberName"
		// ?sinceParam is an optional bind parameter, of name "sinceParam": you must provide it at execution time, or
		// a GraphQLRequestExecutionException will be thrown.
		topicsRequest = queryExecutor.getTopicsGraphQLRequest("{id date author{name email alias id type} nbPosts "
			+ "posts(memberId:?memberId, memberName: ?memberName, since: &sinceParam){id date author{name @yourDirective(param: &theDirParam) email alias}}}");
}

	void exec(Date since, String memberName, String memberId, boolean theParam) {
		// The topic query, as defined in the GraphQL schema, has one parameter: the board name.
		// This parameter is a parameter of the topics method
		// You can then as many couples ("bindParameterName", bindParameterValue) that you want, in any order
		List<Topic> topics = queryExecutor.topics(topicsRequest, "a board name", 
			"sinceParam", since, 
			"memberName", memberName, 
			"memberId", memberId,
			"theDirParam", theParam);
	
		// Do something with topics
		...
	}
}

Please note that the bind parameters can be provided as a map, where the key is the parameter name, by using the xxxWithBindValues(GraphQLRequest, [xxx], Map), where [xxx] is the list of values for the query/mutation/subscription.

The previous sample then becomes:

@Component
public class MyClass {

	@Autowired
	QueryExecutor queryExecutor;
	
	GraphQLRequest topicsRequest;

	@PostConstruct
	void init() {
		// ?memberId is an optional bind parameter, of name "memberId"
		// ?memberName is an optional bind parameter, of name "memberName"
		// ?sinceParam is an optional bind parameter, of name "sinceParam": you must provide it at execution time, or
		// a GraphQLRequestExecutionException will be thrown.
		topicsRequest = queryExecutor.getTopicsGraphQLRequest("{id date author{name email alias id type} nbPosts "
			+ "posts(memberId:?memberId, memberName: ?memberName, since: &sinceParam){id date author{name @yourDirective(param: &theDirParam) email alias}}}");
	}

	void exec(Date since, String memberName, String memberId, boolean theParam) {
		// The topic query, as defined in the GraphQL schema, has one parameter: the board name.
		Map<String, Object> params = new HashMap<>();
		params.put("sinceParam", since);
		params.put("memberName", memberName);
		params.put("memberId", memberId);
		params.put("theDirParam", theParam);
		
		// The topic query, as defined in the GraphQL schema, has one parameter: the board name
		// This parameter is a parameter of the topics method
		List<Topic> topics = queryExecutor.topicsWithBindValues(topicsRequest, "a board name", params);
	
		// Do something with topics
		...
	}
}

Use direct queries

If you don't want to store the GraphQLRequest, you can avoid that, by using direct queries. But please note that there is an overhead, as the query must be analyzed at runtime by the plugin to manage bind parameters, and add the __typename GraphQL introspection attribute where it is missing, to insure proper java deserialization of the response.

So using direct queries generates an overhead at each execution, whereas with prepared queries, the request analysis is done only once.

Direct queries are available only for partial queries. The input parameters for fields and directive are provided the same way as for full queries:

  • With a list of couples ("bindParameterName", bindParameterValue)
  • Or with a map containing this parameter values, where the key is the bindParameterName, and the value is the bind parameter value.

Here is a sample with the bind parameters value given as method parameters:

@Component
public class MyClass {

	@Autowired
	QueryExecutor queryExecutor;

	void exec(Date since, String memberName, String memberId) {		
		// The topic query accept one parameter: the board name
		// You can then as many couples ("bindParameterName", bindParameterValue) that you want.
		String request = "{id date author{name email alias id type} nbPosts "
				+ "posts(memberId:?memberId, memberName: ?memberName, since: &sinceParam){id date author{name email alias}}}";
		List<Topic> topics = queryExecutor.topicsWithBindValues(request, "a board name", 
				// Then the parameters name and value: 
				"sinceParam", since,
				"memberName", memberName,
				"memberId", memberId,
				);
	
		// Do something with these topics
		...
	}
}

Here is a sample with the bind parameters value given as a map:

@Component
public class MyClass {

	@Autowired
	QueryExecutor queryExecutor;

	void exec(Date since, String memberName, String memberId) {	
		Map<String, Object> params = new HashMap<>();
		params.put("sinceParam", since);
		params.put("memberName", memberName);
		params.put("memberId", memberId);
		
		// The topic query accept one parameter.
		// You can then as many couples ("bindParameterName", bindParameterValue) that you want.
		String request = "{id date author{name email alias id type} nbPosts "
				+ "posts(memberId:?memberId, memberName: ?memberName, since: &sinceParam){id date author{name email alias}}}";
		List<Topic> topics = queryExecutor.topicsWithBindValues(request, "a board name", params);
	
		// Do something with these topics
		...
	}
}

HowTo retrieve the extensions GraphQL response field

This field is an optional field, described in the GraphQL spec. It contains a Map, and the values for this map is free, and may be anything, as choosed by the GraphQL server implementation.

To retrieve its value, you can do, for instance:

@Component
class AClass {
	
	@Autowired
	MyQueryExecutor myQuery;
		
	public void doSomething() {
		// Retrieve the result as a full query
		MyQuery resp = myQuery.exec("{directiveOnQuery}"); 
		
		// You can then retrieve the whole extensions field as a map
		Map<String, JsonNode> map = resp.getExtensionsAsMap();
		
		// Or retrieve just a value, from a key. This uses Jackson to deserialize 
		// the jsonNode into the target class for this key
		YouClass value = resp.getExtensionsField("YourKey", YourClass.class);
		
		... Do something useful
	}
}

Use of Bind parameters and GraphQL variables to map Java variables into GraphQL requests

As described here above, the plugin accepts:

  • GraphQL variables, as described in the GraphQL spec
  • Bind parameters

They are described here above, with samples.

You'll find a comparison of them in the project's wiki

Use of GraphQL alias

Since the release 1.15, you can use alias in GraphQL queries, mutations and subscriptions. The syntax is described in the GraphQL spec.

You can use them in either Partial or Full Requests, and Direct or Prepared requests.

The value received in the GraphQL response is parsed into the relevant Java type, stored in a Map, and can be retrieved with the Object getAliasValue(String alias) method, in replacement of the getter to use for a regular field.

Here are two samples:

First with a Partial request:

	void partialRequestWithAlias(List<FieldParameterInput> inputs) throws GraphQLRequestPreparationException, GraphQLRequestExecutionException {
		GraphQLRequest graphQLRequest = queryType
				.getAllFieldCasesGraphQLRequest("{alias65:issue65(inputs: &inputs) issue65(inputs: &inputs)}");
		List<AllFieldCasesWithoutIdSubtype> issue65 = queryType.allFieldCases(graphQLRequest, null, "inputs", inputs).getIssue65();
		
		// Let's get the alias value
		List<AllFieldCasesWithoutIdSubtype> alias65 = (List<AllFieldCasesWithoutIdSubtype>) ret
				.getAliasValue("alias65");
				
		... Let's do something useful with this value
	}

And with a Full request:

	void fullRequestWithAlias() throws GraphQLRequestPreparationException, GraphQLRequestExecutionException {
		// Preparation
		GraphQLRequest multipleQueriesRequest = queryType.getGraphQLRequest("{"//
				+ " withOneOptionalParam {aliasId:id id aliasName:name name aliasAppearsIn:appearsIn appearsIn aliasFriends:friends {id name} friends {aliasId:id id aliasName:name name aliasFriends:friends {id name} friends {aliasId:id id aliasName:name name}}}"//
				+ " queryAlias:withOneOptionalParam  {aliasId:id id aliasName:name name aliasAppearsIn2:appearsIn appearsIn aliasFriends:friends {id name} friends {aliasId:id id aliasName:name name aliasFriends:friends {id name} friends {aliasId2:id id aliasName2:name name}}}"//
				+ "}");

		// Go, go, go
		MyQuery resp = multipleQueriesRequest.execQuery( //
				"value", "An expected returned string", //
				"skipAppearsIn", true, //
				"skipName", false);
		Character queryAlias = (Character) resp.getAliasValue("queryAlias");
		String aliasId = (String) queryAlias.getAliasValue("aliasId");
		List<Character> friends = (List<Character>) queryAlias.getAliasValue("aliasFriends");
		String aliasIdOfFirstFriend = (String) queryAlias.getFriends().get(0).getAliasValue("aliasId");
		
		... Do something with the response for this query
	}
Clone this wiki locally