Mapping multiple graphQL schema files to separate resolvers - Spring Boot - java

I'm finding it really difficult to separate queries from one schema file. I want to have something like this:
car.graphqls
type Query {
car(id: ID!): Car
}
type Car {
id: ID!,
name: String!
}
house.graphqls
type Query {
house(id: ID!): House
}
type House {
id: ID!,
owner: String,
street: String
}
I searched a lot but I can't find a way to write two java classes and implement getHouse() in one of them and getCar() in other.
#Component
public class CarQuery implements GraphQLQueryResolver {
#Autowired
private CarService carService;
public List<Car> getCar(final int id) {
return this.carService.getCar(id);
}
}
public class HouseQuery implements GraphQLQueryResolver {
#Autowired
private HouseService houseService;
public List<House> getHouse(final int id) {
return this.houseService.getHouse(id);
}
}
I found out that the graphql-java-tools package which I'm using will search through the project and finds all schema files (that end with .graphqls), but the code which I showed above gives me this error:
Caused by: com.coxautodev.graphql.tools.FieldResolverError: No method found with any of the following signatures (with or without one of [interface graphql.schema.DataFetchingEnvironment] as the last argument), in priority order:
com.example.polls.resolvers.CarQuery.house(~count)
com.example.polls.resolvers.CarQuery.getHouse(~count)
I also found some advises that I need to have only one Root Query in schema files, and to extend all other Query types in schema files. I tried to write to house.graphqls something like this, but failed:
extend Type Query {
house(id: ID!): House
}
Is there a way to tell graphql and java what schema file I want to be mapped to which java resolver file?

Thanks AllirionX. Your answer was helpful.
I would just like to summarize final solution to all who are looking for answer how to create multiple schema files with separate query types in each of them and map those query types to different Java Components using GraphQLQueryResolver.
My Spring Boot project structure
I have two schema files A.graphqls and B.graphqls.
A.graphqls
---------------
type Person {
id: ID!,
name: String
}
type Query {
getPerson(id: Int):Person
}
type Mutation {
createPerson(name: String):Int
}
B.graphqls
---------------
type Book {
id: ID!,
title: String,
owner: Person
}
extend type Query {
getBooks(count: Int):[Book]
}
extend type Mutation {
deleteBook(id: Int):Int
}
schema {
query: Query,
mutation: Mutation
}
I will explain what I learned about rules we need to follow about this topic (I don't guarantee that this is all necessary, but that is how I managed to get it work how I wanted it to work).
The key here is to only have one schema definition. It doesn't matter in which file (A.graphqls or B.graphqls or C.graphqls...) - In example, I added it to B.graphqls file at the bottom.
Also, you can have only one "type Query" definition in ONE file. In all other schema files you will need to extend that type with "extend type Query" (yeah, I know, it makes sense now...). In which schema file you do that main definition for Query that is not relevant. Everything in this paragraph applies to mutations also.
You can use type defined in one .graphqls file in other .graphqls file. It will get recognized. So, in this example, you can use Person type reference in B.graphqls.
Java resolvers:
import com.coxautodev.graphql.tools.GraphQLQueryResolver;
import graphql.demo.model.Person;
import graphql.demo.service.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
#Component
public class AQuery implements GraphQLQueryResolver {
#Autowired
private PersonService personService;
public Person getPerson(final int id) {
return this.personService.getPerson(id);
}
}
And second one...
#Component
public class BQuery implements GraphQLQueryResolver {
#Autowired
private BookService bookService;
public List<Book> getBooks(final int count) {
return this.bookService.getBooks(count);
}
}
Names of this classes are not important. We could also have only one class that implements GraphQLQueryResolver and we could implement all query methods from both A.graphqls and B.graphqls files (getBooks() and getPerson() methods). As long as we implement all methods, it's not important in which resolver class we implemented it graphql-java will find it.
Same applies to mutations using GraphQLMutationResolver.
I have full working example (MySQL, Spring Boot, React with Apollo client) on my github, so you can check it out. There is also mysql script for generating database used in project. There is plenty of tables, but there are just for testing purposes, what is important is file structure and files I explained above. If you are not interested in client app, you can test it using graphiql, of course.
https://github.com/dusko-dime/spring-react-graphql
Hope this can be helpful to someone and thanks for helping me once again :)

