-
Notifications
You must be signed in to change notification settings - Fork 48
client_graphql_repository
- GraphQL Repositories
- Rules to respect, to define a GraphQL Repository
- Define your GraphQL Repository
- Execute GraphQL requests defined in a GraphQL Repository
- Using GraphQL Repositories with Spring againsy several GraphQL servers
Starting with release 1.17, you can use GraphQL Repositories.
GraphQL Repositories is an adaptation of Spring Repositories, for GraphQL request.
The idea is to write no code at all for the GraphQL request execution: just create a Java interface that defines the GraphQL queries, mutations and subscriptions you need. The runtime will take care of all the execution stuff.
Here is a sample:
import org.allGraphQLCases.client.Character;
import org.allGraphQLCases.client.Human;
import org.allGraphQLCases.client.HumanInput;
import com.graphql_java_generator.annotation.RequestType;
import com.graphql_java_generator.client.graphqlrepository.BindParameter;
import com.graphql_java_generator.client.graphqlrepository.FullRequest;
import com.graphql_java_generator.client.graphqlrepository.GraphQLRepository;
import com.graphql_java_generator.client.graphqlrepository.PartialRequest;
import com.graphql_java_generator.exception.GraphQLRequestExecutionException;
@GraphQLRepository
public interface MyGraphQLRepository {
@PartialRequest(request = "{appearsIn name}")
List<Character> withoutParameters() throws GraphQLRequestExecutionException;
@PartialRequest(request = "{id name}", requestType = RequestType.mutation)
Human createHuman(HumanInput human) throws GraphQLRequestExecutionException;
@FullRequest(request = "subscription {subscribeToAList {}}", requestType = RequestType.subscription)
SubscriptionClient subscribeToAList(SubscriptionCallback<?> callback) throws GraphQLRequestExecutionException;
}
This GraphQL Repository can be used as described below, in a Spring application:
@Component
public class MyCode {
@Autowired
MyGraphQLRepository myRepo;
public void doSomething() throws Exception {
List<Character> chars = myRepo.withoutParameters();
// Do something with chars
}
}
And that's all: you don't write any boilerplate code for the GraphQL request execution.
Importante notices:
- When in Spring app (like in the sample above), you must enable the GraphQL Repositories in the Spring Context. See below how to use GraphQL Repositories in Spring apps.
- When in non-Spring app, you will have to copy/paste two lines of codes to build the dynamic proxy that will execute the GraphQL queries. See below how to use GraphQL Repositories in non-Spring apps.
You may define as may GraphQL Repositories as you want:
- For Spring app, they all will be discovered provided that their package (or parent package) are provided in the
@EnableGraphQLRepositories
annotation - For non-Spring app, the proxy must be created by a call to
Proxy.newProxyInstance(..)
(see below for the details)
A GraphQL Repository may:
- Mix partial requests and full requests
- Mix queries, mutations and subscriptions
A GraphQL Repository must:
- Contain only GraphQL methods, that is: its method must be annotated by either
@PartialRequest
or@FullRequest
:- The
@PartialRequest
allows these parameters (see below for details):- requestName: optional if the method name is exactly the same as the GraphQL query/mutation/subscription name in the GraphQL schema. Mandatory if the method name is different.
- request: the request string, as explained for Partial Request in the client execution page. This request may contain Bind Parameters.
- requestType: optional if the request is a query. Mandatory otherwise. It contains one of the item of the
RequestType
enumeration (query, mutation or subscription)
- The
@FullRequest
annotation allows these parameters:- request: the request string, as explained for Partial Request in the client execution page. This request may contain Bind Parameters.
- requestType: optional if the request is a query. Mandatory otherwise. It contains one of the item of the
RequestType
enumeration (query, mutation or subscription)
- The
Each method of a GraphQL Repository must respect these rules:
- It returns either the same type as defined in the GraphQL schema (for query and mutation) or a
SubscriptionClient
(for subscription):- For Partial Request query or mutation, it's the type of the query or mutation field
- For Partial Request subscription, it's a
SubscriptionClient
- For Full Request query or mutation, it's the query or mutation type.
- For Full Request subscription, it's a
SubscriptionClient
(the full request may then contain only one subscription)
- For Partial Requests only:
- The first parameters must be the parameters of the specified query or mutation, in the exact same order as defined in the GraphQL schema, and with not java annotation (more info later on this page).
- Their type must be the type defined in the GraphQL schema, for the relevant parameter.
- Please take care to these points :
- The
Float
GraphQL type maps to theDouble
java type (not the Float java type) - The return type of a method may not be the java scalar type (int, double, boolean). It must be the class type (Integer, Double, Boolean).
- The
- (Optionally) The method defines one parameter for each Bind Parameter or GraphQL variable that is defined in the provided GraphQL request (see the client execution page for more information on Bind Parameters and GraphQL variables)
- These method parameters must be marked by the
@BindParameter
java annotation (see below)
- These method parameters must be marked by the
- It throws exactly one exception:
GraphQLRequestExecutionException
The first step is to activate the GraphQL capability. To do this, you have to annotate a Spring Configuration class with the EnableGraphQLRepositories
annotation:
import org.springframework.context.annotation.Configuration;
import com.graphql_java_generator.client.graphqlrepository.EnableGraphQLRepositories;
@Configuration
@EnableGraphQLRepositories({ "org.allGraphQLCases.demo.impl", "org.allGraphQLCases.subscription.graphqlrepository" })
public class SpringTestConfig {
}
The parameter for this annotation is the list of package names where Spring should look for GraphQL Repositories.
The GraphQL Repositories are interfaces annotated with the GraphQLRepository
annotation, like this:
import com.graphql_java_generator.client.graphqlrepository.GraphQLRepository;
@GraphQLRepository
public interface FullRequestSubscriptionGraphQLRepository {
... Write GraphQL requests here
}
Note: if Spring doesn't find your GraphQL Repositories, check the EnableGraphQLRepositories
annotation, and the provided package names.
When in non-Spring application, the automagic Spring discovery doesn't work. It remain simple to use the GraphQL Repository. You create a proxy class for each of you GraphQL Repositories this way:
import java.lang.reflect.Proxy;
..
// If MyGraphQLRepository is your GraphQL Repository interface :
GraphQLRepositoryInvocationHandler<MyGraphQLRepository> invocationHandler =
new GraphQLRepositoryInvocationHandler<MyGraphQLRepository>(MyGraphQLRepository.class,
queryExecutor, mutationExecutor, subscriptionExecutor);
MyGraphQLRepository myGraphQLRepository =
(MyGraphQLRepository) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] {MyGraphQLRepository.class}, invocationHandler);
// You can then execute GraphQL by using the created proxy:
MyGraphQLType response = myGraphQLRepository.myGraphQLRequest(param1, param2);
Full Request are easy to define.
When defining a method that is mapped to a GraphQL Full Request (that is: a regular GraphQL query, as you can test it with graphiql), it must respect these conditions:
- The method must be annotated by the
@FullRequest
java annotation. This annotation has these parameters:- The GraphQL request string, in the
request
parameter. This request is typically a copy/paste of a request tested with graphiql, with possibly Bind Parameters and GraphQL variables (see the client execution page for more information on this) - The GraphQL request type, in the
requestType
parameter. This parameter is optional, and may be omitted if your request is a query one. - (for query and mutation) The request may contain any number of queries or mutations
-
(for subscription only) The request may contain only one subscription. This is because you must provide its callback, and the method returns the
SubscriptionClient
for this subscription.
- The GraphQL request string, in the
- The method must have one parameter per Bind Parameter and GraphQL variable defined in the request query
-
(for subscription only) The first parameter must be a
SubscriptionCallback<Xxxx>
, where Xxxx is the type of the subscription field, that is: the GraphQL type that will be sent for each subscription notification. - You can then add any number of method parameters. These parameters must map to a Bind Parameter or GraphQL variable. As such, they must be annotated with the
@BindParameter
java annotation.- This annotation has one parameter:
name
must contain the name of the Bind Parameter or GraphQL variable, as defined in the request string.
- This annotation has one parameter:
- No other method parameter is allowed
-
(for subscription only) The first parameter must be a
-
(for query and mutation) The method must return the query type or the mutation type, as defined in the GraphQL schema.
- It's then up to you to retrieve the field value(s) that map(s) to the query(ies) or mutation(s) that was in the request string
-
(for subscription only) The method must return a
SubscriptionClient
, that mainly allows to close the subscription, and properly clean the client and server resources. - The method must throw the
GraphQLRequestExecutionException
.
Here is a sample of a GraphQL Repository, with Full Requests:
@GraphQLRepository
public interface GraphQLRepositoryTestCase {
/** Full request: query */
@FullRequest(request = "{directiveOnQuery (uppercase: ?uppercase) @testDirective(value:&valueParam)}")
public MyQueryType fullRequest1(@BindParameter(name = "uppercase") boolean uppercase,
@BindParameter(name = "valueParam") String value) throws GraphQLRequestExecutionException;
/** Full request: query, with queryType */
@FullRequest(request = "{directiveOnQuery (uppercase: ?uppercase) @testDirective(value:&valueParam)}", requestType = RequestType.query)
public MyQueryType fullRequest2(@BindParameter(name = "valueParam") String value,
@BindParameter(name = "uppercase") boolean uppercase) throws GraphQLRequestExecutionException;
/** Full request: mutation */
@FullRequest(request = "mutation($input: HumanInput!) {createHuman(human: $input) {id name }}", requestType = RequestType.mutation)
public AnotherMutationType fullRequestMutation(@BindParameter(name = "input") HumanInput humanInput)
throws GraphQLRequestExecutionException;
/** Full request: subscription */
@FullRequest(request = "subscription {issue53(date: &date) {}}", requestType = RequestType.subscription)
public SubscriptionClient fullSubscription(SubscriptionCallback<Date> callback,
@BindParameter(name = "date") Date date) throws GraphQLRequestExecutionException;
// You may add any number of GraphQL requests, including Partial Requests
}
The short story: with Partial Requests, you need to take care of the parameter for the choosed query, mutation or subscription. Anything else is the same as the Full Request, out of the Java annotation.
When defining a method that is mapped to a GraphQL Partial Request, it must respect these conditions:
- The method must be annotated by the
@PartialRequest
java annotation. This annotation has these parameters:- requestName: the GraphQL request name.
- It's optional if the method name is the field name for the query, mutation or subscription, as defined in the GraphQL query type, mutation type or subscription type
- request: the GraphQL request string. It may contain Bind Parameters (see the client execution page for more information on this).
- Please note the Partial Requests may not contain GraphQL variables
- requestType: the GraphQL request type. This parameter is optional, and may be omitted if your request is a query one.
- requestName: the GraphQL request name.
- The method must have these parameters:
-
(for subscription only) The first parameter must be a
SubscriptionCallback<Xxxx>
, where Xxxx is the type of the subscription field, that is: the GraphQL type that will be received for each subscription notification. - You must provide the exact same method parameters as the field parameters for the query, mutation or subscription field, as defined in the GraphQL schema
- For instance, for this
query1
query:Query {query1(param1: Int!, param2: MyInputType): MyGraphQLType}
, the GraphQL Repository java method should like this:public MyGraphQLType query1(Integer p1, MyInputType p2)
, where the method name is free if you used the requestName parameter of the@PartialRequest
java annotation. - These parameters must not be annotated by the
@BindParameter
java annotation. - Of course, of your query, mutation or subscription has no parameter, then the java method will have no such parameter.
- For instance, for this
- You can then add any number of method parameters. Each of these parameters must map to a Bind Parameter. As such, they must be annotated with the
@BindParameter
java annotation.- This annotation has one parameter (
name
) which must contain the name of the Bind Parameter, as defined in the request string.
- This annotation has one parameter (
- No other method parameter is allowed
-
(for subscription only) The first parameter must be a
-
(for query and mutation) The method must return the query type or the mutation type, as defined in the GraphQL schema.
- It's then up to you to retrieve the field value(s) that map(s) to the query(ies) or mutation(s) that was in the request string
-
(for subscription only) The method must return a
SubscriptionClient
, that mainly allows to close the subscription, and properly clean the client and server resources. - The method must throw the
GraphQLRequestExecutionException
.
Here is a sample of a GraphQL Repository, with Partial Requests:
@GraphQLRepository
public interface GraphQLRepositoryTestCase {
// withOneOptionalParam is a field of the Query GraphQL type
@PartialRequest(request = "{appearsIn name}")
public Character withOneOptionalParam(CharacterInput character) throws GraphQLRequestExecutionException;
@PartialRequest(requestName = "withOneOptionalParam", request = "{appearsIn name id}")
public Character thisIsNotARequestName1(CharacterInput character) throws GraphQLRequestExecutionException;
// A sample with one query parameter (input) and two Bind Parameters.
@PartialRequest(requestName = "allFieldCases", request = "{listWithoutIdSubTypes(nbItems: &nbItemsParam, input:?fieldParameterInput)}")
public AllFieldCases thisIsNotARequestName3(AllFieldCasesInput input,
@BindParameter(name = "nbItemsParam") long nbItems, //
@BindParameter(name = "fieldParameterInput") FieldParameterInput fieldParameterInput)
throws GraphQLRequestExecutionException;
// A mutation sample (note the requestType annotation parameter)
// Of course, it could also have some Bind Parameters
@PartialRequest(request = "{id}", requestType = RequestType.mutation)
public Human createHuman(HumanInput character) throws GraphQLRequestExecutionException;
// A subscription sample (note the requestType annotation parameter)
// Of course, it could also have some Bind Parameters
@PartialRequest(request = "{ id appearsIn }", requestType = RequestType.subscription)
public SubscriptionClient subscribeNewHumanForEpisode(SubscriptionCallback<Human> subscriptionCallback,
Episode episode) throws GraphQLRequestExecutionException;
// You can any number of other GraphQL request methods, including Full Requests
}
Like with Spring Repositories, the GraphQL Repositories needs to be wired into the Spring Context. To do this, you must add the EnableGraphQLRepositories
java annotation on a Spring Configuration class.
Here is a sample:
[...]
import com.graphql_java_generator.client.graphqlrepository.EnableGraphQLRepositories;
@Configuration
@ComponentScan(basePackageClasses = { GraphQLConfiguration.class, QueryExecutor.class })
@EnableGraphQLRepositories({ "org.allGraphQLCases.demo.impl", "org.allGraphQLCases.subscription.graphqlrepository" })
public class SpringTestConfig {
}
Once you've done this, Spring will create a dynamic proxy for each interface marked with the @GraphQLRepositoryTestCase
annotation.
So if the org.allGraphQLCases.demo.impl
package contains this interface:
package org.allGraphQLCases.demo.impl;
@GraphQLRepository
public interface MyGraphQLRepository {
// This interface contains several method marked either by @PartialRequest or @FullRequest
}
Then it can be used this way, in any Spring component:
@Component
public class MyComponent {
@Autowired
MyGraphQLRepository myGraphQLRepo;
public void doSomething() throws Exception {
MyGraphQLType response = myGraphQLRepo.executeMyGraphQLRequest(myparams);
[Do something with this result]
}
}
Important Notice: The above class must be a Spring bean (component...), so that Spring autowires the MyGraphQLRepository proxy into the myGraphQLRepo
attribute.
In a non-Spring app, it's up to you to call the method that creates the dynamic proxy. To do this, it's just a matter copying/pasting two lines of code.
Considering that you've defined this GraphQL Repository:
package org.allGraphQLCases.demo.impl;
@GraphQLRepository
public interface MyGraphQLRepository {
// This interface contains several method marked either by @PartialRequest or @FullRequest
}
You can create the dynamic proxy with these lines:
public class MyUsefulClass {
private MyGraphQLRepository myGraphQLRepo;
public MyUsefulClass(String endpoint) {
QueryExecutor queryExecutor = new QueryExecutor(endpoint);
MutationExecutor mutationExecutor = new MutationExecutor(endpoint);
SubscriptionExecutor subscriptionExecutor = new SubscriptionExecutor(endpoint);
//Step 1: create the invocationHandler
GraphQLRepositoryInvocationHandler<MyGraphQLRepository> invocationHandler = new GraphQLRepositoryInvocationHandler<MyGraphQLRepository>
(MyGraphQLRepository.class, queryExecutor, mutationExecutor, subscriptionExecutor);
//Step 2: create the dynamic proxy
myGraphQLRepo = (MyGraphQLRepository) Proxy.newProxyInstance(App.class.getClassLoader(),
new Class[] { MyGraphQLRepository.class }, invocationHandler);
}
public void doSomething() throws Exception {
MyGraphQLType response = myGraphQLRepo.executeMyGraphQLRequest(myparams);
[Do something with this result]
}
}
In this case, two additional rules apply:
- One GraphQL Repository may contain requests (queries, mutations and/or subscriptions) based on only one unique GraphQL schema
- The
QueryExecutor
of this GraphQL schema must be provided in thequeryExecutor
parameter of theGraphQLRepository
java annotation
Here is a sample:
@GraphQLRepository(queryExecutor = QueryExecutor.class)
public interface PartialRequestGraphQLRepository extends PartialQueries {
... Your Request definitions go here
}
As QueryExecutor
is the query executor for the Forum schema, all queries, mutations and/or subscriptions of this GraphQL Repositories must be based on this GraphQL schema.
Creating a first app (non spring)
Connect to more than one GraphQL servers
Easily execute GraphQL requests with GraphQL Repositories
Access to an OAuth2 GraphQL server
How to personalize the client app
Howto personalize the generated code
Client migration from 1.x to 2.x
Implement an OAuth2 GraphQL server
Howto personalize the generated code
Server migration from 1.x to 2.x