Circuit Breaker Pattern with CRISP API

The POJO Mapping feature, used in the examples below, has been supported since v2.1.1.

Introduction

You can apply the Circuit Breaker Pattern in your components and services which are using CRISP API. For example, you can implement HstComponents as Spring-managed components and business service components which are auto-wired into the HstComponents. The business service layer components may include some Circuit Breaker framework annotations (such as @HystrixCommand when using Netflix Hystrix framework) to fallback to another method when the operation fails or timed out while trying to get a result.

As CRISP API basically provides Resource objects in a very thin layer on top of the backend JSON or XML data from REST API invocations, there is no significant overhead by applying Circuit Breaker Pattern on top of CRISP API. It could be more beneficial because you can take advantage of all the CRISP API features such as repository-based configuration, default caching control, generic object pattern for templating, etc.

In the following sections, you will learn about how to apply Netflix Hystrix framework in a delivery tier web application with some examples including a Spring-managed component and a business service component having a @HystrixCommand operation that reads data through CRISP API.

How to Apply Netflix Hystrix Framework in Delivery tier Web Application?

As your delivery tier web application is not a Spring Boot application typically, you need to add hystrix-javanica dependency. See hystrix-javanica project for detail.

First, add the following dependency with a version in the root pom.xml:

  <properties>

    <!-- SNIP -->

    <!-- If necessary, update the version to the most proper and latest one. -->
    <hystrix-javanica.version>1.5.12</hystrix-javanica.version>

    <!-- SNIP -->

  </properties>

  <dependencyManagement>

    <dependencies>

      <!-- SNIP -->

      <dependency>
        <groupId>com.netflix.hystrix</groupId>
        <artifactId>hystrix-javanica</artifactId>
        <version>${hystrix-javanica.version}</version>
      </dependency>

      <!-- SNIP -->

    </dependencies>

  </dependencyManagement>

And, add the dependency in site/pom.xml:

  <dependencies>

    <!-- SNIP -->

    <dependency>
      <groupId>com.netflix.hystrix</groupId>
      <artifactId>hystrix-javanica</artifactId>
    </dependency>

    <!-- SNIP -->

  </dependencies>

Spring Configurations for Hystrix and Spring-managed components

As we use Spring AOP in the delivery tier web application project, we need to add the following configuration to make Spring capable to manage aspects in AspectJ and @HystrixCommandAspect. Add the following into site/src/main/resources/META-INF/hst-assembly/overrides/hystrix-javanica-spring-aspect.xml for instance:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">

  <!-- Make Spring capable to manage aspects which were written using AspectJ. -->
  <aop:aspectj-autoproxy />

  <!-- Declare HystrixCommandAspect which was written using AspectJ for HystrixCommand handling. --> 
  <bean id="hystrixAspect"
        class="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect">
  </bean>

</beans>

Also, as we want to have a Spring-managed component which a business service bean is automatically wired into, we need to add the following into site/src/main/resources/META-INF/hst-assembly/overrides/spring-managed-components.xml for instance:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">

  <!-- (HST)Components Annotation Scanning -->
  <!--
    In this example, it'a assumed that all the Spring-managed components are in org.onehippo.cms7.crisp.demo.components package,
    and any other auto-wired business service beans are in org.onehippo.cms7.crisp.demo.service package.
  -->
  <context:component-scan
      base-package="org.onehippo.cms7.crisp.demo.components,org.onehippo.cms7.crisp.demo.service" />

</beans>

Now, you're ready to implement Netflix Hystrix framework enabled components in your delivery tier web application. 

Example Components with Hystrix

Let's see an example business service bean, ProductService. The service bean contains one @HystrixCommand operation, #getProductCollection(), which invokes CRISP API, and it can fall back to #getReliableProductCollection() automatically by Netflix Hystrix framework if the original #getProductCollection() call fails or timed out.

@Service
public class ProductService {

    private static Logger log = LoggerFactory.getLogger(ProductService.class);

    // demo cached json file as fallback data.
    private static final URL DEMO_LOCAL_CACHED_PRODUCTS_JSON_URL = ProductService.class.getResource("cached-products.json");
    // demo object mapper to parse json data to POJO in fallback operation.
    private ObjectMapper objectMapper = new ObjectMapper();

    /**
     * The example Hystrix command operation with a fallback method and execution timeout (3 seconds in this demo).
     */
    @HystrixCommand(
            fallbackMethod = "getReliableProductCollection",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
            }
        )
    public Collection<Product> getProductCollection() {
        Resource productCatalogs = null;

        ResourceServiceBroker resourceServiceBroker = CrispHstServices.getDefaultResourceServiceBroker(HstServices.getComponentManager());
        final Map<String, Object> pathVars = new HashMap<>();
        productCatalogs = resourceServiceBroker.findResources(RESOURCE_SPACE_DEMO_PRODUCT_CATALOG, "/products/",
                pathVars);

        ResourceBeanMapper resourceBeanMapper = resourceServiceBroker
                .getResourceBeanMapper(RESOURCE_SPACE_DEMO_PRODUCT_CATALOG);
        Collection<Product> productCollection = resourceBeanMapper.mapCollection(productCatalogs.getChildren(),
                Product.class);
        return productCollection;
    }

    /**
     * A reliable fallback operation example, reading cached data from a JSON data file in the classpath.
     */
    public Collection<Product> getReliableProductCollection() {
        List<Product> productsList = new LinkedList<>();

        InputStream is = null;
        BufferedInputStream bis = null;

        try {
            is = DEMO_LOCAL_CACHED_PRODUCTS_JSON_URL.openStream();
            bis = new BufferedInputStream(is);
            JsonNode root = objectMapper.readTree(bis);

            for (Iterator<JsonNode> it = root.elements(); it.hasNext();) {
                JsonNode elem = it.next();
                Product product = objectMapper.convertValue(elem, Product.class);
                productsList.add(product);
            }
        } catch (Exception e) {
            log.error("Failed to read data from json resource file.", e);
        } finally {
            IOUtils.closeQuietly(bis);
            IOUtils.closeQuietly(is);
        }

        return productsList;
    }
}

Please note that the fallback operation, #getReliableProductCollection(), simply reads data from the local JSON data file in the classpath for simplicity and demonstration purpose.

Now, the example HstComponent code, ProductListComponent, looks like this:

@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ProductListComponent extends BaseHstComponent {

    private static Logger log = LoggerFactory.getLogger(ProductListComponent.class);

    @Autowired
    private ProductService productService;

    @Override
    public void doBeforeRender(final HstRequest request, final HstResponse response) {
        super.doBeforeRender(request, response);

        Collection<Product> products = productService.getProductCollection();
        request.setAttribute("products", products);
    }
}

As the example HstComponent, ProductListComponent, is instantiated by Spring Framework, the productService member will be automatically wired into it. In its #doBeforeRender(...) method, it invokes the ProductService#getProductCollection() operation. Spring AOP and Netflix Hystrix framework create a dynamic proxy for the business service bean as it has a @HystrixCommand annotated operation, so the caller (ProductListComponent in this case) may invoke the service operation transparently without concerning too much. Under the hood, Netflix Hystrix framework establishes Circuit Breaker Pattern automatically for the service.

Summary

Circuit Breaker Pattern can be applied easily to your delivery tier web application project by using Netflix Hystrix framework. To make it easier, you can implement HstComponents as Spring-managed components and get business service components to be auto-wired into the HstComponents. This way, Spring AOP and Netflix Hystrix framework will be able to create dynamic proxies to enable Circuit Breaker Pattern automatically. You may implement @HystrixCommand operations to fallback to another when the operation, using CRISP API inside, fails with an exception or time out.

References

Did you find this page helpful?
How could this documentation serve you better?
On this page
    Did you find this page helpful?
    How could this documentation serve you better?