Multiple schema files
Graphql-java-tools will find all the .graphqls files to build a schema instance. Multiple schema files work out of the box.
Multiple resolvers
Each resolver component must implement GraphQLQueryResolver or GraphQLMutationResolver and be scannable by spring boot. Make sure it has a #Component annotation and is in one of the spring #ComponentScan basePackages.
The error
No method found with any of the following signature means that graphql-tools was not able to find a resolver component with a method matching the signatures. In this case, the HouseQuery resolver is missing the #Component annotation.

Also there can be issue with your Resolver name
Please ensure that you specify the same name of your Resolver method as you have defined in your GraphQL Schema

Related

Spring Boot 2 | Very basic Dependency Injection #Autowired question

Very new to Java Spring Boot 2. Apologies in advance for what I am sure is a seriously dumb question.
Background:
For me, I need to see and understand, first of all, a very pure and simple implementation of a concept before I can begin to extrapolate the utility of the concept or technique.
Therefore I am trying to set up an idiot-proof example showing #Autowired and DI 'at work' which I can fully understand before moving on.
I have tried to set up a super simple Spring Boot 2 project with just two classes; 1 "TesterApplication" which contains the psvm and 2 'Users' which is a very simple class which contains a single field 'name', with a no args constructor. See images:
The PSVM is here:
package com.tester.kryten.tester;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class TesterApplication {
public static void main(String[] args) {
System.out.println("1. Pre Spring Run");
SpringApplication.run(TesterApplication.class, args);
System.out.println("2. Post Spring Run");
#Autowired
Users user;
System.out.println("3. " + user.getName());
}
}
The 'Users' class is here:
package com.tester.kryten.tester;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class Users {
public String name = "foo bar";
#Autowired
public Users() {
System.out.println("User class constructor called");
}
public String getName() { return name; }
}
Now, ALL I am trying to do is understand the correct way to reference and USE a Spring managed instance of 'User' in another class, in this simple example the TesterApplication class.
I am reading about how it's frowned upon to use:
Users user = new User();
And that its better to use Spring DI to manage dependencies, so how would it be done in this example?
What is throwing me is that on line 20 of the TesterApplication class I am getting the expected code completion from Intellij offering me the getName() method. This makes me think that the instance must me there, but I just get:
Error:(17, 9) java: annotation type not applicable to this kind of
declaration
Could some kind soul out there please put me out of my misery and tell me where I'm misunderstanding how to simply inject an instance of Users into TestApplication.class - the right way? Please.
Move User user; outside of main method. It have to be class property, not local variable
You do not need #Authowired annotation for constructor in Users class
You can use #Authovired annotation for constructor if it is parametrised by other Spring beans to inject them
The first thing to understand is the use of the #Component annotation. When you put the annotation on a class as in your case Users during the start of the application context, the constructor of the Users class is invoked by creating a Users object and adding it to the Spring IOC container. To use this object inside another class just create an attribute of type Users and put the #Autowired annotation on this attribute not inside the method. So you can use this attribute in the methods of your class. Also, you don't need to put the #Autowired annotation on the Users class constructor.

Springfox / Swagger does not resolve polymorphic field

