loading...

How to express non functional concerns using kotlin DSL

viniciusccarvalho profile image Vinicius Carvalho ・3 min read

Kotlin DSL support is awesome!

One of my favorites features of Kotlin, is the powerful DSL(ish) support the language provides.

You can write very expressive functions that look almost like a DSL, all because in Kotlin if the last argument of a function is a lambda you can move it outside the parentheses into curly brackets {}

This allows you to write code that almost look like a markdown representation such as the HTML builder that ships with the language:

html {
        head {
            title {+"XML encoding with Kotlin"}
        }
        body { }
}

This code looks like a markdown, but it's a static compiled piece of code. Isn't that nice?

One of my favorites usages of DSLs in kotlin is a project that wraps kubernetes-client (which uses a lot of builders) into nice friendly DSL. Check it out: k8s-kotlin-dsl

How I got addicted to it

After a while I started to using DSLs more and more. If you use co-routines you probably saw this DSL-esque function:

fun testAsyncFunction() = runBlocking {
//Some async code goes here
}

Another one that I enjoy quite a lot is to replace all those: System.currentTimeInMillis() we all have on our code:

val runningTime = measureTimeInMillis {
//invoke function here
}

As you can see, using DSLs can make your code easier to read, one can easily infer what is happening at measureTimeInMillis or in runBlocking

So because of that I started creating a few of my own.

Being a nice API consumer

Another area where Kotlin shines is concurrency with co-routines (as well as parallelism with async).

In one of my pet projects I crawl an external public API that has a rate-limit policy. No limits, as long as you space your calls every 2 seconds.

Since I create a gazillion co-routines (actually more like 3k) and I want them to run in parallel not writing a delay(2000) on each function. I decided to use an implementation of a TokenBucket - Wikipedia : Github repo.

So I wanted all my functions that were running inside an async block to be limited by a given tokenbucket. So the idea was that any function should look like this:

fun request() : ApiResponse<T> = limited {

}

This would allow anyone reading this code to see that this call, is limited by a certain factor.

The limited function is very simple:

suspend fun <T> limited(bucket: TokenBucket = DEFAULT_LIMITER,  block: suspend () -> T) : T {
    bucket.consume()
    return block()
}

The block parameter is the function you are wrapping between limited { <block> }, which returns whatever the type your original function returns. We call this higher order functions.

Because all the code is supposed to run on a suspended state, both the function and the block are marked as suspend.

Now I can run this code inside a loop like this:

val pages = getNumberOfPages()
for(p in 0..pages){

GlobalScope.launch {
val response request(p)
//process response
}

//return immediately to caller


And I'm sure that all thousands of jobs in flight will be limited by only 1 call every 2 seconds.

Final thoughts

It's becoming pretty easy to understand why kotlin is such a beloved language. Small features like this make your code so much more readable. It's pretty neat to be able to express your code in a clear way.

Happy coding

Discussion

markdown guide