From GraphQL to Kotlin
That's one small step for [a] man, one giant leap for mankind.
GraphQL has redefined the way of writing APIs by providing an elegant way to declare and query data. Structuring models and their relationships, and querying those models for specific data can be done using a simple Schema Definition Language (SDL). GraphQL provides client applications with the flexibility to retreive data that they are interested in and reduces the need for multiple web requests.
Using the appropriate tools, GraphQL Schemas and Queries can be used to generate Kotlin code for the client. This greatly expedites application development with GraphQL APIs. However, these tools typically require a functioning GraphQL API Server which could be introspected. This might be fine in most cases but I recently found myself in a scenario where I was simultaneously developing the API and the Client. So I had to devise an approach to generating Kotlin code from GraphQL SDL files instead of an endpoint that can be introspected. This article discusses the solution I decided on.
TL;DR
Creating a simple Gradle Task that loads the GraphQL SDL files and generates a schema.json
file allowed me to use the apollo-android
client to generate Kotlin code for interacting with the GraphQL API.
The Background
For awhile I had been working on different approaches to interacting with GraphQL APIs with Kotlin. My desire was to have a Kotlin Multi-platform GraphQL Library. I created a Kotlin Multi-platform GraphQL query builder library but there were some issues with this approach and I wasn't quite satisfied with it. So I began developing graphkl which was intended to be a complete Kotlin Multi-platform GraphQL Library. It had modules for parsing GraphQL SDLs, writing typesafe queries, and generating Kotlin code. Unfortunately, I was sidetracked and other projects garnered my attention. I always had the intent to go back to the graphkl
project and finish developing it, but it seems that it might not be necessary anymore.
There are now multiple Kotlin libraries that generate Kotlin code from GraphQL. Two of the more popular ones are graphql-kotlin and apollo-android. Despite it's name, apollo-android
recently added experimental support for generating Kotlin Multi-platform code, which is the reason why I decided to go with this library.
The Problem
The apollo-android
library provides both a runtime library and a Gradle Plugin that performs the code generation. The Gradle Plugin requires a schema.json
file which defines your GraphQL API Schema. The Gradle Plugin provides a way to obtain this file by introspecting a specified endpoint to the server implementing the GraphQL API. But I didn't have a server running for my API, instead I had the GraphQL SDL files defining my Schema. Unfortunately, the apollo-android
Gradle Plugin doesn't have a way to create the schema.json
file from those SDL files.
The Solution
Since the Kotlin code generation from apollo-android
is done from a Gradle Plugin, I can create my own Gradle Task that creates the schema.json
file and have it run before the code generation. While previously working on setting up the server, I used the graphql-java library. From my experience with the graphql-java
library, I knew it was possible to create a Schema object from SDL files. I just had to figure out how to create the schema.json
file from this object.
That's when I stumbled across this new project on Github called gradle-plugin-graphql-schema-tools. It seems the developer was attempting to acheive a similar goal. With the help of these two projects, I was able to acheive my desired goal. The solution was to create a mocked RuntimeWiring
object, combine that with the SDL files to create a Schema
object, run an introspection query on that Schema, and output the result of executionResult.toSpecification()
to a schema.json
file.
The Code
As of the time of writing this post, the code can be found here which is located in the buildSrc
directory of my Video repository. The most relevant class is the CreateSchemaJsonTask
which can be found here.
- First, a mocked
RuntimeWiring
object is needed to perform the introspection query. This mock doesn't return any actual data but just echos the name and type that is being queried.
private class MockedWiringFactory : EchoingWiringFactory() {
override fun providesScalar(environment: ScalarWiringEnvironment): Boolean =
!ScalarInfo.isGraphqlSpecifiedScalar(environment.scalarTypeDefinition.name)
override fun getScalar(environment: ScalarWiringEnvironment): GraphQLScalarType =
GraphQLScalarType.newScalar()
.name(environment.scalarTypeDefinition.name)
.coercing(object : Coercing<Any, Any> {
override fun parseValue(input: Any): Any =
throw UnsupportedOperationException("parseValue() function is not implemented.")
override fun parseLiteral(input: Any): Any =
throw UnsupportedOperationException("parseLiteral() function is not implemented.")
override fun serialize(dataFetcherResult: Any): Any =
throw UnsupportedOperationException("serialize() function is not implemented.")
})
.build()
}
An instance of this class can be obtained like so:
val mockRuntimeWiring = RuntimeWiring.newRuntimeWiring().wiringFactory(MockedWiringFactory()).build()
- Then a
TypeDefinitionRegistry
needs to be created using the GraphQL SDL files.
val parser = SchemaParser()
val typeRegistry = TypeDefinitionRegistry()
(graphQLDirectory.listFiles()?.toList() ?: emptyList())
.filter {
it.isFile && (it.path.endsWith(".graphql") ||
it.path.endsWith(".graphqls") ||
it.path.endsWith(".gql"))
}
.forEach { typeRegistry.merge(parser.parse(it)) }
- Then the
Schema
needs to be generated and an instropection query needs to be run on it.
val schema = SchemaGenerator().makeExecutableSchema(typeRegistry, mockRuntimeWiring)
val graphQL = GraphQL.newGraphQL(schema).build()
val executionResult = graphQL.execute(IntrospectionQuery.INTROSPECTION_QUERY)
- Finally the
schema.json
file can be created with the introspection result.
val output = JSONObject(executionResult.toSpecification())
val outputFile = File(schemaDirectory, "schema.json")
outputFile.writeText(output.toString())
Then all that is needed is to have the generateApolloSources
task depend on this newly created Task. Now, everytime the GraphQL SDL files are updated and the project is built, the schema.json
file will be generated, followed by the Kotlin code using that schema.json
file.
The Example
Now that the Gradle schema.json
Task is created, let's have a look at an example of using the apollo-android
library. The API used in this example is available here. The API GraphQL SDL files directory location is provided to the CreateSchemaJsonTask
in the build.gradle
file. Then the output schema.json
file is placed in the src/commonMain/graphql
directory, along with the GraphQL Queries on the API, which the apollo-android
Gradle Plugin will use to generate the Kotlin Code to execute the queries. The following are the relevant parts of the GraphQL Schema for this example:
schema {
query: Query
mutation: Mutation
}
type Query @access(type: OPEN) {
apiVersion: String! @access(type: OPEN)
login: LoginInfo! @access(type: OPEN)
provider: ProviderInfo! @access(type: OPEN)
viewer: Viewer! @access(type: LOGIN_STRATEGY)
search(query: String!, take: Int!, after: Cursor): SearchResultConnection! @access(type: LOGIN_STRATEGY)
feed(take: Int!, after: Cursor): FeedConnection! @access(type: LOGIN_STRATEGY)
}
type ProviderInfo implements TimeDetail @access(type: OPEN) {
created: DateTime!
lastUpdated: DateTime!
uri: UriString!
name: String!
description: String
about: String
website: String
contactEmail: String
images: ProviderImageInfo!
}
type ProviderImageInfo @access(type: OPEN) {
thumbnail: UriString
banner: UriString
}
Then we can create any GraphQL Queries in .graphql
files in the src/commonMain/graphql
directory. The following is an example of a query:
query ProviderInfoQuery {
apiVersion
provider {
created
lastUpdated
uri
name
images {
banner
thumbnail
}
}
}
After building the project, the Kotlin Code for the above query should be generated. Performing the query is then relatively simple:
val result: Flow<Response<ProviderInfoQuery.Data>> = apolloClient.query(ProviderInfoQuery()).execute()
The End
GraphQL is a modern approach to developing APIs that provides flexibility to the client applications. The apollo-android
library makes GraphQL accessible to Kotlin Multi-platform projects. With the guidance of this post, it is my hope that developing GraphQL APIs, from a Kotlin perspective, will become efficient, trivial, and enjoyable.
I began this post with a quote from the Apollo 11 astronaut Neil Armstrong and an image of an Apollo Lunar Landing Module. These were deliberately chosen as a reference to the apollo-android library. Apollo was an ancient Greek and Roman Olympian Deity which was perhaps the inspiration for the Apollo Program. It seems as though the naming of the Apollo GraphQL organization was inspired by the Apollo Program due to their constant use of space like imagery. Due to this, I found it fitting to reference the Apollo Program in this post, and as such, ending in the same manner.
I feel we need to remind the world about the Apollo missions and that we can still do impossible things.
-- Buzz Aldrin
The Update
It looks as though the apollo-android
library added support for an SDL schema file called schema.sdl
which may provide similar functionality to what this post describes. However, it seems that they only currently support a single .sdl
file for the Schema, so in my particular case, the approach described in this article is still much better, as it allows me to have multiple .graphql
files representing my Schema and from those it generates a single schema.json
file to be used by the library. The PR adding the schema.sdl
support can be found here: https://github.com/apollographql/apollo-android/pull/2417/files