Step by step guide to building Web API with Kotlin and Dropwizard

tagmg

Tagir Magomedov

Posted on September 16, 2017

Step by step guide to building Web API with Kotlin and Dropwizard

In this post we'll set a foundation for Web API server written in Kotlin and then build upon it in future posts covering topics like database access, authentication and deployment.

Why Kotlin?

Kotlin is a great language for JVM that generated much buzz when it was declared to be one of the officially supported languages for Android development.

However, the main strength of Kotlin is its amazing Java interoperability story and that makes it a great choice for building backend services as well.

We could of course use one of the newest cool frameworks for Kotlin, like KTOR, but why do that when we can leverage that interoperability story to use an amazing existing Java ecosystem that has been polished for God knows how long already?

Dropwizard

One of the projects that tries to consolidate some of the better practices of web development on JVM is Dropwizard.

It includes:

  • Jetty for HTTP
  • Jersey for REST
  • Jackson for JSON
  • Logback and slf4j for logging
  • JDBI and Hibernate for database access
  • etc

Dropwizard itself has pretty awesome docs including getting started guide, but it's all Java and Maven, which kind of makes you feel like writing legacy code from the get go.

How about living a little and making it all Kotlin and Gradle?

New Kotlin application

We'll build a simplistic Calculator web service. The full source code is available on GitHub.

Easiest way to get started is to create a Gradle project in IntelliJ choosing a Kotlin (JVM) template.

Set GroupID: to.dev.example, ArtifactID: kotlin-webapi, check Use auto-import and you are good to go. IntelliJ will setup basic project layout for compiling Kotlin code.

Add a src/main/kotlin/to/dev/example/MainApp.kt file, it will contain our application's entry point:

package to.dev.example

fun main(args: Array<String>) {
    println("Hello, Kotlin!")
}
Enter fullscreen mode Exit fullscreen mode

By default IntelliJ creates a Gradle build for a library, not an application. Let's add some lines to the build.gradle file at the root of our project to turn it into an runnable application:

apply plugin: 'application'
mainClassName = 'to.dev.example.MainAppKt'
Enter fullscreen mode Exit fullscreen mode

Notice the MainAppKt class name? Where does it come from?

There are no functions outside of classes in Java world, so Kotlin conveniently created a class for us behind the scenes, named it <FileName>Kt and put the main function in there.

Now we can go to the project root and type: $ ./gradlew run to make Gradle build and run our modest application.

:compileKotlin UP-TO-DATE
:compileJava NO-SOURCE
:copyMainKotlinClasses UP-TO-DATE
:processResources NO-SOURCE
:classes UP-TO-DATE
:run
Hello, Kotlin!
Enter fullscreen mode Exit fullscreen mode

Now that we have a running application, let's add some Dropwizard to it.

Dropwizard application

Dropwizard is structured as a collection of smaller libraries, so that you can add only the ones you need.
For now dropwizard-core will be enough.

Add the following lines to the same build.gradle file:

ext.dropwizard_version = '1.1.4'

dependencies {
    compile "io.dropwizard:dropwizard-core:$dropwizard_version"
}
Enter fullscreen mode Exit fullscreen mode

Configuration

Dropwizard has a notion of configuration files built into it so deep that in fact the first thing you need to do to have a running Dropwizard application is to define the configuration class derived from io.dropwizard.Configuration.

This configuration will primarily be used to distinguish between development and production settings like database connection strings.

Kotlin makes it very easy to create such simple classes. Just one line of code:

class CalculatorConfig(val name: String = "unknown") : Configuration()
Enter fullscreen mode Exit fullscreen mode

Of course we need an actual configuration file to be parsed into this config.
Add a config/local.yaml class to the root of our project:

name: Kotlin Calculator
Enter fullscreen mode Exit fullscreen mode

Application

To create a web service we will need to register components with Jersey.
Components can easily be enhanced with standard and self-explanatory javax.ws.rs annotations:

@Path("/")
class CalculatorComponent {
    @Path("/add")
    @GET
    fun add(@QueryParam("a") a: Double, @QueryParam("b") b: Double): Double {
        return a + b
    }

    @Path("/multiply")
    @GET
    fun multiply(@QueryParam("a") a: Double, @QueryParam("b") b: Double): Double {
        return a * b
    }

    @Path("/divide")
    @GET
    fun divide(@QueryParam("a") a: Double, @QueryParam("b") b: Double): Double {
        if (b == .0) {
            throw WebApplicationException("Can't divide by zero")
        }
        return a / b
    }
}
Enter fullscreen mode Exit fullscreen mode

Combined with your configuration declared above a class derived from io.dropwizard.Application forms a core of your Dropwizard application and that's where we will register our component with Jersey:

class CalculatorApp : Application<CalculatorConfig>() {
    override fun run(configuration: CalculatorConfig, environment: Environment) {
        println("Running ${configuration.name}!")
        val calculatorComponent = CalculatorComponent()
        environment.jersey().register(calculatorComponent)
    }
}
Enter fullscreen mode Exit fullscreen mode

Note: this registers a singleton component which will be shared between requests. There are other much more powerful ways to control creation of components for serving resources, but that's a topic for another day.

Now to create an object of this class and pass it command line arguments from our main function:

fun main(args: Array<String>) {
    CalculatorApp().run(*args) // use collection as a varargs
}
Enter fullscreen mode Exit fullscreen mode

Run

$ ./gradlew run
...
:run
usage: java -jar project.jar [-h] [-v] {server,check} ...

positional arguments:
  {server,check}         available commands

optional arguments:
  -h, --help             show this help message and exit
  -v, --version          show the application version and exit
Enter fullscreen mode Exit fullscreen mode

Looks like we need to pass some command line arguments to the app.
We should build an artifact and run it as a standalone Java application, but I prefer to hack it for now providing those arguments to the run task in build.gradle file:

run {
    args = ['server', 'config/local.yaml']
}
Enter fullscreen mode Exit fullscreen mode

Run again:

$ ./gradlew run
...
Running Kotlin Calculator!
...
INFO  [2017-09-16 21:17:27,181] io.dropwizard.server.ServerFactory: Starting CalculatorApp
...
INFO  [2017-09-16 21:17:27,664] io.dropwizard.jersey.DropwizardResourceConfig: The following paths were found for the configured resources:

    GET     / (to.dev.example.CalculatorComponent)
    GET     /add (to.dev.example.CalculatorComponent)
    GET     /multiply (to.dev.example.CalculatorComponent)

...

INFO  [2017-09-16 21:17:27,681] org.eclipse.jetty.server.AbstractConnector: Started application@720bf653{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
INFO  [2017-09-16 21:17:27,682] org.eclipse.jetty.server.AbstractConnector: Started admin@360bc645{HTTP/1.1,[http/1.1]}{0.0.0.0:8081}
...
Enter fullscreen mode Exit fullscreen mode

As you can see in the above logs web app started listening on default port 8080 and admin port 8081:

$ curl "localhost:8080/add?a=5&b=10"
15
$ curl "localhost:8080/multiply?a=5&b=10"
50
$ curl "localhost:8080/divide?a=5&b=0"
{"code":500,"message":"Can't divide by zero"}
Enter fullscreen mode Exit fullscreen mode

Voilà , we have a self-contained web service written in less than 50 lines of Kotlin code capable of serving 30,000-50,000 requests per second.

In the next posts we'll cover testing, serving JSON objects, creating a standalone app for deployment, database access and authentication.

💖 💪 🙅 🚩
tagmg
Tagir Magomedov

Posted on September 16, 2017

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related