1. Introduction
Previously, we have shown how to build a high-performance and reactive application with Ratpack.
In this article, we’ll look at how to integrate Netflix Hystrix with a Ratpack application.
Netflix Hystrix helps control interactions between distributed services by isolating points of access to stop cascading failures and providing fallback options for fault tolerance. It can help us build a more resilient application. See our introduction to Hystrix for a quick review.
And so, that’s how we’ll use it – we’re going to enhance our Ratpack application with these useful features out by Hystrix.
NOTE: the ratpack-hystrix module has been removed: https://github.com/ratpack/ratpack/blob/master/release-notes.md?plain=1 so the code in this article is no longer maintained.
2. Maven Dependency
To use Hystrix with Ratpack, we need the ratpack-hystrix dependency in the project pom.xml:
<dependency>
<groupId>io.ratpack</groupId>
<artifactId>ratpack-hystrix</artifactId>
<version>1.4.6</version>
</dependency>
The latest version of ratpack-hystrix can be found here. The ratpack-hystrix includes ratpack-core and hystrix-core.
To make use of reactive features of Ratpack, we would also need ratpack-rx:
<dependency>
<groupId>io.ratpack</groupId>
<artifactId>ratpack-rx</artifactId>
<version>1.4.6</version>
</dependency>
The latest version of ratpack-rx can be found here.
3. Serving With Hystrix Command
When using Hystrix, underlying services are typically wrapped in HystrixCommand or HystrixObservableCommand. Hystrix supports executing these Commands in ways of synchronous, asynchronous and reactive. Among these, only reactive is nonblocking and officially recommended.
In the following examples, we’ll build some endpoints that fetch a profile from Github REST API.
3.1. Reactive Command Execution
First, let’s build a reactive backend service with Hystrix:
public class HystrixReactiveHttpCommand extends HystrixObservableCommand<String> {
//...
@Override
protected Observable<String> construct() {
return RxRatpack.observe(httpClient
.get(uri, r -> r.headers(h -> h.add("User-Agent", "Baeldung HttpClient")))
.map(res -> res.getBody().getText()));
}
@Override
protected Observable<String> resumeWithFallback() {
return Observable.just("eugenp's reactive fallback profile");
}
}
Here a Ratpack reactive HttpClient is used to make a GET request. The HystrixReactiveHttpCommand can perform as a reactive handler:
chain.get("rx", ctx ->
new HystrixReactiveHttpCommand(
ctx.get(HttpClient.class), eugenGithubProfileUri, timeout)
.toObservable()
.subscribe(ctx::render));
The endpoint can be verified with the following test:
@Test
public void whenFetchReactive_thenGotEugenProfile() {
assertThat(appUnderTest.getHttpClient().getText("rx"),
containsString("www.baeldung.com"));
}
3.2. Asynchronous Command Execution
An asynchronous execution of HystrixCommand queues up the command on the thread pool and returns a Future:
chain.get("async", ctx -> ctx.render(
new HystrixAsyncHttpCommand(eugenGithubProfileUri, timeout)
.queue()
.get()));
The HystrixAsyncHttpCommand looks like:
public class HystrixAsyncHttpCommand extends HystrixCommand<String> {
//...
@Override
protected String run() throws Exception {
return EntityUtils.toString(HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.setDefaultHeaders(Collections.singleton(
new BasicHeader("User-Agent", "Baeldung Blocking HttpClient")))
.build().execute(new HttpGet(uri)).getEntity());
}
@Override
protected String getFallback() {
return "eugenp's async fallback profile";
}
}
Here we use a blocking HttpClient instead of a nonblocking one because we want Hystrix to control the execution timeout of the actual command so that we won’t need to handle it on our own when getting a response from the Future. This also allows Hystrix to fallback or cache our request.
The async execution also yields expected outcome:
@Test
public void whenFetchAsync_thenGotEugenProfile() {
assertThat(appUnderTest.getHttpClient().getText("async"),
containsString("www.baeldung.com"));
}
3.3. Synchronous Command Execution
A synchronous execution executes the command directly in the current thread:
chain.get("sync", ctx -> ctx.render(
new HystrixSyncHttpCommand(eugenGithubProfileUri, timeout).execute()));
The implementation of HystrixSyncHttpCommand is almost identical to HystrixAsyncHttpCommand except that we give it a different fall back result. When not falling back, it behaves the just the same as reactive and asynchronous execution:
@Test
public void whenFetchSync_thenGotEugenProfile() {
assertThat(appUnderTest.getHttpClient().getText("sync"),
containsString("www.baeldung.com"));
}
4. Metrics
By registering the Guice module – HystrixModule into Ratpack registry, we can stream the request scoped metrics and expose the event streams via a GET endpoint:
serverSpec.registry(
Guice.registry(spec -> spec.module(new HystrixModule().sse())))
.handlers(c -> c.get("hystrix", new HystrixMetricsEventStreamHandler()));
The HystrixMetricsEventStreamHandler helps stream Hystrix metrics in text/event-stream format, such that we can monitor the metrics in Hystrix Dashboard.
We can set up a standalone Hystrix dashboard, and add our Hystrix event-stream to the monitor list to see how our Ratpack application performs:
After several requests to our Ratpack application, we can see the Hystrix related commands in the dashboard.
4.1. Under the Hood
In HystrixModule, a Hystrix Concurrency Strategy is registered with Hystrix via HystrixPlugin to manage the request context with Ratpack registry. This removes the necessity for initializing Hystrix request context before each request begins.
public class HystrixModule extends ConfigurableModule<HystrixModule.Config> {
//...
@Override
protected void configure() {
try {
HystrixPlugins.getInstance().registerConcurrencyStrategy(
new HystrixRegistryBackedConcurrencyStrategy());
} catch (IllegalStateException e) {
//...
}
}
//...
}
5. Conclusion
In this quick article, we’ve shown how Hystrix can be integrated into Ratpack and how to push metrics of our Ratpack application to Hystrix Dashboard for a better view of the application performance.
As always, the full implementation can be found over on the Github project.