I'm having a simple Spring Boot application with one REST endpoint to return a "Job" object, which contains a list of polymorphics, next to other stuff.
We go Code First approach and try to create the API models to fit our needs. But the generated Api Doc does not represent our model the in it's full complexity, as it does not resolve the list of polymorphics.
The Job object looks like
#Data // Lombok Getters and Setters
public final class Job {
private String foo;
private String bar;
private List<Condition> conditionList;
}
Condition is a parent object for a set of different conditions
public abstract class Condition {
}
Two example implementations of a Condition would be
#Data
public final class Internal extends Condition {
private String nodeId;
}
and
#Data
public final class Timed extends Condition {
private ZonedDateTime timestamp;
}
The REST controller is stupidly simple:
#RestController
#RequestMapping("/hello")
public class MyController {
#GetMapping
public ResponseEntity<Job> getJob() {
return new ResponseEntity<>(new Job(), HttpStatus.OK);
}
}
Now, when I open the Swagger UI and look at the generated definition, the element conditionList is an empty object {}
I tried to use the #JsonSubTypes and #ApiModel on the classed, but there was no difference in the output. I might not have used them correctly, or maybe Swagger is just not able to fulfill the job, or maybe I'm just blind or stupid.
How can I get Swagger to include the Subtypes into the generated api doc?
We "fixed" the problem by changing the structure. So it's more of a workaround.
Instead of using a List of polymorphics, we now use a "container" class, which contains each type as it's own type.
The Condition object became a "container" or "manager" class, instead of a List.
In the Job class, the field is now defined as:
private Condition condition;
The Condition class itself is now
public final class Condition{
private List<Internal> internalConditions;
// etc...
}
And, as example, the Internal lost it's parent type and is now just
public final class Internal{
// Logic...
}
The Swagger generated JSON now looks like this (excerpt):
"Job": {
"Condition": {
"Internal": {
}
"External": {
}
//etc...
}
}
Useful display of polymorphic responses in Swagger UI with Springfox 2.9.2 seems hard (impossible?). Workaround feels reasonable.
OpenAPI 3.0 appears to improve support for polymorphism. To achieve your original goal, I would either
Wait for Springfox to get Open API 3.0 support (issue 2022 in Springfox Github). Unfortunately, the issue has been open since Sept 2017 and there is no indication of Open API 3.0 support being added soon (in Aug 2019).
Change to Spring REST Docs, perhaps adding the restdocs-api-spec extension to generate Open API 3.0.
We have run into similar problems with polymorphism but have not yet attempted to implement a solution based on Spring REST Docs + restdocs-api-spec.

What is the purpose of the Spring Bean annotation name attribute?

I'm just learning spring, and something struck me as very odd about the annotation configurations using the name attribute as a string.
#Bean(name = "com.my.injected.Service")
public InjectedService injectedService() {
return injectedService;
}
Is this name similar to the Spring Bean XML configuration id and class attributes?
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
Why isn't this simply
#Bean(clazz = com.my.injected.Service.class)
public InjectedService injectedService() {
return injectedService;
}
instead?
You're fully qualifying the path in both cases and actually using the class makes it way easier for your IDE to tell you when you've screwed it up. I understand that the XML configuration came first, and naturally it was always looking up things by string, so is this just a holdover? Is there some advantage to using strings or major disadvantage to using .class?
Question was originally based on a false premise. I edited it to spell out what this premise was and make it less confusing for new people who come along. Hopefully I did this such that the given answers are still exactly applicable; apologies if not.
#Bean annotation is meant to provide a spring bean. The type of the bean to provide will be the same type of the class/interface you define in the return method. So, instead of declaring to return a concrete class in the method, return the top (abstract) class/interface instead.
Imagine this case:
public interface MyEntityDao {
MyEntity get(String id);
}
#Repository
public class MyEntityDaoDatabaseImpl implements MyEntityDao {
#Override
public MyEntity get(String id) {
/* implementation that goes to database every time */
}
}
#Repository
public class MyEntityDaoCacheImpl implements MyEntityDao {
#Override
public MyEntity get(String id) {
/* implementation that looks the data
up in cache, never in database */
}
}
#Configuration
public class MyAppConfiguration {
#Bean
public MyEntityDaoDatabaseImpl method1() {
return new MyEntityDaoDatabaseImpl();
}
#Bean
public MyEntityDaoCacheImpl method2() {
return new MyEntityDaoCacheImpl();
}
}
#Service
public class MyEntityService {
#Autowired //what to inject here?
MyEntityDao dao;
}
In case above, there are two implementations of the proposed interface. How the framework may be able to understand which implementation to use except for the name?
#Service
public class MyEntityService {
#Autowired
#Qualifier("properBeanNameToInject")
MyEntityDao dao;
}
Bean name is not necessarily related to its class or even any of interfaces it implements. It is a name and nothing more. When you use the annotation configuration, Spring figures out what the exact class or interface the #Bean provides like the rest of java code would: either through the fully qualified name in the code or through the imports specified in the file. In your case, you presumably have an import com.my.injected.Service; statement at the top of the java file.
Your example is using the fully qualified class name as the bean name. It is your choice. You could use any other identifier. Using the fully qualified name could be useful if your code is providing an object that is named exactly like another 3rd party #Bean object that your code must include or consume. However, you could just as easily use name = "myService".
The bean name helps Spring (and application programmer) to distinguish between multiple instances of of the same bean class because you can deploy the same class as bean several times. If only one instance of bean type appear you event do not have to give it name manually: spring does this by default.
If you have several beans that have the same type or implement the same interface and you want to refer specific bean use #Qualifier annotation.

