Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inject bean into DataFetcher of GraphQL

I'm using Spring & graphql-java (graphql-java-annotation) in my project. For retrieving data part, i'm using a DataFetcher to get data from a service (from database).

The weird thing is that: myService is always null. Anyone know the reason?

DataFetcher

@Component
public class MyDataFetcher implements DataFetcher {

    // get data from database
    @Autowired
    private MyService myService;

    @Override
    public Object get(DataFetchingEnvironment environment) {
        return myService.getData();
    }
}

Schema

@Component
@GraphQLName("Query")
public class MyGraphSchema {

    @GraphQLField
    @GraphQLDataFetcher(MyDataFetcher.class)
    public Data getData() {
        return null;
    }
}

MyService

@Service
public class MyService {

    @Autowired
    private MyRepository myRepo;

    @Transactional(readOnly = true)
    public Data getData() {
        return myRepo.getData();
    }
}

Main test

@Bean
public String testGraphql(){
    GraphQLObjectType object = GraphQLAnnotations.object(MyGraphSchema.class);
    GraphQLSchema schema = newSchema().query(object).build();
    GraphQL graphql = new GraphQL(schema);

    ExecutionResult result = graphql.execute("{getData {id name desc}}");;
    Map<String, Object> v = (Map<String, Object>) result.getData();
    System.out.println(v);
    return v.toString();
}
like image 657
JasonS Avatar asked Jan 03 '17 09:01

JasonS


People also ask

What is DataFetcher in GraphQL?

Data fetchers are also known as “Resolvers” in many graphql implementations”. Resolver function is the place where graphql resolves the type or the field and gets its value from the configured resources like a database or other APIs or from cache etc and returns data back to user/caller.

Can GraphQL be used with Java?

GraphQL is a cross-platform, open-source, data query, and manipulation language for APIs. GraphQL servers are available for multiple languages such as Java, Python, C#, PHP, R, Haskell, JavaScript, Perl, Ruby, Scala, Go, Elixir, Erlang, and Clojure, etc. So, it can be used with any programming language and framework.

Can GraphQL be used with spring boot?

GraphQL is a relatively new concept from Facebook that's billed as an alternative to REST for Web APIs. In this tutorial, we'll learn how to set up a GraphQL server using Spring Boot, so that we can add it to existing applications or use it in new ones.

What is GraphQL SPQR?

GraphQL SPQR (GraphQL Schema Publisher & Query Resolver, pronounced like speaker) is a simple-to-use library for rapid development of GraphQL APIs in Java.


3 Answers

Since in graphql-java-annotation the data fetcher is defined by annotation, it is constructed by the framework (using reflection to get the constructor), thus it can't be a bean.

The workaround I've found for this is setting it as ApplicationContextAware, and then I can initialize some static field instead of a bean. Not the nicest thing, but it works:

@Component
public class MyDataFetcher implements DataFetcher, ApplicationContextAware {

    private static MyService myService;
    private static ApplicationContext context;

    @Override
    public Object get(DataFetchingEnvironment environment) {
        return myService.getData();
    }

    @override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansExcepion {
        context = applicationContext;
        myService = context.getBean(MyService.class);
    }
}

Basically you'll still get a new instance of the data fetcher initialized by graphQL, but also spring will initialize it, and since the myService is static, you'll get the initialized one.

like image 100
Nir Levy Avatar answered Oct 21 '22 03:10

Nir Levy


Though @Nir's approach works (and I often use it inside JPA Event Listeners), the DataFetcher objects are Singletons, so injecting via static properties is a little hacky.

However, GraphQL's execute method allows you to pass in an object as a context, which will then be available in your DataFetchingEnvironment object inside of your DataFetcher (see the graphql.execute() line below):

@Component
public class GraphQLService {

    @Autowired
    MyService myService;

    public Object getGraphQLResult() {
        GraphQLObjectType object = GraphQLAnnotations.object(MyGraphSchema.class);
        GraphQLSchema schema = newSchema().query(object).build();
        GraphQL graphql = new GraphQL(schema);

        ExecutionResult result = graphql.execute("{getData {id name desc}}", myService);

        return result.getData();
    }
}

public class MyDataFetcher implements DataFetcher {

    @Override
    public Object get(DataFetchingEnvironment environment) {
        MyService myService = (MyService) environment.getContext();

        return myService.getData();
    }
}
like image 26
Mike Avatar answered Oct 21 '22 05:10

Mike


The solution provided by @Nir Levy works perfectly. Just to make it a bit more reusable here. We can extract an abstract class which encapsulate the bean lookup logic and make autowiring work for its subclasses.

public abstract class SpringContextAwareDataFetcher implements DataFetcher, ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public final Object get(DataFetchingEnvironment environment) {
        return applicationContext.getBean(this.getClass()).fetch(environment);
    }

    protected abstract Object fetch(DataFetchingEnvironment environment);
}

And the subclass can be like this:

@Component
public class UserDataFetcher extends SpringContextAwareDataFetcher {

    @Autowired
    private UserService userService;

    @Override
    public String fetch(DataFetchingEnvironment environment) {
        User user = (User) environment.getSource();
        return userService.getUser(user.getId()).getName();
    }
}
like image 1
Yihan Zhao Avatar answered Oct 21 '22 04:10

Yihan Zhao