eh3rrera / graphql-java-spring-boot-example

Sample GraphQL server implemented with graphql-java and Spring Boot
MIT License
211 stars 151 forks source link

Multiple GraphQLQueryResolver interface implementation classes #11

Closed khteh closed 5 years ago

khteh commented 5 years ago

Hi;

Is it possible to have multiple implementaions of GraphQLResolver classes? It would be very messy to put everything into a single ROOT implementation classes when the project grows bigger. Thanks.

eh3rrera commented 5 years ago

Sure. From https://github.com/graphql-java-kickstart/graphql-java-tools#why-graphql-java-tools:

GraphQL Java Tools allows you to register "Resolvers" for any type that can bring state along and use that to resolve fields.

khteh commented 5 years ago

Hi; To clarify what I asked, is it possible to have multiple concrete implementation classes of the GraphQLQueryResolver interface in a single .jar / .war file? Is it possible to have multiple GraphQLQueryReosolver types in the same schema?

For example, in one book.graphqls, I define type BookQuery and in author.graphqls, I define another type AuthorQuery instead of extend type Query. Thanks!

khteh commented 5 years ago

I tried and received a runtime exception indicating missing root Query and Mutation types. So I added these empty "default" "root" resolver classes, added the miscellaneous Query and Mutation classes. I have not had a chance to test it out due to this error: https://github.com/graphql-java-kickstart/graphql-java-tools/issues/242

themanojshukla commented 5 years ago

Hi @khteh, when I was learning GraphQL I was having the same sort of doubt. when I got my answer, that time, it wasn't possible (I'm not sure about current trends).

GraphQL is specification given by Facebook, where as, GraphQL Java is the implementation of that specification on Java Platform. So, by specification, it compels to follow the extend type .... for more than one graphql schema definitions. And, in the same way we have our SchemaPraser in Java implementation.

I had tried the same thing in apollo-graphql (JavaScript/nodeJs based implementation). There to, the schema files had to be written in exact same way. I mean, you can actually use the same graphql schema file for both apollo-graphql and for graphql-java. So it is not an issue/bug with graphql-java. It is just the implementation of specification how it should be done.

themanojshukla commented 5 years ago

By the way, In java implementation, we can have single abstract class for Query Resolver and each of our Schema based resolvers can extend them with their own functional logic.

But, for graphql schema definition file should be written in the way it is suggested. I hope this was helpful.

khteh commented 5 years ago

root.graphqls:

type Query {
}
type Mutation {
}

book.graphqls:

type Book {
    id: ID!
    title: String!
    isbn: String!
    pageCount: Int
    author: Author
}
type BookQuery {
    findAllBooks: [Book]!
    countBooks: Long!
}
type BookMutation {
    newBook(title: String!, isbn: String!, pageCount: Int, author: ID!): Book!
    deleteBook(id: ID!): Boolean
    updateBookPageCount(id: ID!, pageCount: Int!): Book!
}

Query.java (To avoid runtime exception indicating missing root Query and Mutation types):

public class Query implements GraphQLQueryResolver { /* Empty */ }

Mutation.java (runtime exception indicating missing root Query and Mutation types):

public class Mutation implements GraphQLMutationResolver { /* Empty */ }

BookQuery.java:

public class BookQuery implements GraphQLQueryResolver { /* Implementations of the book.graphqls */ }

BookMutationJava:

public class BookMutation implements GraphQLMutationResolver {  /* Implementations of the book.graphqls */ }

Is this valid according to the GraphQL specification?

eh3rrera commented 5 years ago

Hi @khteh,

Sorry, I overlooked this issue :disappointed:

All types within a GraphQL schema must have unique names, so you can only have one Query type (and it's mandatory, that's why you got an exception about missing root Query). If you want to have multiple Query types defined in the schema, the only thing you can do is extend the type as @themanojshukla mentions (thanks!).

About the schema implementation, that's another thing. You can spread the Query definition between multiple GraphQLQueryResolver classes (the same apply to GraphQLMutationResolver).

For example, for this application, instead of one com.example.DemoGraphQL.resolver.Query class, we can have two classes:

public class BookQuery implements GraphQLQueryResolver {
    // ...

    public Iterable<Book> findAllBooks() {
        return bookRepository.findAll();
    }

    public long countBooks() {
        return bookRepository.count();
    }
}

And:

public class AuthorQuery implements GraphQLQueryResolver {
    // ...

    public Iterable<Author> findAllAuthors() {
        return authorRepository.findAll();
    }

    public long countAuthors() {
        return authorRepository.count();
    }
}

Or as @themanojshukla also mentions, have a single abstract class and multiple concrete classes.

Hope this answer your question.

khteh commented 5 years ago

How does the extension hierarchy look like? I tried to use

type AuthorQuery extends Query { ...}
type BookQuery extends Query { ... }

which is a flat 2-level extension but it throws exception:

2019-03-18 09:29:27.680 ERROR 14366 --- [           main] o.s.b.web.embedded.tomcat.TomcatStarter  : Error starting Tomcat context. Exception: org.springframework.beans.factory.UnsatisfiedDependencyException. Message: Error creating bean with name 'graphQLServletRegistrationBean' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLWebAutoConfiguration.class]: Unsatisfied dependency expressed through method 'graphQLServletRegistrationBean' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'graphQLHttpServlet' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLWebAutoConfiguration.class]: Unsatisfied dependency expressed through method 'graphQLHttpServlet' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'invocationInputFactory' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLWebAutoConfiguration.class]: Unsatisfied dependency expressed through method 'invocationInputFactory' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'graphQLSchemaProvider' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLWebAutoConfiguration.class]: Unsatisfied dependency expressed through method 'graphQLSchemaProvider' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'graphQLSchema' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLJavaToolsAutoConfiguration.class]: Unsatisfied dependency expressed through method 'graphQLSchema' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'schemaParser' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLJavaToolsAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.coxautodev.graphql.tools.SchemaParser]: Factory method 'schemaParser' threw exception; nested exception is org.antlr.v4.runtime.misc.ParseCancellationException: There are more tokens in the query that have not been consumed
eh3rrera commented 5 years ago

Actually, I use type extension in this project, in book.graphqls to extend the Query type defined in author.graphqls:

extend type Query {
    findAllBooks: [Book]!
    countBooks: Long!
}

Basically, extend adds fields to an already-defined type.

khteh commented 5 years ago

Just tested both methods work. No build error and object instantiation of the query and mutation types work. There is no good reason to subclass an empty abstract class.

pramod-madha commented 2 years ago

how to implement hierarchical mutations or nested mutations, can you guide me or help me on this with code

eh3rrera commented 2 years ago

Hi @pramod-madha. Probably this is not the best place to get an answer for your question, personally, I haven't used GraphQL for a long time. However, this article and its associated repo may help you: https://fedidat.com/280-graphql-java-nesting/. Good luck!

pramod-madha commented 2 years ago

Thanks eh3rrera for the reply. thanks for sharing the article, but I am using com.graphql-java-kickstart, but on an high level article it doesn't use com.graphql-java-kickstart library. can you please share or guide any other useful link or resource for the same. As I have an requirement to implement this functionality