Resolving subdocument types with Spring Data and MongoDB

I'm encountering an error with a Spring Data repository as it attempts to resolve a property expression:
public interface ContractRepository
extends MongoRepository<Contract,String> {
public List<Contract> findByCodeBindings(String binding);
}
Here's the relevant parts of Contract:
#Document(collection="CONTRACTS")
public class PersistentContract extends BaseContract {
#PersistenceConstructor
public PersistentContract(String name, Version version, Code code) {
super(name, version, code);
}
}
Code is an interface implemented by CodeImpl. It contains a property bindings, which has a getter and setter in Code. So the query's property expression is designed to find those contracts with a nested Code document containing a given binding. So far, so good.
However, the problem is an IllegalArgumentException is getting thrown:
java.lang.IllegalArgumentException: No property bindings found on my.company.Code!
org.springframework.data.mapping.context.AbstractMappingContext.getPersistentPropertyPath(AbstractMappingContext.java:225)
Debugging that section of code shows that Spring Data is picking apart the expression and determines there's a property of type Code. However, because Code is an interface, it has no properties listed.
Is there a means to hint to Spring Data that either Code has this property or that CodeImpl is the actual type of the code property? I'm surprised that the library doesn't attempt to parse the getters or setters of the interface.
This is using spring-data-commons 1.5.1.RELEASE and spring-data-mongodb 1.2.1.RELEASE.
Appreciate the help.
My solution was to avoid interfaces at all in the persistent object. So BaseContract became the following:
public abstract class BaseContract<T extends Code> {
public abstract T getCode();
}
And PersistentContract was implemented in terms of concrete classes:
public class PersistentContract extends BaseContract<CodeImpl> {
}
This seems to strike the right balance between coding against interfaces in the base class and satisfying Spring Data's need for concrete classes.

Spring Data MongoDB tries to generate queries for custom repository methods

Based on the Spring Data Document documentation, I have provided a custom implementation of a repository method. The custom method's name refers to a property which doesn't exist in the domain object:
#Document
public class User {
String username;
}
public interface UserRepositoryCustom {
public User findByNonExistentProperty(String arg);
}
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
#Override
public User findByNonExistentProperty(String arg) {
return /*perform query*/;
}
}
public interface UserRepository
extends CrudRepository<?, ?>, UserRepositoryCustom {
public User findByUsername(String username);
}
However, perhaps because of the method name I've chosen (findByNonExistentPropertyName), Spring Data attempts to parse the method name, and create a query from it. When it can't find the nonExistentProperty in User, an exception is thrown.
Possible resolutions:
Have I made a mistake in how I provide the implementation of the custom method?
Is there a way to instruct Spring to not attempt to generate a query based on this method's name?
Do I just have to avoid using any of the prefixes that Spring Data recognizes?
None of the above.
Thank you!
Your implementation class has to be named UserRepositoryImpl (if you stick to the default configuration) as we try to look it up based on the Spring Data repository interface's name being found. The reason we start with that one is that we cannot reliably know which of the interfaces you extend is the one with the custom implementation. Given a scenario like this
public interface UserRepository extends CrudRepository<User, BigInteger>,
QueryDslPredicateExecutor<User>, UserRepositoryCustom { … }
we would have to somehow hard code the interfaces not to check for custom implementation classes to prevent accidental pick-ups.
So what we generally suggest is coming up with a naming convention of let's say the Custom suffix for the interface containing the methods to be implemented manually. You can then set up the repository infrastructure to pick up implementation classes using CustomImpl as suffix by using the repository-impl-postfix attribute of the repositories element:
<mongo:repositories base-package="com.acme"
repository-impl-postfix="CustomImpl" />
There's more information on that in the reference documentation but it seems you have at least briefly checked that. :)

Categories

Resources