Circuit Breaker Pattern with CRISP API
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.
Demo Components with Hystrix
Let's see the example business service bean, ProductService, in the demo project. 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(); 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 demo 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 demo 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.
Test in Demo Project
Please see Demo Project page to learn how to build and run the demo project.
Here's a test scenario:
- After running the demo project, visit http://localhost:8080/site/products.
You will see a list of products retrieved by the ProductListComponent which invokes ProductService#getProductCollection() operation. - The ProductService#getProductCollection() operation is configured with 3 second execution timeout in the @HystrixCommand annotation.
So, if you let the ProductService#getProductCollection() time out somehow (by either attaching a debugger to wait more than 3 seconds or adding a line to wait forcefully like "Thread.sleep(5000);" as an example), then you will see only 3 product items in the page when the page gets refreshed. Those 3 product items are coming from a classpath resource ("cached-products.json", as simple fallback example data) because the original operation call timed out. - Or, if you let the ProductService#getProductCollection() throw an exception somehow (by either adding a line like "throw new RuntimeException();" or attaching a debugger to set productCatalogs variable to null and throw a NullPointerException as an example), then you will see only 3 product items, coming from the local JSON resource file in the classpath as a fallback, in the page because the original operation call fails with an exception.
- Finally, if you revert any changes (that was for either forceful exception or time out) to the original state, or if you disconnect the debugger, then the page will start rendering the whole list of the products again.
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 . You can find all the examples shown above in Demo Project.
References
- Netflix Hystrix hystrix-javanica project, https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-javanica