How to execute Java calls to GraphQL in a Spring B

2020-07-28 11:28发布

问题:

In a Spring Boot application, we're already having a fully functional GraphQL endpoint, serving .graphqls files via GraphQL Java Tools (we included the graphql-spring-boot-starter dependency) and handling the data resolution through our base Query class implementing GraphQLQueryResolver and subsequent GraphQLResolver's.

For a business need, we have to re-create standard REST API endpoints, so I wonder why not just making calls to GraphQL (instead of having to re-implement "by hand" the data resolution once again)? And as it's in the same backend application, no need to make HTTP or servlet (ForwardRequest) calls, just call some API's in Java. The thing is I don't know how to proceed.

I read this example, but it's with basic GraphQL Java (not Tools): https://www.graphql-java.com/documentation/v9/execution/

I know this should possible because we are allowed to do this in tests: https://github.com/graphql-java-kickstart/graphql-spring-boot/blob/master/example-graphql-tools/src/test/java/com/graphql/sample/boot/GraphQLToolsSampleApplicationTest.java

But how to do it in regular code? There is not such thing as a GraphQLTemplate.

I also tried to search through examples at:

  • https://github.com/graphql-java-kickstart/graphql-java-tools/tree/master/example
  • https://github.com/graphql-java-kickstart/graphql-spring-boot

but found nothing relevant to our need.

Found nothing more in Documentation:

  • https://www.graphql-java-kickstart.com/tools/
  • https://www.graphql-java-kickstart.com/spring-boot/

What did I miss? Ideally I'm looking to inject some GraphQLSomething like this:

@RestController
@RequestMapping(path = "api")
public class CompanyController {

    @Autowired
    private GraphQLSomething graphQLSomething;

    @GetMapping("company/{id}")
    public ResponseEntity<?> societe(@PathVariable @NotNull Integer id) {
        GraphQLSomethingResult result = GraphQLSomething.query("company(id: $id) { id name andsoone }", "{ \"id\": 123456 }").execute(); // not really sure of the GraphQL syntax here, but it'll need some tests...
        return result.getDataOrElse();
    }

}

回答1:

Finally found how to do the thing as I wanted:

import java.util.Map;
import java.util.Optional;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.google.common.collect.ImmutableMap;

import graphql.ExecutionResult;
import graphql.servlet.core.GraphQLQueryInvoker;
import graphql.servlet.core.internal.GraphQLRequest;
import graphql.servlet.input.GraphQLInvocationInputFactory;
import graphql.servlet.input.GraphQLSingleInvocationInput;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Validated
@RestController
@RequestMapping(path = "api")
public class CompanyController {

    @Autowired
    private GraphQLInvocationInputFactory invocationInputFactory;

    @Autowired
    private GraphQLQueryInvoker queryInvoker;

    @GetMapping("company/{id}")
    public ResponseEntity<?> societe(@PathVariable @NotNull Integer id) {
        String query = "query ($id: Int!) { company(id: $id) { id name andsoon } }";
        /*
         * ImmutableMap is a Guava class; you can build the map (e.g. a HashMap) on your
         * own, or simply Map.to(..) in Java 9, or even @PathVariable Map<String,
         * Object> variables as the method's parameter instead (but you'll miss the
         * validation).
         */
        Map<String, Object> variables = ImmutableMap.of("id", id);

        GraphQLRequest request = new GraphQLRequest(query, variables, null);
        GraphQLSingleInvocationInput invocationInput = invocationInputFactory.create(request);
        ExecutionResult result = queryInvoker.query(invocationInput);

        /*
         * Of course result.getData() can be null here - see also result.isDataPresent()
         * - but data/error handling's left to you
         */
        Optional<Object> company = Optional.ofNullable(result.getData().get("company"));
        return ResponseEntity.of(company);
    }

}


回答2:

FYI, to get the dependencies for the above code, you'll need this:

    <dependency>
        <groupId>com.graphql-java</groupId>
        <artifactId>graphql-spring-boot-autoconfigure</artifactId>
        <version>5.0.2</version>
    </dependency>
    <dependency>
        <groupId>com.graphql-java</groupId>
        <artifactId>graphql-java-servlet</artifactId>
        <version>6.1.3</version>
    </dependency>