In this guide we will go over setting up a REST and GraphQL api in kotlin using Jooby.

Common Data Classes

To start we need to spin up some data classes we're going to use. These will be the base of our API response.

Located at:

common/all/src/main/kotlin/design/animus/common/records/

I always code records as my data classes, and director as the main entry point for my code. Just a commonality in my code.

You can break this into as many files as you want. I generally have a file per concern. So anything regarding records for say pet would be under pet.kt.

import kotlinx.serialization.Serializable

@Serializable
enum class PetType {
    DOG,
    CAT,
    FISH,
    LIZARD,
    TIGER,
    PANDA,
    NARWHAL
}

@Serializable
enum class PetGender {
    MALE,
    FEMALE,
    OTHER
}

@Serializable
data class Pet(
        val type: String,
        val gender: String,
        val age: Number
)

@Serializable
data class PetResponse(
        val error: Boolean,
        val data: List<Pet>
)

So we have several classes here. All annotated with Serializable. See here for information on that. This allows us to easily serialize these classes to a variety of format. Think Protobuf, JSON, XML, etc. It works across all platforms! So we can easily parse strings into these objects on our javsacript target.

The meat and potatoes here is the Pet data class. This is a very simple object containg information on our pets. I mean we want to start a zoo am I right? This is then used in PetResponse.

Aside

My opinionated way of how I handle my rest responses.

Status Codes

  • 200 (Ok) - Everthing is ok.
  • 204 (No Content) - The server processed the request but there is no content.
  • 401 (Unauthorized) - An unauthorized request.
  • 5XX - Any Server Error

Common Object

{
   "error": true
}

All objects returned will have an error flag. If true, we know it's a bad response.

Back to our regularly scheduled program.

Why the Response Wrapper

I generally like to do a generic of something like.

@Serializable
data class RestResponse<R>(
        val error: Boolean,
        val data: List<R>
)

As noted here, I had issues parsing out a generic via kotlin serialization in the JS side. So for now until I get more time to delve into the issue. This works.

We now have our responses modeled properly.

Jooby

There are a ton of REST frameworks/libraries for the JVM. After trying several I ended up here.

Routing

There are a number of ways to route components. You can do a list of closures, classes, etc.

I ended up at their MVC module. This breaks it into multiple classes per concern. I feel, in my opinion

  • Easier to reason about code
  • Seperation of concern
  • More targeted merges on changes
  • Easy modularization of the code.

Servers

So what does this run on?

I'm using netty. Which is an asynchronous web server library.

This doesn't require JBoss, tomcat or any application server. This is a deployable fat jar you can run. So just spinning up the jar, runs your api server.

Director

api/src/main/kotlin/design/animus/kotlinmultiplatform/api/director.kt

As I noted prior I label my main entry file director. Name this as you see fit, but remember build.gradle, will need to be appropiately updated as well.

package design.animus.kotlinmultiplatform.api

import design.animus.kotlinmultiplatform.api.controllers.v1.PetsVersionOne
import org.jooby.apitool.ApiTool
import org.jooby.json.Jackson
import org.jooby.run

val Swagger = ApiTool()

fun main(args: Array<String>) {
    run(*args) {
        use(Jackson())
        use(Swagger
                .filter { r -> r.pattern().startsWith("/api") }
                .swagger()
                .raml())
        use(PetsVersionOne::class)
    }
}

That's it, pretty darn concise.

The use functon adds a Jooby module to your application. Then returns the instance of the jooby application. There are a fair number of modules.

Swagger is instaniated as a seperate item. The reason for this is there are times you need to modify a response or documentation string. If it's buried in the closure it makes it nastier to do those per controller modifications.

There are some special items to it's closure. We're filtering for routes that start with "/api". Then saying to generate swagger and raml documentation.

Next we get into using the actual controller we generate.

Pet Controller v1

Located At:

api/src/main/kotlin/design/animus/kotlinmultiplatform/api/controllers/v1/pet.kt

package design.animus.kotlinmultiplatform.api.controllers.v1

import design.animus.common.records.Pet
import design.animus.common.records.PetGender
import design.animus.common.records.PetResponse
import design.animus.common.records.PetType
import org.jooby.mvc.GET
import org.jooby.mvc.Path
import javax.inject.Inject


