I’ve been a fan of GraphQL ever since I first tried it. I push back against RESTful APIs to anyone that will listened (or not). I’ve written a few post about it (GraphQL: Java Server, JavaScript Client, GraphQL: Java Schema Annotations, GraphQL: A Java Server in Ratpack). What I haven’t done, is stay current. I got hooked on graphql-java at version 3.X and decided the annotations were the best way to go, and sadly the annotations development stalled and made upgrades tricky, and so I didn’t. But it was a constant nagging itch, to upgrade, and finally I did.
This post will discuss a Ratpack server, using GraphQL-java 6.0. I should note, that as I did this work, the annotations finally release an upgrade. Doh.
GraphQL Java 6.0
I committed to upgrade. The annotations had not kept up so this meant a bit of a rewrite. Normally I’m pretty suspicious of gratuitous annotation use. They often mask too much of what’s really going on, and they tend to spray the information about one concern throughout your code, making it hard to locate coherent answers on a topic. That was exactly the case here. Leaving the annotations behind meant:
- I had to figure out what previous magic I now had to handle on my own.
- I had to determine just how deeply into my code they’d rooted themselves.
I tried to approach it intelligently, but in the end I went with brute force, I removed the dependency, and kept trying to build the project, ripping out references, until, while no longer working, the project built and the annotations were gone. Then I set about fixing what I’d broken.
What Was Missing?
Basically without the annotations there were two things I needed to repair:
- Defining the query schema
- Wiring the queries up to the data sources
Defining Your Schema
GraphQL-java 6.0 supports a domain specific language for defining your schema known as IDL. It’s a major win. First, it gets your schema, which is by definition, a single concept, into one place, and makes it coherent. Second, they didn’t go off and write “Yet Another DSL” but instead supported one that while not part of the official spec, is part of the reference implementation, and has traction in the community. Nice.
Wiring Up Your Data Sources
The best practice for this now is using the “DataFetcher” interface. The name is a bit misleading, since these aren’t just for your queries (i.e. fetching data) but also for your mutations (modifying data). The name is weak, but the interface and it’s use is a breeze.
To the Code
I did all this work on my snippets server kata project, so for a richer example go there, but for the sake of clarity here will look at the more concise Ratpack GraphQL Server example.
The Dispatcher
This didn’t change hardly at all. It’s still as simple as grappig a JSON map, pulling out the query and variables, and executing them:
| public class GraphQLHandler implements Handler { | |
| // ... | |
| @Override | |
| public void handle(Context context) throws Exception { | |
| context.parse(Map.class).then(payload -> { | |
| Map<String, Object> variables = | |
| (Map<String, Object>) payload.get("variables"); | |
| ExecutionInput executionInput = | |
| ExecutionInput.newExecutionInput() | |
| .query(payload.get("query").toString()) | |
| .variables(variables) | |
| .build(); | |
| final ExecutionResult executionResult = | |
| graphql.execute(executionInput); | |
| Map<String, Object> result = new LinkedHashMap<>(); | |
| if (executionResult.getErrors().isEmpty()) { | |
| result.put("data", executionResult.getData()); | |
| } else { | |
| result.put("errors", executionResult.getErrors()); | |
| } | |
| context.render(json(result)); | |
| }); | |
| } | |
| } |
Pretty straight forward.
Defining the Schema: IDL
So in this trivial example all I have are Company entities, defined with this bean:
| public class Company { | |
| private final String name; | |
| private Integer revenue; | |
| public Company(String name, Integer revenue) { | |
| this.name = name; | |
| this.revenue = revenue; | |
| } | |
| public Integer getRevenue() { | |
| return revenue; | |
| } | |
| public void setRevenue(Integer revenue) { | |
| this.revenue = revenue; | |
| } | |
| public String getName() { | |
| return name; | |
| } | |
| } |
And all I wanted to support was, get one, get all, save one, delete one. So I needed to define my Company type, two queries, and two mutations. Defining this in IDL was easy:
| schema { | |
| query: QueryType | |
| mutation: MutationType | |
| } | |
| type QueryType { | |
| company( name: ID!) : Company | |
| companies : [ Company ] | |
| } | |
| type MutationType { | |
| company( name: ID! revenue: Int!): Company | |
| companyDelete( name: ID! ): Boolean | |
| } | |
| type Company { | |
| name: ID! | |
| revenue: Int! | |
| } |
Loading The Schema
I just tucked my schema definition into my resources folder and then:
| final SchemaParser schemaParser = new SchemaParser(); | |
| final TypeDefinitionRegistry registry; | |
| try (final InputStream inputStream = | |
| getClass().getClassLoader().getResourceAsStream("schema.graphqls"); | |
| final InputStreamReader streamReader = new InputStreamReader(inputStream)) { | |
| registry = schemaParser.parse(streamReader); | |
| } catch (Exception e) { | |
| throw new IllegalStateException("Could not parse graphql schema", e); | |
| } |
Wiring The Data to The Schema
In GraphQL-java, the way I chose to do this is with DataFetcher implementations. So for example, to find a company, by name, from a map it would look about like:
| public class CompanyQuery implements DataFetcher<Company> { | |
| private final Map<String, Company> companies; | |
| public CompanyFetcher(Map<String, Company> companies) { | |
| this.companies = companies; | |
| } | |
| @Override | |
| public Company get(DataFetchingEnvironment environment) { | |
| return companies.get(environment.getArgument("name")); | |
| } | |
| } |
So that’s the way to “fetch” the data, but how do you connect this to your schema? You define a “RuntimeWiring”:
| RuntimeWiring.newRuntimeWiring() | |
| .type("QueryType", wiring -> wiring | |
| .dataFetcher("company", new CompanyQuery(companies)) | |
| .dataFetcher("companies", new CompaniesQuery(companies)) | |
| ) | |
| .type("MutationType", wiring -> wiring | |
| .dataFetcher("company", new CompanyMutation(companies)) | |
| .dataFetcher("companyDelete", new CompanyDelete(companies)) | |
| ) | |
| .build(); |
And then you associate that wiring with your schema you loaded:
| // Load Schema | |
| final SchemaParser schemaParser = new SchemaParser(); | |
| final TypeDefinitionRegistry registry; | |
| try (final InputStream inputStream = | |
| getClass().getClassLoader().getResourceAsStream("schema.graphqls"); | |
| final InputStreamReader streamReader = new InputStreamReader(inputStream)) { | |
| registry = schemaParser.parse(streamReader); | |
| } catch (Exception e) { | |
| throw new IllegalStateException("Could not parse graphql schema", e); | |
| } | |
| // Create Wiring | |
| final RuntimeWiring wiring = RuntimeWiring.newRuntimeWiring() | |
| .type("QueryType", wiring -> wiring | |
| .dataFetcher("company", new CompanyQuery(companies)) | |
| .dataFetcher("companies", new CompaniesQuery(companies)) | |
| ) | |
| .type("MutationType", wiring -> wiring | |
| .dataFetcher("company", new CompanyMutation(companies)) | |
| .dataFetcher("companyDelete", new CompanyDelete(companies)) | |
| ) | |
| .build(); | |
| // Associate... | |
| SchemaGenerator schemaGenerator = new SchemaGenerator(); | |
| graphql = GraphQL.newGraphQL(schemaGenerator.makeExecutableSchema(registry, wiring)).build(); |
And Then…
Well that’s it. You’ve:
- Created a GraphQL dispather
- Defined your entites
- Defined your GraphQL schema
- Created queries
- Instantiated the schema, wired in the queries
Done. Take a look at my GraphQL server in Ratpack for the complete working code.
3 thoughts on “GraphQL Java 6.o”