From GraphQL to Kotlin

Converting GraphQL SDL files to Kotlin Code

From GraphQL to Kotlin

That's one small step for [a] man, one giant leap for mankind.

-- Neil Armstrong

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