private val pets = listOf(
        Pet(
                type = PetType.DOG.name,
                gender = PetGender.MALE.name,
                age = 6
        ),
        Pet(
                type = PetType.PANDA.name,
                gender = PetGender.OTHER.name,
                age = 13
        )
)

@Path("/api/v1/pets")
class PetsVersionOne @Inject constructor() {
    @GET
    fun getAllPets(): PetResponse {
        return PetResponse(
                error = false,
                data = listOf("one", "two", "three")
        )
    }
}

To create a controller we say the constructors can be injected. This allows Jooby I think to inject it's own constructor, and then patch the class to be a proper api controller.

See here for the api documentation.

Any functions annotated with one of the HTTP verbs are added as api routes. We can also do path parameters.

Getting By Type

When we take in a url paramater, it will be cast as a string. If we want to get a list of a specific type falling under the enum. We then need to cast that string to the enum.

First we need a simple helper function to get the enum value when given a string.

fun PetTypeFromString(type: String) = PetType.values().firstOrNull { it.name == type  }

Next we modify the controller, adding an endpoint to get a pet by an inbound type. Here we use that helper function to get the enum from the url paramater that is provided. An option type would be better, but this null works for now.

import org.jooby.Request

...
    @GET
    @Path("/type/:petType")
    fun getAllPetsOfSpecificType(req: Request): PetResponse {
        val stringType = req.param("petType").value()
        val petType = PetTypeFromString(stringType)
        return when (petType) {
            null -> PetResponse(error = true, data = listOf())
            else -> PetResponse(
                    error = false,
                    data = listOf("one", "two", "three")
            )
        }
    }

So it's pretty easy to add additional end points with filtration.

GraphQL

I went to a local meet up in NYC about graphql. There was a talk by the company Artsy, and they referenced this post.

I'm new to graphql so can't speak to the idiomatic way of developing. But I do see value in being able to specify your api from the frontend.

For this we'll be using KGraphQL

KotlinMultiPlatforSkeleton/build.gradle

