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!