$ http get https://catfact.ninja/fact
{"fact": "Isaac Newton invented the cat flap. Newton was experimenting in a pitch-black room. Spithead, one of his cats, kept opening the door and wrecking his experiment. The cat flap kept both Newton and Spithead happy.",
"length": 211
}
And the second about dog facts:
$ http get https://some-random-api.ml/facts/dog
{"fact": "A large breed dog's resting heart beats between 60 and 100 times per minute, and a small dog breed's heart beats between 100-140. Comparatively, a resting human heart beats 60-100 times per minute."}
By building a simple gateway, we take on complexity so that the front-end developers have one less thing to worry about:
we take care of calling the multiple endpoints and combining them, becoming a backend-for-frontend.
we offer a nice GraphQL schema to the front-end(s).
we normalize the response format - dog facts have no length attribute, but we can compute it!
we can potentially reduce the total response time. Without the gateway, the front-end would do two round-trips of let say 300 ms, so 600ms. With the gateway, there is one round-trip of 300 ms and two round-trips between the gateway and the facts server. If those are located on the same network, those could be done in 10 ms each, for a total of 320 ms.
So how do we build that gateway?
Dependencies
If you start a new project from scratch via https://start.spring.io/, you will need to add those dependencies:
Note that I'm using gradle refreshVersions to make it easy to keep the project up-to-date. Therefore, the versions are not defined in the build.gradle files, they are centralized in the versions.properties file. RefreshVersions is bootstrapped like this in settings.gradle.kts:
plugins{// See https://jmfayard.github.io/refreshVersionsid("de.fayard.refreshVersions")version"0.10.1"}
GraphQL-schema first
GraphQL-java-kickstart uses a schema-first approach.
We first define our schema in resources/graphql/schema.grqphqls :
Spring wants at least a GraphQLQueryResolver, the class responsible for implementing GraphQL queries.
We will define one, but keep it empty for now:
@Component
class AnimalsQueryResolver() : GraphQLQueryResolver {
}
GraphQLQueryResolver
If we start our application with ./gradlew bootRun, we will see it fail fast with this error message:
FieldResolverError: No method or field found as defined in schema graphql/schema.graphqls:2
with any of the following signatures
(with or without one of [interface graphql.schema.DataFetchingEnvironment] as the last argument),
in priority order:
dev.jmfayard.factsdemo.AnimalsQueryResolver.cat()
dev.jmfayard.factsdemo.AnimalsQueryResolver.getCat()
dev.jmfayard.factsdemo.AnimalsQueryResolver.cat
The schema, which is the single source of truth, requires something to implement a cat query, but we didn't have that in the code.
To make Spring happy, we make sure our Query Resolver has the same shape as the GraphQL schema: