How-tos
Connecting a Spring Cloud application to Cloudant Service with Feign and Hystrix
August 7, 2017 | Written by: Jeffrey Ruffolo
Categorized: How-tos
Share this post:
In this post, we’ll create a simple Spring Cloud application that demonstrates the capabilities of Feign and Hystrix by connecting to a Cloudant service on Bluemix. Feign is a declarative web service client, which comes with Hystrix built in when you use it with Spring Cloud. Hystrix is a Netflix OSS library that implements the circuit breaker pattern.
Prerequisites
For this project, you will only need to have Java and Maven installed and configured on your PATH
. I will also assume you have setup a Cloudant service in Bluemix and have the credentials available.
Building the Spring Cloud Application
Setup Maven project
To begin, let’s create a project with the following structure:
project/spring-feign-hystrix/src/main/java/application/
Next create a pom.xml file with the following contents:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion><groupId>com.application</groupId>
<artifactId>spring-feign-hystrix</artifactId>
<version>1.0-SNAPSHOT</version><parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent><dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement><dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
</dependencies><properties>
<java.version>1.8</java.version>
</properties><build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Create FeignClient with Hystrix Fallback
The first class we create will be the FeignClient, and it will also contain the Hystrix implementation. There will be a lot happening in this class, so we’ll break it down and build it one step at a time. I’ll include the completed class at the end, if you want to see it all together.
Create a HystrixClient.java file with the following contents (I’ve left out the imports I used for now, but will provide them in the assembled file contents):
@FeignClient(name = "hystrixclient", url = "localhost:8090", fallback = HystrixClient.HystrixClientFallback.class)
public interface HystrixClient {@RequestMapping(value = "/populateCloudant", method = RequestMethod.GET)
ResponseEntity populateCloudant();
In this code, we create an interface named HystrixClient
and annotate it with @FeignClient
. A name is required for our FeignClient, and can be anything you choose. The URL provided points to the service this FeignClient will interface with; in this case, it will be a service that saves documents to a Cloudant database. The fallback describes what should happen if the service does not respond appropriately.
This interface has one method: populateCloudant
. The @RequestMapping
annotation indicates which REST endpoint the method corresponds to. Next, we’ll setup the fallback for this method.
@Component
class HystrixClientFallback implements HystrixClient {@Override
public ResponseEntity populateCloudant() {
return new ResponseEntity("Fallback\n", HttpStatus.OK);
}
}
Here we have defined a subclass to implement the fallback behavior I mentioned above. The populateCloudant
method is overridden and returns a ResponseEntity
telling us it has resorted to the fallback method. Near the end of this post, we will demonstrate what circumstances can lead to this fallback method being called.
In a more practical application, you might choose to use the fallback that allows your application to function in the event of failed services. Here, however, we are just interested in seeing that it has used the fallback.
Sometimes, it is useful to know what caused the fallback. In Spring Cloud, we can do this by implementing the FallbackFactory
. This code is similar in form and function to the HystrixClientFallback
we created previously. However, instead of just informing us that a fallback has occurred, it will give us some information about why it happened.
@Component
class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {@Override
public HystrixClient create(Throwable cause){
return new HystrixClient(){
@Override
public ResponseEntity populateCloudant() {
return new ResponseEntity("Fallback cause: " + cause.getMessage() + "\n", HttpStatus.OK);
}
};
}
}
In order to use the FallbackFactory, we will need to change this line from the @FeignClient
annotation above:
@FeignClient(name = "hystrixclient", url = "localhost:8090", fallbackFactory = HystrixClient.HystrixClientFallbackFactory.class)
Here is the HystrixClient.java
file all together:
package application;import feign.hystrix.FallbackFactory;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;@FeignClient(name = "hystrixclient", url = "localhost:8090", fallbackFactory = HystrixClient.HystrixClientFallbackFactory.class)
public interface HystrixClient {@RequestMapping(value = "/populateCloudant", method = RequestMethod.GET)
ResponseEntity populateCloudant();@Component
class HystrixClientFallback implements HystrixClient {@Override
public ResponseEntity populateCloudant() {
return new ResponseEntity("Fallback\n", HttpStatus.OK);
}
}@Component
class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {@Override
public HystrixClient create(Throwable cause){
return new HystrixClient(){
@Override
public ResponseEntity populateCloudant() {
return new ResponseEntity("Fallback cause: " + cause.getMessage() + "\n", HttpStatus.OK);
}
};
}
}
}
Make the Application Accessible
Now let’s finish up this application by making it executable and creating a REST endpoint. Create a Application.java
file with the following contents:
package application;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@SpringBootApplication
@RestController
@EnableFeignClients
@EnableCircuitBreaker
public class Application {@Autowired
private HystrixClient hystrixClient;public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}@RequestMapping("/feign")
public ResponseEntity feign() {
return hystrixClient.populateCloudant();
}
}
This class is fairly standard for a Spring application, but with the addition of two annotations: @EnableFeignClients
and @EnableCircuitBreaker
, which do exactly what their names imply.
The REST endpoint (/feign
) will be used later to test our application. It calls the populateCloudant
method from our FeignClient and returns a ResponseEntity
.
Configuring Hystrix
Hystrix is a Netflix library that implements the circuit breaker pattern. Normally, use of this library would require you to wrap all methods in HystrixCommand
s. But with a FeignClient, Hystrix functionality comes included. You just need to set a few properties to configure Hystrix to your needs.
Create an application.properties
file with the following contents:
feign.hystrix.enabled=true
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=4000
hystrix.command.default.circuitBreaker.enabled=true
hystrix.command.default.circuitBreaker.requestVolumeThreshold=2
The first property enables Hystrix on the FeignClients in our application. Next, we set the maximum amount of time allowed before a request is considered to be timed out and fail. Finally, we enable Hystrix circuit breakers, as well as setting the number of failed requests that will cause the circuit to open.
Note: To see other available properties, visit the Hystrix Netflix configuration wiki.
Building the Cloudant Service Application
Setup Maven project
Now create a new project with the following structure:
project/cloudant-service/src/main/java/application/
Next create a pom.xml file with the following contents:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion><groupId>com.application</groupId>
<artifactId>cloudant-service</artifactId>
<version>1.0-SNAPSHOT</version><parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent><dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.cloudant</groupId>
<artifactId>cloudant-client</artifactId>
<version>2.7.0</version>
</dependency>
</dependencies><properties>
<java.version>1.8</java.version>
</properties><build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Configure CloudantClient
Find the connection credentials for your Cloudant service in Bluemix (under the Service credentials tab). Then create an application.properties
file with the following contents:
server.port=8090cloudant.url=CLOUDANT_URL
cloudant.username=CLOUDANT_USERNAME
cloudant.password=CLOUDANT_PASSWORD
You will need to fill in the values from your own credentials, of course. Also, be sure to handle your credentials securely if you intend to deploy this application beyond your local runtime.
Next, we will create a configuration class, which will use these credentials to create a CloudantClient
bean. Create a CloudantClientConfig
file with the following contents:
package Application;import com.cloudant.client.api.ClientBuilder;
import com.cloudant.client.api.CloudantClient;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;import java.net.MalformedURLException;
import java.net.URL;@Configuration
public class CloudantClientConfig {@Value("${cloudant.url}")
protected String cloudantUrl;@Value("${cloudant.username}")
protected String cloudantUsername;@Value("${cloudant.password}")
protected String cloudantPassword;@Bean(destroyMethod = "shutdown")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public CloudantClient cloudantClient(InjectionPoint ip) throws MalformedURLException {
CloudantClient client = ClientBuilder.url(new URL(cloudantUrl))
.username(cloudantUsername)
.password(cloudantPassword)
.build();
return client;
}
}
Make the Service Accessible
First, let’s create a document that we can store in our Cloudant database. Create an ExampleDocument.java
file with the following contents:
package Application;import java.util.UUID;
public class ExampleDocument {
private String id = UUID.randomUUID().toString();
public String toString() {
return "{ id: " + id + "}";
}
}
Now let’s make the application executable and create the REST endpoint that our FeignClient from above is expecting. Create an Application.java
file with the following contents:
package Application;import com.cloudant.client.api.CloudantClient;
import com.cloudant.client.api.Database;
import com.cloudant.client.org.lightcouch.NoDocumentException;
import com.cloudant.client.org.lightcouch.TooManyRequestsException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@SpringBootApplication
@RestController
public class Application {public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}@Autowired
private CloudantClient client;@RequestMapping("/populateCloudant")
public ResponseEntity cloudant(){
Database db = client.database("example-db", true);try{
db.save(new ExampleDocument());
} catch (TooManyRequestsException e){
e.printStackTrace();
return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR);
}return new ResponseEntity(HttpStatus.OK);
}
}
When a request is sent to the /populateCloudant
REST endpoint, it will find a database named “example-db” (and create if not found), then try to save an instance of ExampleDocument
in it. Because Cloudant is rate-limited, it is possible that our CloudantClient
may be unable to write to the database. We catch the corresponding exception here.
Testing the Application
Now we’ll do a couple tests to see if everything is working. Startup both of your Spring applications locally by running the following command in both project directories:
$ mvn spring-boot:run
First, let’s check that the FeignClient is working with the “cloudant-service” application:
$ curl localhost:8080/feign
This request should finish after about a second with no message. Next, let’s test the case where the “cloudant-service” does not respond. This scenario is similar to what would occur if you hit the rate-limit for your Cloudant service in Bluemix.
We’ll force our “cloudant-service” to not respond by ending its process. After you do that, let’s test the circuit breaker:
$ curl localhost:8080/feign
Fallback cause: Connection refused (Connection refused) executing GET http://localhost:8090/populateCloudant
$ curl localhost:8080/feign
Fallback cause: Connection refused (Connection refused) executing GET http://localhost:8090/populateCloudant
$ curl localhost:8080/feign
Fallback cause: Hystrix circuit short-circuited and is OPEN
After running these commands, you should see a message saying “Fallback cause: Hystrix circuit short-circuited and is OPEN.” Because the request has failed more than two times (a threshold we specified in the circuitbreaker properties above), the circuit has opened and will respond to all requests with the fallback method. The default amount of time for the circuit to remain open is 10 seconds. After which, it will close and continue trying to send requests to the service.
Conclusion
If you followed along with me through this post, you have now created a Spring Cloud application that includes Feign and Hystrix functionality. Our application uses a FeignClient to connect to a Cloudant service on Bluemix, and has Hystrix circuit breaker protection for failed requests. We demonstrated the Hystrix functionality with a couple simple tests.
We’ve Moved! The IBM Cloud Blog Has a New URL
In an effort better integrate the IBM Cloud Blog with the IBM Cloud web experience, we have migrated the blog to a new URL: www.ibm.com/cloud/blog.
Use IBM Cloud Certificate Manager to Obtain Let’s Encrypt TLS Certificates for Your Public Domains
IBM Cloud Certificate Manager now lets you obtain TLS certificates signed by Let’s Encrypt. Let’s Encrypt is an automated, ACME-protocol-based CA that issues free certificates valid for 90 days.
Are You Ready for SAP S/4HANA Running on Cloud?
Our clients tell us SAP applications are central to their success and strategy for cloud, with a deadline to refresh the business processes and move to SAP S/4HANA by 2025. Now is the time to assess, plan and execute the journey to cloud and SAP S/4HANA