Repository: graphql-java/graphql-java

Issue #122: TypeResolver often needs more input to resolve (Relay Node) type
Created: 2016-04-19T11:34:38Z
NOTE: My original example was tragically bad, so I've completely replaced it. As a result, the comments discussing my original example will not make sense. I apologize for that. The gist of the issue is still the same though.
An example of the problem is when resolving a type for a GraphQL interface. The only information available to the TypeResolver is the resulting Java object itself, from which the resolver is supposed to deduce the corresponding GraphQL type. This forces the implementor to manually maintain a map of GraphQLObjectTypes (as the schema object isn't (easily) reachable) and, more importantly, if the resulting object is an instance of a generic class, this decision is impossible without further information.
Suppose the following:
interface Repository { ... } 
 
class InMemoryRepository<T> implements Repository { ... }

InMemoryRepository<Order> might map to e.g. InMemoryRepository_Order GraphQL type (that has a containedItem field of type Order), while InMemoryRepository<Product> might map to InMemoryRepository_Product GraphQL type (that has a containedItem field of type Product), both implementing Repository:
type InMemoryRepository_Order implements Repository {
  containedItem: Order
}

type InMemoryRepository_Product implements Repository {
  containedItem: Product
}

When TypeResolver receives an instance of InMemoryRepository, there's no way to decide the GraphQL type: as it may be InMemoryRepository_Order or InMemoryRepository_Product.
If TypeResolver were to receive more information, it may be able to deduce the type. E.g. if it had access to the Field object, it could get the Java type of the field e.g. InMemoryRepository<Order> and it may deduce the corresponding type should be InMemoryRepository_Order. (It's possible to store the mapped Java type into Field's GraphQL type, as demonstrated by graphql-java-annotations here). With some advanced type analysis, even subtypes can be dealt with, e.g. if the runtime type of the resulting object wasn't InMemoryRepository but some subtype of it. I use this type of advanced analysis in my project (link below).
For example, in graphql-java-annotations and schemagen-graphql this exact strategy could be used for a much richer type resolution logic (a Type could be used instead of just a Class and combined with the runtime type of the resulting object).
In general, this change is especially of value to the maintainers of libraries that rely on GraphQL-Java (and we fortunately have a few of those now, mine included), because when developing a library as opposed the final project, the Java types can not be known ahead of time, so strategies like inspecting the runtime type of containedItem in the example above e.g. repo.getContainedItem().getClass() are impossible.
The change I propose to allow such advanced type resolution logic is providing TypeResolver most of the data available to DataFetchingEnvironment (this object is called TypeResolutionEnvironment in my PR) instead of just the resulting object. You can see this in use in my project: https://github.com/leangen/graphql-spqr/blob/master/src/main/java/io/leangen/graphql/generator/HintedTypeResolver.java#L44
It can be argued that this is an edge case, but that would imply usage of generics with this library in general is an edge case. I firmly believe that the fact we're dealing with a Java implementation of the GraphQL spec means it needs to deeply support Java idioms.

Duplicate issue #205: Circularity between object and interface type definitions
Created: 2016-09-12T19:00:11Z
The following circularity is problematic for users:

Interface types require a type resolver, and type resolvers must be able to reference the object types they need to resolve.
However, object types need to be able to reference the interfaces that they implement.

For example:
public class Schema {

    public static GraphQLInterfaceType AnimalType = newInterface()
      .name("Animal")
      .field(newFieldDefinition().name("name").type(GraphQLString).build())
      .typeResolver(new TypeResolver() {
          @Override
          public GraphQLObjectType getType(Object object) {
              return BirdType;
          }
      })
      .build();

    public static GraphQLObjectType BirdType = newObject()
      .name("Bird")
      .withInterface(AnimalType)
      .field(newFieldDefinition().name("name").type(GraphQLString).build())
      .build();
}

In the above example, the AnimalType type resolver can forward reference BirdType because it is implemented as an anonymous class.
However, this does not work if the type resolver is implemented as a lambda:
    public static GraphQLInterfaceType AnimalType = newInterface()
      .name("Animal")
      .field(newFieldDefinition().name("name").type(GraphQLString).build())
      .typeResolver(obj -> BirdType)
      .build();

In this case, BirdType must be referenced by its fully qualified name: Schema.BirdType:
      .typeResolver(obj -> Schema.BirdType)


The situation is further complicated when schema types are not defined as class members. For example, there is no direct way to resolve the circularity above if the same schema is defined within a method body.
public class Schema {

    public static void main(String[] args) {

        GraphQLObjectType BirdType = newObject()
          .name("Bird")
          .withInterface(AnimalType)  // AnimalType is not defined
          .field(newFieldDefinition().name("name").type(GraphQLString).build())
          .build();

        GraphQLInterfaceType AnimalType = newInterface()
          .name("Animal")
          .field(newFieldDefinition().name("name").type(GraphQLString).build())
          .typeResolver(new TypeResolver() {
              @Override
              public GraphQLObjectType getType(Object object) {
                  return BirdType;
              }
          })
          .build();
    }
}

There is no idiomatic way to solve this issue. Looking at the tests, types are defined as class members, leading the user to conclude that that is the only way to define a schema where interface/object relationships must be defined.
This is fine, but not good. To address this issue:

A type resolver must be declared in the interface example in the README. The example must be written in such a way that if the user follows this example, they are lead to writing code that compiles and defines a valid schema.
The type resolver API could be improved. For example, as @idubrov suggests, the type resolver could accept the GraphQL schema as a parameter.

Closing comment (2017-04-27T09:46:22Z): Is this related to #122 ?