Alternative approaches to schema development

The way you’ve been developing so far is known as schema-first, as you always start by defining the schema. This style has important benefits, discussed at the beginning of this tutorial, and it works well for new projects, where no legacy code exists. Still, you may have noticed that in strongly and statically typed languages, like Java, it leads to a lot of duplication. For example, revisit the way you developed the Link type.

You defined it in the schema:

type Link {
    id: ID!
    url: String!
    description: String
}

and then you created a corresponding POJO:

public class Link {
    
    private final String id;
    private final String url;
    private final String description;
    
    //constructors, getters and setters
    //...
}

Both of these blocks contain the exact same information. Worse yet, changing one requires immediate change to the other. This makes refactoring risky and cumbersome. On the other hand, if you’re trying to introduce a GraphQL API into an existing project, writing the schema practically means re-describing the entire existing model. This is both expensive and error-prone, and still suffers from duplication.

Code-first style

A common alternative to the schema-first style, known as code-first, is generating the schema from the existing model. This keeps the schema and the model in sync, easing refactoring. It also works well in projects where GraphQL is introduced on top of an existing codebase. The downside of this approach is that the schema doesn’t exist until some server code is written, introducing a dependency between the client-side and server-side work. One workaround would be using stubs on the server to generate the schema quickly, then developing the real server code in parallel with the client.

The Java/GraphQL ecosystem spawned a few libraries that facilitate this style of development. You can find them listed here. An example using graphql-spqr, written by yours truly, follows below.

Setting up graphql-spqr

Additionally, it will be much more comfortable to work if the method parameter names are preserved (you’ll understand why in a second).

Make sure you rebuild the project now (e.g. run mvn clean package) for the new option to take effect. Then, restart Jetty.

Generating the schema using graphql-spqr

In order to generate a schema similar to the one you’ve been working on so far, but this time using the code-first style you’d (unsurprisingly) start from the business logic. It is fortunate that you already have some business logic ready, in Query, Mutation and *Resolver classes, as it simulates introducing GraphQL into an existing project.

The easiest way to demonstrate graphql-spqr is by using annotations, but note that they’re entirely optional.

A few things to note about this code:

  1. Implementing GraphQLRootResolver is no longer needed (nor is the dependency to graphql-java-tools). In fact, graphql-spqr goes to great lengths to ensure the code needs no special classes, interfaces or any modifications in order to be exposed over GraphQL
  2. As noted, the annotations are entirely optional, but the default configuration will expect them at the top-level
  3. By default, the name of the method parameter will be used in the schema (this is why you want -parameters javac option enabled when compiling). Using @GraphQLArgument is a way to change the name and set the default value. All of this is doable without annotations as well.

The point of interest in this block:

  1. No more implements GraphQLResolver<Link>
  2. @GraphQLContext is used to wire external methods into types. This mapping is semantically the same as if the Link class contained a method public User postedBy() {...}. In this manner, it is possible to keep the logic separate from data, yet still produce deeply nested structures.

Things to note:

  1. You expose mutations via @GraphQLMutation
  2. You can inject the AuthContext directly via @GraphQLRootContext. No more need for DataFetchingEnvironment. This nicely removes the dependency to graphql-java specific code in the logic layer.

If you now fire up GraphiQL, you’d get the exact same result as before:

Query links the same result as before
Mutation createlink same result as before

The important points to note:

  • You never defined the schema explicitly (meaning you won’t have to update it when the code changes either).
  • You don’t have to separate the logic for manipulating Links into the top level queries (allLinks inside Query), embedded ones (postedBy inside LinkResolver) and mutations (createLink inside Mutation). All the queries and mutations operating on links could have been placed into a single class (e.g. LinkService), yet having them separate was not a hurdle either. This implies that your legacy code and best practices can stay untouched.

This is just a glance at the alternative style of development. There are many more possibilities to explore, so take a look at what the ecosystem has to offer. For more info on graphql-spqr check out the project page, and for a full example see here.

Unlock the next chapter
Does GraphQL development always start by defining the schema?
Yes
No, but it is a popular approach
No, the schema can only be produced at the end
The schema can only be generated dynamically