I'm new to GraphQL and I'm currently implementing a GraphQL API into an established Java code, using GraphQL-SPQR and I'm running into a couple issues when it comes extracting data from hierarchical classes.
The issues that I am running into are as follows.
Firstly I don't if there is an easy way to get all the data associated with a returned node. If there is, this would be most useful for my more complex classes.
Secondly when a method returns an abstract class, I only seem able to request the variables on the abstract class. I'm sure this should be possible I am just hitting my head against a wall.
As a simple example
public abstract class Animal {
private String name;
private int age;
// Constructor
@GraphQLQuery(name = "name")
public String getName() {
return name;
}
// Age getter
}
public class Dog extends Animal {
private String favouriteFood;
// Constructor
@GraphQLQuery(name = "favouriteFood")
public String getFavouriteFood() {
return favouriteFood;
}
}
public class Database {
@GraphQLQuery(name = "getanimal")
public Animal getAnimal(@GraphQLArgument(name = "animalname") String animalname) {
return database.get(name);
}
}
So in my first question what I am currently querying is. "{animalname(name: \"Daisy\") {name age}}" This works fine as expected. If you imagine the class however had 10 variables I would like to merely be able to write the equivalent of the following without having to look them up. "{node(name: \"Daisy\") {ALL}}" Is this possible?
In terms of my second question. The follow query, throws an error ('Field 'favouriteFood' in type 'Animal' is undefined')
"{animalname(name: \"Bones\") {name age favouriteFood}}"
likewise (reading Inline Fragments of https://graphql.org/learn/queries/)
"{animalname(name: \"Bones\") {name age ... on Dog{favouriteFood}}}"
throws an error Unknown type Dog
This is annoying as I have a number of sub classes which could be returned and may require handling in different fashions. I think I can understand why this is occuring as GraphQL has no knowledge as to what the true class is, only the super class I have returned. However I'm wondering if there is a way to fix this.
Ultimately while I can get past both these issues by simply serialising all the data to JSON and sending it back, it kind of gets rid of the point of GraphQL and I would rather find an alternate solution.
Thank you for any response. Apologies if these are basic questions.
Answering my own question to help anyone else who has this issue.
The abstract class needs to have @GraphQLInterface included, as shown below
@GraphQLInterface(name = "Animal ", implementationAutoDiscovery = true)
public abstract class Animal {
private String name;
private int age;
// Constructor
@GraphQLQuery(name = "name")
public String getName() {
return name;
}
// Age getter
}
The following code was found after much solution and was created by the creator of SPQR. Effectively, when setting up your schema you need to declare an interface mapping strategy. The code below can be copied wholesale with only the "nodeQuery" variable being replaced with the service you are using to containing your "@GraphQLQuery" and "@GraphQLMutation" methods.
final GraphQLSchema schema = new GraphQLSchemaGenerator()
.withInterfaceMappingStrategy(new InterfaceMappingStrategy() {
@Override
public boolean supports(final AnnotatedType interfase) {
return interfase.isAnnotationPresent(GraphQLInterface.class);
}
@Override
public Collection<AnnotatedType> getInterfaces(final AnnotatedType type) {
Class clazz = ClassUtils.getRawType(type.getType());
final Set<AnnotatedType> interfaces = new HashSet<>();
do {
final AnnotatedType currentType = GenericTypeReflector.getExactSuperType(type, clazz);
if (supports(currentType)) {
interfaces.add(currentType);
}
Arrays.stream(clazz.getInterfaces())
.map(inter -> GenericTypeReflector.getExactSuperType(type, inter))
.filter(this::supports).forEach(interfaces::add);
} while ((clazz = clazz.getSuperclass()) != Object.class && clazz != null);
return interfaces;
}
}).withOperationsFromSingleton(nodeQuery)// register the service
.generate(); // done ;)
graphQL = new GraphQL.Builder(schema).build();
As this solution took some hunting, I'm going to start a blog soon with the other solutions I've stumbled on.
With regards to having a query that just returns all results. This is not possible in GraphQL. One workaround I might write is to have a endpoint that returns JSON of the entire object and the name of the object, then I can just use ObjectMapper to convert it back.
I hope this helps other people. I'm still looking into an answer for my first question and will update this post when I find one.