buildscript {
        ext.KotlinVersion = '1.2.41'
        ext.SerializationVersion = '0.5.0'
        ...
        ext.KotlinGraphQLVersion = '0.2.6'

Add the version to the top level version declaration block.

KotlinMultiPlatforSkeleton/api/build.gradle

dependencies {
    expectedBy project(":common/all")
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$KotlinVersion"
    ...
    compile "com.github.pgutkowski:kgraphql:$KotlinGraphQLVersion"
}

Then add the library to the api build.gradle file.

Now you can layout this as you see fit. But with this api backend, we're now providing REST and GraphQL logic. The two should be very similar, just allowing flexibility in the data we get back.

That is to say the logic to retrieve a pet by type, name, or age. Is the exact same. So if we extrapolate that core logic. It can be shared between the REST and GraphQL layer.

The biggest difference is if we want to add or remove a field in the REST layer. We generally need to increment the version to reflect a new data model. With GraphQL we just ask for the fields, or remove them. So let's build some wrappers to limit how much code we have to write.

ReWorking Common Data Classes

We had defined some data classes before, but to support versioning of REST, and lack there of in GraphQL. We need to make some changes.

Wait Let's be Clear

You can do graphql queries of

  • getPetVersionOne
  • getPetVersionTwo
  • getPetVersionThirty

Bad dev, very bad dev. I will beat you with a foam pool noodle.

Create a new package design.animus.common.records.pet. We will split these into several kotlin files. I added a few new properties, to simulate a proper rest version change.

As an aside data classes do not support inheritance. There is a loosely recommended work around where we use an abstract class. That is what we'll be doing here. Implementing an abstract base class, then building off of that.

KotlinMultiPlatformSkeleton/common/src/main/kotlin/design/animus/common/records/pet

  • v1 The records for version one.
  • v2 The records for verson two.
  • pet The latest version.

pet

package design.animus.common.records.pet

import kotlinx.serialization.Serializable

@Serializable
enum class PetType {
    DOG,
    CAT,
    FISH,
    LIZARD,
    TIGER,
    PANDA,
    NARWHAL,
    PENGUIN,
    BIRD
}

fun PetTypeFromString(type: String) = PetType.values().firstOrNull { it.name == type }

@Serializable
enum class PetGender {
    MALE,
    FEMALE,
    TRANS,
    QUESTIONING,
    OTHER
}

abstract class PetBase {
    abstract val type: PetType
}

@Serializable
data class Pet(
        override val type: PetType,
        override val gender: PetGender,
        override val age: Int,
        override val name: String,
        override val aggressionLevel: Int
) : PetBaseVersionTwo()

data class PetResponse<D : PetBase>(
        val error: Boolean,
        val data: List<D>
)

We have moved the enums into this file. The enums don't need to be versioned, not in a case I can think of at least. We then build a common PetResponse data class that builds off of the PetBase. This basis allows us to inherit and extend our data classes. This will aid our builder function.

The core Pet object should inherit off of the latest version abstract class.

v1

package design.animus.common.records.pet

import kotlinx.serialization.Serializable

abstract class PetBaseVersionOne : PetBase() {
    abstract val gender: PetGender
    abstract val age: Int
}

@Serializable
data class PetVersionOne(
        override val type: PetType,
        override val gender: PetGender,
        override val age: Int
) : PetBaseVersionOne()

v2

package design.animus.common.records.pet

import kotlinx.serialization.Serializable

abstract class PetBaseVersionTwo : PetBaseVersionOne() {
    abstract val name: String
    abstract val aggressionLevel: Int
}

@Serializable
data class PetVersionTwo(
        override val type: PetType,
        override val gender: PetGender,
        override val age: Int,
        override val name: String,
        override val aggressionLevel: Int
) : PetBaseVersionTwo()

These are just building off of the PetBase. By using the interface, we have a contract that the data class will have at least these provided fields.

Response Version Helpers

The following will assume a relative start of:

KotlinMultiPlatformSkeleton/api/src/main/kotlin/design/animus/api

We are going to build a new namespace of responses

KotlinMultiPlatformSkeleton/api/src/main/kotlin/design/animus/api/responses/versions.kt

package design.animus.kotlinmultiplatform.api.responses

enum class PetVersions(version:String) {
    ONE("one"),
    TWO("two"),
    LATEST("latest")
}

We are creating an enum listing all the possible versions of a pet response. This way we ensure we've covered at compilation all permuations of respective versions in our builder expression.

KotlinMultiPlatformSkeleton/api/src/main/kotlin/design/animus/api/responses/pet.kt

package design.animus.kotlinmultiplatform.api.responses

import design.animus.common.records.pet.*

fun buildPetResponse(pets: List<Pet>, version: PetVersions = PetVersions.LATEST): PetResponse<PetBase> {
    return PetResponse(
            error = false,
            data = when (version) {
                PetVersions.ONE ->
                    pets.map {
                        PetVersionOne(
                                type = it.type,
                                gender = it.gender,
                                age = it.age
                        )
                    }
                PetVersions.TWO, PetVersions.LATEST ->
                    pets.map {
                        PetVersionTwo(
                                type = it.type,
                                gender = it.gender,
                                age = it.age,
                                name = it.name,
                                aggressionLevel = it.aggressionLevel
                        )
                    }
            }
    )
}

Okay this function is honestly a bit on the ugly side. But it limits us repeating ourselves over and over. To start with we take in a list of the latest Pet objects. This pet object will contain all fields relevant through the versions history. We have a key word argument of the version we want to target. This defaults to the latest version.

We use the when pattern matching on the version enum. With the comma seperator we can say that latest is equivalent to version two. This will build out the appropiate response. Returning a type PetResponse<PetBase>.

Modifying The Controllers

Since we have this helper, to build out versioned responses.

./api/controllers/v2/pet.kt

@Path("/api/v1/pets")
class PetsVersionOne @Inject constructor() {
    @GET
    fun getAllPets(): PetResponse<PetBase> {
        return buildPetResponse(pets, PetVersions.ONE)
    }

    @GET
    @Path("/type/:petType")
    fun getAllPetsOfSpecificType(req: Request): PetResponse<PetBase> {
        val stringType = req.param("petType").value()
        val petType = PetTypeFromString(stringType)
        return buildPetResponse(pets.filter { it.type == petType }, PetVersions.ONE)
    }
}

./api/controllers/v2/pet.kt

@Path("/api/v2/pets")
class PetsVersionTwo @Inject constructor() {
    @GET
    fun getAllPets(): PetResponse<PetBase> {
        return buildPetResponse(pets, PetVersions.TWO)
    }

    @GET
    @Path("/type/:petType")
    fun getAllPetsOfSpecificType(req: Request): PetResponse<PetBase> {
        val stringType = req.param("petType").value()
        val petType = PetTypeFromString(stringType)
        return buildPetResponse(pets.filter { it.type == petType }, PetVersions.TWO)
    }
}

This is a naive/happy path approach. But at this point since we have a simple in memory list of pets we need to control. This will work for the current implementation. We can reduce this even further, with inheritance. The only difference between the two implementations is the class.

Back to GraphQL

Starting path.

./api/graphql/schema.kt

package design.animus.kotlinmultiplatform.api.graphql

import com.github.pgutkowski.kgraphql.KGraphQL
import com.google.gson.Gson
import design.animus.common.records.pet.Pet
import design.animus.common.records.pet.PetGender
import design.animus.common.records.pet.PetType
import design.animus.kotlinmultiplatform.api.pets
import design.animus.kotlinmultiplatform.api.responses.buildPetResponse
import org.jooby.MediaType
import org.jooby.Request
import org.jooby.Response
import org.jooby.mvc.POST
import org.jooby.mvc.Path
import javax.inject.Inject

val schema = KGraphQL.schema {
    query("pets") {
        resolver { ->
            buildPetResponse(pets).data
        }
    }

    query("petsByType") {
        resolver { petType: PetType ->
            buildPetResponse(pets.filter { it.type == petType }).data
        }
    }

    type<Pet>()
    enum<PetGender>()
    enum<PetType>()
}

data class GraphQLRequest(val query: String = "")

@Path("/graphql")
class GraphQLEndpoint @Inject constructor() {
    @POST
    fun postQuery(req: Request, rsp: Response): String {
        val query = req.body().value()
        println("Received a query of $query")
        val gson = Gson()
        val graphQLQuery = gson.fromJson<GraphQLRequest>(query, GraphQLRequest::class.java)
        val queryResult = schema.execute(graphQLQuery.query)
        rsp.type(MediaType.json)
        return queryResult
    }
}
    query("pets") {
    query("petsByType") {

Any queries we wish to define for GraphQL are built with the query function. The string paramater is the name of the query. So in this instance we define two, much like our rest endpoints. One to get all pets, the other to get pets by a specific type.

        resolver { ->
        resolver { petType: PetType ->

This flows into closures that execute for the query. If we want to add any parameters for graphql. We simply add them as paramters. Also note that we are specifiying the inbound type to be specifically of the enum type. This removes the requirement to use the from string helper function. This is because we register that type in the graphql schema.

What goes inside the resolvers is not important in this discussion. But as can be seen we're simply using the buildResponse helper we defined earlier. This shows that if we break our code into smaller bite sized functions. They can be shared between the REST and GraphQL layer.

    type<Pet>()
    enum<PetGender>()
    enum<PetType>()

Lastly in the schema definition. We register the types. The graphql implementation has a understanding of basic types. Think Numbers, String, List, etc. Any custom objects, enums, or data classes will be registered.

Building the Endpoint

So we still need an endpoint to take in these graphql queries.

data class GraphQLRequest(val query: String = "")

We start defining the post body we get when a query is made. This is demarshalled via Gson. Then we build the api endpoint.

@Path("/graphql")
class GraphQLEndpoint @Inject constructor() {
    @POST
    fun postQuery(req: Request, rsp: Response): String {
        val query = req.body().value()
        val gson = Gson()
        val graphQLQuery = gson.fromJson<GraphQLRequest>(query, GraphQLRequest::class.java)
        val queryResult = schema.execute(graphQLQuery.query)
        rsp.type(MediaType.json)
        return queryResult
    }
}

The highlights here are we explicity set the response type to json. We're not casting to a Kotlin object or class. So we get back a string when executing the query against the eschame. We then return that queryResult string.

Modifying the Director

fun main(args: Array<String>) {
    run(*args) {
        use(Jackson()
                .raw()
        )

Firstly call the raw method on jackson. If you do not set this, it will escape the json response string. Rendering it an invalid response. Then enable the new graphql endpoint.

        )
        use(PetsVersionOne::class)
        use(PetsVersionTwo::class)
        use(GraphQLEndpoint::class)

Metrics

I was talking to one of my co-workers the other day about the importance of an api. It's the infrastructure the frontend or mobile runs off of. With that in mind the user experience will only be as good as your backend.

It's the layer of streets it travels to get the information you need. If they start to develop pot holes, or other issues. Then it hinders the experience. Even in a microservice architecture, with ephemeral containers metrics are still useful. I'll go into monitoring later on. But for now enable metrics.

Jooby has this built in. Which is coming from drop wizard upstream. There are additional things we can do. But this minimum works for now.

    compile "org.jooby:jooby-metrics:$JoobyVersion"

Add the above under dependencies.

Then back to director we add the following. Adding under neath of the existing respective swagger sections.

val Swagger = ApiTool()
val MetricInstance = Metrics()
        use(Swagger
                .filter { r -> r.pattern().startsWith("/api") }
                .swagger()
                .raml())
        use(MetricInstance
                .request()
                .ping()
                .threadDump()
                .metric("memory", MemoryUsageGaugeSet())
                .metric("threads", ThreadStatesGaugeSet())
                .metric("gc", GarbageCollectorMetricSet())
                .metric("fs", FileDescriptorRatioGauge())
        )

We now have the following new routes.

  GET /sys/metrics                  [*/*]     [*/*]    (/anonymous)
  GET /sys/metrics/:type            [*/*]     [*/*]    (/anonymous)
  GET /sys/healthcheck              [*/*]     [*/*]    (/anonymous)

This results in the following.

metrics

Docker

To deploy our API, we are going to bundle up a docker image. This sort of feels like a nested Russian doll though. The JVM is already cross platform, see the fat jar section. The benefit is when you're using something like docker compose.

buildscript {
    dependencies {
        classpath "com.github.jengelman.gradle.plugins:shadow:2.0.1"
        classpath 'se.transmode.gradle:gradle-docker:1.2'
    }
}

apply plugin: 'docker'

version = "1.0"
group = "animusdesign"

So we're pulling in a gradle plugin to build docker images. This is the more simple one. The version will be used for the docker tag number. If no version is specified it will default to latest. The group is utilized in the tag. See below.

As noted we had already set the main class manifest, and jar information. Defining the entry point for our API. So that doesn't need to be modified, because it's present. But it must be set.

docker {
    baseImage "openjdk"
    maintainer 'Animus Null'
}

distDocker {
    exposePort 8080
    tag "${project.group}/${applicationName.toLowerCase()}"
    setTagVersion(version)

}

Now we can run the gradle wrapper to simply generate our docker image.

./gradlew :api:distDocker

This will then create the following.

animusdesign/api    1.0                 1c0b3d202074        11 seconds ago      647MB

We can simply call it with the following command.

docker run -it -p 8080:8080 animusdesign/api:1.0
  GET /swagger/swagger.json         [*/*]     [*/*]    (/anonymous)
  GET /swagger/swagger.yml          [*/*]     [*/*]    (/anonymous)
  GET /swagger/static/**            [*/*]     [*/*]    (/anonymous)
  GET /swagger/static/**            [*/*]     [*/*]    (/anonymous)
  GET /swagger                      [*/*]     [*/*]    (/anonymous)
  GET /raml/api.raml                [*/*]     [*/*]    (/anonymous)
  GET /raml/static/**               [*/*]     [*/*]    (/anonymous)
  GET /raml                         [*/*]     [*/*]    (/anonymous)
  GET /sys/metrics                  [*/*]     [*/*]    (/anonymous)
  GET /sys/metrics/:type            [*/*]     [*/*]    (/anonymous)
  GET /sys/healthcheck              [*/*]     [*/*]    (/anonymous)
  GET /sys/ping                     [*/*]     [*/*]    (/anonymous)
  GET /sys/thread-dump              [*/*]     [*/*]    (/anonymous)
  GET /**                           [*/*]     [*/*]    (/anonymous)
  GET /api/v1/pets                  [*/*]     [*/*]    (/PetsVersionOne.getAllPets)
  GET /api/v1/pets/type/:petType    [*/*]     [*/*]    (/PetsVersionOne.getAllPetsOfSpecificType)

listening on:
  http://localhost:8080/

There you have it. A backend api with rudimentary monitoring, and build scripts. This is a basic foundation, we'll continue building off this.