GraphQL for server-side resource aggregation
When implementing service oriented and microservices architectures, sooner or later, the aggregation of data coming from different services becomes a problem to face. In this post, we will see how GraphQL can help with this.
We are developing a property rental API, and the consumer needs an endpoint to fetch information about a property and its landlord. An example response would look like this:
GET /properties/1234
{
id: 1234,
address: "10 Downing Street",
area: 90,
landlord: {
id: 882,
name: "John Doe"
email: "john.doe@mail.com"
}
}
We can see two aggregates involved in this piece of information: Property and Landlord.
In a monolithic application, we would write a query, with some SQL JOIN
maybe, to fetch the data from a database, and marshall it to the above JSON.
However, let's assume in our rental organization we have a microservices architecture; we have a Property Microservice and Landlord Microservice, with a database for each one. For that reason, we can't just 'JOIN' two database tables; integration between services is made across the network and not through database sharing.
We need an API Gateway or BFF to aggregate data from both services and serve it to the API consumer.
We could perform aggregation "by hand":
//This code belongs to API Gateway / BFF
fun getProperty(id: Int) : JsonObject {
val property = propertyService.getById(id)
val landlord = landlordService.getById(property.landlordId)
return buildJson(property, landlord)
}
Note that property
object, fetched from propertyService::getById
, does not contain information about its landlord
, but only its identifier; landlordId
. We use that field to fetch the whole landlord info, through 'landlordService', and construct the full response JsonObject
.
When implementing this approach, due to its stream-based nature, reactive programming tools like ReactiveX are quite useful. An alternative to this is letting GraphQL engine do the job.
Resource aggregation using GraphQL
First, we are going to define the GraphQL schema:
type Query {
getProperty(id: String!): Property
}
type Property {
id: String
address: String
area: Int
landlord: Landlord
}
type Landlord {
id: String
name: String
email: String
}
Now, we have to define a data fetcher for getProperty
operation:
fun getPropertyDataFetcher() : DataFetcher {
return DataFetcher { env -> {
val propertyId = env.getArgument("id")
return propertyService.getProperty(propertyId)
}
}
So far, we have the information about the property. Now, we need to implement the data fetcher for its landlord:
fun getLandlordDataFetcher() : DataFetcher {
return DataFetcher { env -> {
val property = env.getSource<Property>()
val landlordId = property.landlordId
return landlordService.getLandlord(landlordId)
}
}
With the schema and the fetchers, we are ready to build our GraphQL
instance and make the query:
val graphQL = buildGraphQLFromSchemaAndFetchers()
executeQuery(graphQL, """{ query:
getProperty(id: 1234) {
id
address
area
landlord {
id
name
email
}
}
}""")
With this approach, we delegate the logic of the data composition to GraphQL library implementation, this way being less error-prone than doing it manually.
It looks like a declarative way; you tell it how to fetch properties, and how to fetch landlords. Then, just ask for the data you need and data aggregation is managed by GraphQL.
Sometimes, we tend to think of GraphQL just as a type of HTTP API; an alternative to REST. This is true, but using GraphQL does not mean starting a GraphQL server. You don't need to expose an API of this type at all, depending on the use case you are trying to solve.
In this example, I have shown how to use GraphQL as a query engine and data aggregator, but I haven't mentioned anything about GraphQL servers.
Let's consider this code, that could be written using some modern REST tool:
@GET
fun getProperty(@PathParam("id") id: String) : Property {
val property = executeQuery(graphQL, """{ query:
getProperty(id: $id) {
id
address
area
landlord {
id
name
email
}
}
}""")
return property
}
This is a REST API resource, but, under-the-hood, a GraphQL engine is responsible for aggregation of data from several sources. There is no need to run any GraphQL server.
There are many reasons you may not want to expose a pure GraphQL API. e.g. hard integration with frontend or other services, maintain compatibility, lack of GraphQL knowledge amongst the developers, or even organizational convention. Anyway, you can take advantage of GraphQL power and use it as a query engine, like we have just seen above.