leangen / graphql-spqr

Build a GraphQL service in seconds
Apache License 2.0
1.09k stars 179 forks source link

How to map a java class to GraphQL interface without using @GraphQLInterface annotation? #476

Closed niliha closed 8 months ago

niliha commented 9 months ago

Hi,

first of all, thank you very much for all the work you put in this project!

We try to use spqr in our project, where we want to make a complex tree data structure accessible using graphql. The data structure is included as a third party dependency, thus we do not have control over it. Furthermore, many abstract types are included in the definition of the structure.

Is there a way where we can register these abstract classes as GraphQL interfaces and discover their implementations without having to modify the classes, i.e. annotating them with @GraphQLInterface(implementationAutoDiscovery=true) ?

We use graphql-spqr-spring-boot-starter:1.0.0

Thank you in advance :)

Balf commented 8 months ago

I think I can help you on your way here a bit, since your usecase somewhat resembles mine in Issue 475.

The GraphQLSchemaGenerator provides several methods to expand the schema with types that are not annotated. In your case you can use the withAdditionalTypes method to register the extra interfaces to the Schema.

Please note that this method takes a list of GraphQLTypes. For the interface of your external project you would have to create the GraphQLType definition yourself, using the methods provided by the GraphQL Java project on which SPQR depends.

Like so: Creating a schema programatically.

That way you can register custom types to the Schema. To automatically discover any implementations of that interface you would probably need to create your own ImplementationDiscoveryStrategy and add that to the SchemaGenerator. Here is a link to the default one provided by SPQR to discover implementations of an interface: DefaultImplementationStrategy.

However, if you're not able to annotate your data classes I'm not sure SPQR is the right solution for your project. SPQR's benefits are mostly in that area. Assuming that your data structure does not change (often) you might be better off by creating an SDL file for it and use that to define a schema.

Take a look at GraphQL Java and GraphQL Kickstart for more information on how to define a schema and attach the required DataFetchers and TypeResolvers to it.

niliha commented 8 months ago

Hi @Balf, I really appreciate you helping out.

The workaround you propose seems very promising, but if I understand it correctly, it would require me to create the schema manually for these interface types. Since in the data structure we use, there are quite many and complex abstract classes, this would result in a lot of work.

I ended up with the very same approach you mentioned in the end, i.e. I hacked the annotations into the third-party dependency and imported the schema generated by SPQR into another application using standard graphql-java.

The only thing I still use from SPQR are the scalar definitions since graphql-java-extended-scalars does not cover all scalar types for me.

Thank you again :)

kaqqao commented 8 months ago

Hey @niliha, sorry for the slow response. Are these interfaces reachable from your root types (the one you registered with GraphQLSchemaGenerator)? If so, you can easily control what gets mapped using various InterfaceMappingStrategy implementations. E.g. you can map interfaces from specific packages or something custom. Everything in SPQR is configurable without annotations, that was one of the design goals.

kaqqao commented 8 months ago

@Balf Your situation looks a bit nore complicated, but I'll give it a look. I'm pretty sure you can also get away without manually mapping things...

niliha commented 7 months ago

Hi @kaqqao, thank you for guiding me in the right direction!

I had a look at InterfaceMappingStrategy before, but was confused by the use of AnnotatedType, thinking it would require annotations. However, having a look at the documentation of AnnotatedType, it's clear that the type must not be annotated.

For ayone else who might have a similar issue in the future, i.e. mapping java classes to GraphQL interfaces without using annotations, here is a more detailed description of what you need to do:

  1. Create a custom InterfaceMappingStrategy implementation, i.e. getInterfaces() must return all class types you want to register as GraphQL interface and supports() must return true for these class types. One way to implement this is store the affected class types statically in the implementation.
  2. Register this implementation using GraphQLSchemaGenerator#withInterfaceMappingStrategy()