@Extends
@Key(fields = "id")
public class Product {
private Review review;
// getters and setters not shown
}
16 June 2023
Tags : quarkus, graphql, apollo, api, teams
A canonical example using Quarkus GraphQL Federation and the Apollo Gateway server.
For some time now I have been following and using graphql as an API mechanism. If you have not heard of it, there are many great resources that talk about the benefits. Another great learning site is here. For me, some obvious benefits of GraphQL include:
eliminates over-fetching of data from an API
code a graphql API that can easily change without having to modify all the client code
you don’t end up with hundreds of REST API’s for every single use case that come up
In particular though - the composable nature of Federated graphql schemas is what catches my eye. Some early adopters like Netflix have blogged about their engineering efforts in this area.
In particular checkout Netflix’s Studio Search and DataMesh blogs
By using a Federated Graph with graphql, different departments within the company can control and contribute independently the API’s that compose StudioSearch.
For me, this also fits in with a Team Topology view of the world - where individual stream aligned business teams can control their bits of the graph, whilst contributing to the whole fairly autonomously.
Graphql was born in a nodejs ecosystem. But of course we ❤ Java, and in particular, Quarkus has a smallrye graphql️ implementation.
There has been a long-running RFE for adding GraphQL Federation to Quarkus. I would say in general things are looking pretty good these days with the addition of federation into the underlying smallrye implementation.
The other day, I was following this bug around resolving entities and was thinking to myself "this should work OK now!!" - so I went ahead and played around with the code. What resulted was a nice simple working example of graphql federation.
In the picture of Apollo’s Explorer - the Product{id, name} are sourced from the Product subgraph, and the Product{review} from the Review subgraph.
🤠 The Source Code is HERE so you can follow along. 🤠
Let me explain it a bit more in detail. There are three pieces to the simple architecture - a gateway component (apollo server), and two Quarkus graphql API’s (Product, Review).
You can run the two Quarkus graphql API’s using maven, and for any one of them, browse to the Quarkus Developer UI by hitting the 'd' key in the terminal. For example:
Use the Smallrye GraphQL panel to browse the individual schema, and to run individual queries using GrapQi against the single API e.g. for the Product api
we can run ProductByID and see the result for the fields we desire.
Now, in the Review service, we use a Federated Entity that you can read about here to extend the Product with a Review.
The key bit of Java code is the annotations that make this happen in the Review module’s Product domain model class i.e. @Extends
and @Key
:
@Extends
@Key(fields = "id")
public class Product {
private Review review;
// getters and setters not shown
}
In this way, we are extending the Product from the Product API with a Review.
We can query the individual graphql schemas in the DevUI or from the command line using curl.
Product API
$ curl http://localhost:8081/graphql/schema.graphql
union _Entity = Product
type Product @key(fields : "id") {
id: ID
name: String
}
"Query root"
type Query {
_entities(representations: [_Any!]!): [_Entity]!
_service: _Service!
"All Products"
allProducts: [Product]
"Find product by ID"
productById(id: String): Product @provides(fields : "id")
}
type _Service {
sdl: String!
}
Review API
$ curl http://localhost:8082/graphql/schema.graphql
union _Entity = Product | Review
type Product @extends @key(fields : "id") {
id: ID
review: Review
}
"Query root"
type Query {
_entities(representations: [_Any!]!): [_Entity]!
_service: _Service!
"Find product by ID"
productById(id: String): Product
"Find review by ID"
reviewById(id: String): Review
}
type Review @key(fields : "id") {
id: ID
product: Product
rating: Int
text: String
}
type _Service {
sdl: String!
}
We can see that the Product {id review} is provided by the Review API whilst the Product {id name} is provided by the Product API. You can also Query for Reviews and Products independently.
I have ommitted all the scalars and directives in the schema output above that are generated by setting these application properties:
quarkus.smallrye-graphql.schema-include-scalars=true
quarkus.smallrye-graphql.schema-include-directives=true
These are required by the gateway when it introspects the grapql schema to compose the one Supergraph.
Now to the gateway piece, which is run using apollo nodejs:
gateway$ npm run start
> start
> nodemon gateway.js
[nodemon] 2.0.22
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node gateway.js`
🚀 Server ready at: http://localhost:4000/
If you browse to the apollo endpoint, it will take you to the Sandbox which is very similar to the Quarkus GraphQi interface (the sandbox connects a websocket to your localhost:4000 port eventhough it appears on a cloud hosted url). It shows a nice schema view for the supergraph:
You can also query this using curl which is harder and a bit uglier:
$ curl -s -X POST http://localhost:4000/graphql -H "Content-Type: application/json" --data-binary '{"query":"{\n\t__schema{\n queryType {\n fields{\n name\n }\n }\n }\n}"}' | jq .
{
"data": {
"__schema": {
"queryType": {
"fields": [
{
"name": "allProducts"
},
{
"name": "productById"
},
{
"name": "reviewById"
}
]
}
}
}
}
The important bit is this is the federated Supergraph .. so when we query for Product - we can ask for the Product {id name review} fields and the gateway resolves the entities for us, yay !!
The one little hack required for the Apollo code to work with Quarkus, it is documented in the README. Apollo does not support the newish application/graphql+json
Mime Type yet despite PR’s ;) including one from me!
Hope you Enjoy! there is a wealth of fun stuff to code with using graphql (go lookup Mutations next!) 👾👾👾