Kystrix – A Kotlin DSL for Hystrix

Introduction

When using a microservices based architecture you need to protect against other systems/services being down or slow when you interact with them. One common way to achieve this on the JVM is by using the Hystrix library from Netflix. We’re using Kotlin more and more on the backend and you can obviously use Hystrix from Kotlin as well. Here’s an example of a HystrixCommand that calls a remote service:

class RemoteApiCommand(val webClient: WebClient) : HystrixCommand>(
        Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("MyGroup"))
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("MyThreadPool"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("MyCommand"))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(4000))
                .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(3).withMaxQueueSize(10).withQueueSizeRejectionThreshold(10))) {

    override fun run(): List = webClient.get().uri("http://remote-service/api/stuff").retrieve()

    override fun getFallback(): List = emptyList()
}

you can then call the command like this:

val webClient = ...
val stuffList : List = RemoteApiCommand(webClient).execute()

Not too bad! Hystrix now does its thing by wrapping the call to “http://remote-service/api/stuff” in a circuit breaker. If the call fails an empty list of Stuff is returned thanks to overriding the getFallback method. But since we’re using Kotlin we could arguably do a bit better. Instead of having to create a subclass of HystrixCommand for each code block we want to wrap in Hystrix maybe we can use a DSL and make the code even better looking?

Kystrix

This is where Kystrix comes in! Kystrix provides a small Kotlin DSL over Hystrix to make it easier and more idiomatic to use from Kotlin. The previous example could be rewritten like this using Kystrix:

hystrixCommand> {
    groupKey("MyGroup")
    threadPoolKey("MyThreadPool")
    commandKey("MyCommand")
    command {
        webClient.get().uri("http://remote-service/api/stuff").retrieve()
    }
    fallback {
        emptyList()
    }
    commandProperties {
        withExecutionTimeoutInMilliseconds(4000)
    }
    threadPoolProperties {
        withCoreSize(3)
        withMaxQueueSize(10)
        withQueueSizeRejectionThreshold(10)
    }
}	

This is arguably cleaner and it doesn’t require explicit inheritance. But how do you call it?

val stuffList : List = hystrixCommand> { .. }

I.e. there’s no need to create an instance of the command and execute it. This is done by Kystrix.

Non-blocking IO

Kystrix also provides another DSL, hystrixObservableCommand, for making it easier to work with non-blocking HystrixObservableCommand‘s. For example:

val stuff : Observable = hystrixObservableCommand {
    groupKey("MyGroup")
    commandKey("MyCommand")
    command {
        webClient.get().uri("http://remote-service/api/stuff").retrieveObservable()
    }
    commandProperties {
        withExecutionTimeoutInMilliseconds(4000)
        withExecutionIsolationSemaphoreMaxConcurrentRequests(5)
    }
}	

Spring Support

As of now all Kystrix examples have been using the kystrix-core module. But Kystrix also has special support for Spring by integrating better with its reactive stack such as Webflux. This is provided by the kystrix-spring module. This module provides a couple of useful extension functions to the DSL in kotlin-core. For example if your web client returns a Mono you can make use of two extension functions, monoCommand and toMono, both located in the se.haleby.kystrix package of kystrix-spring:

val stuff : Mono = hystrixObservableCommand {
    groupKey("MyGroup")
    commandKey("MyCommand")
    monoCommand {
        webClient.get().uri("http://remote-service/api/stuff/1").retrieve().bodyToMono()
    }
    commandProperties {
        withExecutionTimeoutInMilliseconds(4000)
        withExecutionIsolationSemaphoreMaxConcurrentRequests(5)
    }.toMono()
}	

Using the monoCommand extension function makes it easy to integrate a Mono response with Hystrix. Also note the call to toMono() at the end, this will convert the Observable returned by Hystrix back to a Mono instance.

There’s also support for Flux by using the fluxCommand and toFlux extension functions:

val stuff : Flux = hystrixObservableCommand {
    groupKey("MyGroup")
    commandKey("MyCommand")
    monoCommand {
        webClient.get().uri("http://remote-service/api/stuff").retrieve().bodyToFlux()
    }
    commandProperties {
        withExecutionTimeoutInMilliseconds(4000)
        withExecutionIsolationSemaphoreMaxConcurrentRequests(5)
    }.toFlux()
}	

Here Kystrix returns a non-blocking stream of Stuff wrapped in a Flux.

But isn’t this 2018?

Ah, I see what you’re thinking! Shouldn’t we all be using service meshes such as Istio or Linkerd today and move the responsibility of circuit breaking, retrying etc out of the application and into the mesh? Well yeah, that seem like a pretty good idea. However I assume that most people by the time of writing has not made the transition to a service mesh. Istio went 1.0 just a couple of months ago and linkerd 2.0 was announced in July. And service meshes might not be the right solution for everyone. So for these reasons I still think that Hystrix has its place, at least for the time being.

Conclusion

As you’ve hopefully seen Kystrix can be a nice little utility in combination with Hystrix when using Kotlin. Feel free to join in and ask questions or submit pull requests at the website. Cheers!

Leave a Reply

Your email address will not be published. Required fields are marked *