Bootiful CMS part 4 - Resilient Microservice Client

In this blog post we will change the blog post editor backend to use the microservice from the previous post, and we will make it resilient by building in client side load balancing with Netflix Ribbon and a circuit breaker using Hystrix.

Post backend changes

We already wrote the blog post microservice in the previous blog post, so we only need to change the blog post editor backend now. Let’s start by adding some dependencies to the build file:

editor/post/post-backend/build.gradle
...
dependencies {
...
compile 'org.springframework.cloud:spring-cloud-starter-feign:1.0.3.RELEASE'
compile 'org.springframework.cloud:spring-cloud-starter-hystrix:1.0.3.RELEASE'
}
...

The first dependency adds support for Netflix Feign, a library that takes away most of the boilerplate code when writing a REST service client, and the second dependency add Netflix Hystrix, an implementation of the circuit breaker pattern, once again removing most of the boilerplate code involved.

Feign client

Now that we have the correct dependencies, we can start writing the REST client for the post service we built in the previous blog post. Thanks to Netflix Feign, this is really simple.

First, we need to enable Feign in our Spring Boot application, which means adding an annotation to the Application class:

editor/post/post-backend/src/main/java/be/beeworks/editor/post/Application.java
...
@EnableFeignClients
public class Application extends SpringBootServletInitializer {
...

Writing a REST client with Spring Cloud Feign is very similar to what you would do with Spring Data. You write an interface for the client, annotate it with the FeignClient annotation to indicate this is a Feign client, and you annotate all methods and all method parameters with the same annotations you use in the controller of the service. So it helps if you have access to the source code of the service.

editor/post/post-backend/src/main/java/be/beeworks/editor/post/gateway/PostServiceClient.java
package be.beeworks.editor.post.gateway;

import be.beeworks.editor.post.model.Post;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.List;

@FeignClient("post-service")
public interface PostServiceClient {
    @RequestMapping(method = RequestMethod.GET, value="/posts")
    List<Post> listPosts();
    @RequestMapping(method = RequestMethod.GET, value="/posts/{id}")
    Post getPost(@PathVariable("id") Long id);
    @RequestMapping(method = RequestMethod.POST, value="/posts", consumes = "application/json")
    void createPost(@RequestBody Post post);
    @RequestMapping(method = RequestMethod.PATCH, value="/posts/{id}", consumes = "application/json")
    void updatePost(@PathVariable("id") Long id, @RequestBody Post post);
}

As you can see, we’re using the name post-service in the FeignClient annotation. This is the name we used to register the blog post service in Eureka. So in this case, Spring Cloud Feign will use Eureka to find an instance of the post service. Spring Cloud Feign also use Netflix Ribbon, so our post service client does client-side load-balancing out of the box. If you’re not using Eureka, it’s possible to specify the URL of the service in this annotation, using the url attribute.

Of course, you also need to have the model, in this case the Post class. We already have this class from our original implementation in the first blog post. All we need to do now is remove the JPA annotations.

editor/post/post-backend/src/main/java/be/beeworks/editor/post/model/Post.java
package be.beeworks.editor.post.model;

public class Post {
    private Long id;
    private String title;
    private String content;

    public Post() {
    }

    public Post(String title) {
        this.title = title;
    }

    public Post(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

Replacing the Post Repository and adding Hystrix

Now that we have a REST client for the post service, we can replace the Spring Data Post Repository with a repository using the client. This repository is the perfect place to add a resilience wrapper with Hystrix. Hystrix is an implementation of the Circuit Breaker pattern. When you want to protect a function call, you wrap it in a Hystrix command (using an annotation of course), which monitors for failures. When the failures reach a certain threshold, the circuit breaker trips, and all further calls result in a call to a fallback method you specify in the annotation. Hystrix continues to monitor, and once the problem on the remote service is solved, the circuit is restored and the original function call is used again. This pattern is extremely important in a microservice architecture, as it protects consumers from failures in their dependencies.

Let’s take a look at the new repository class:

editor/post/post-backend/src/main/java/be/beeworks/editor/post/repository/PostRepository.java
package be.beeworks.editor.post.repository;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import be.beeworks.editor.post.gateway.PostServiceClient;
import be.beeworks.editor.post.model.Post;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.Arrays;
import java.util.List;

@Component
public class PostRepository {
    @Autowired
    private PostServiceClient postServiceClient;

    @HystrixCommand(fallbackMethod = "defaultPosts")
    public List<Post> listPosts() {
        return postServiceClient.listPosts();
    }

    public List<Post> defaultPosts() {
        return Arrays.asList(new Post("fallback post", "the post service seems to be down"));
    }

    @HystrixCommand(fallbackMethod = "defaultPost")
    public Post getPost(@PathVariable("id") Long id) {
        return postServiceClient.getPost(id);
    }

    public Post defaultPost(Long id) {
        return new Post();
    }

    @HystrixCommand(fallbackMethod = "createPostBlowingInTheWind")
    public void createPost(@RequestBody Post post) {
        postServiceClient.createPost(post);
    }

    public void createPostBlowingInTheWind(Post post) {
        // do nothing
    }

    @HystrixCommand(fallbackMethod = "updatePostBlowingInTheWind")
    public void updatePost(@PathVariable("id") Long id, @RequestBody Post post) {
        postServiceClient.updatePost(id, post);
    }

    public void updatePostBlowingInTheWind(Long id, Post post) {
        // do nothing
    }
}

First we inject the PostServiceClient as a dependency. This repository uses the post service after all. The listPosts method uses the PostServiceClient to fetch the list of blog posts from the post service. This method is annotated with the HystrixCommand annotation, where we specify the fallback method defaultPosts. This method has the same signature as the listPosts method and returns a default list, which is fine for our example. In a real life application, what should happen when the circuit trips would typically be a decision a Product Owner makes. Suppose the circuit protecting calls from a website backend to Elastic Search trips, you could imagine that this fallback method results in the search box on the website being hidden.

We have similar fallback methods for all CRUD operations in the repository. As a result, our example application will never show a 500 Internal Server Error when the post service is down.

Putting it all together

The Article Controller in our backend doesn’t change much. The createSamplePosts method can be removed. In a decent application the controller should not change at all when change the underlying store!

editor/post/post-backend/src/main/java/be/beeworks/editor/post/controller/PostController.java
package be.beeworks.editor.post.controller;
import be.beeworks.editor.post.model.Post;
import be.beeworks.editor.post.repository.PostRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import javax.transaction.Transactional;
import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("/posts")
public class PostController {

    private static final Logger logger = LoggerFactory.getLogger(PostController.class);

    @Autowired
    private PostRepository postRepository;

    @RequestMapping(value = "", method = RequestMethod.GET)
    @Secured({"ROLE_VIEW_POST"})
    public Iterable<Post> getPosts() {
        logger.info("requesting posts");
        return postRepository.findAll();
    }

    @PostConstruct
    public void createSamplePosts() {
        postRepository.save(new Post("sample post 1 from database", "sample content 1"));
        postRepository.save(new Post("sample post 2 from database", "sample content 2"));
    }
}

Let’s try if everything works as it should. Start the Redis server, the discovery service, the post backend and the post gateway, and the post service, each of the following commands in a different terminal window:

redis-server
cd discovery; gradle bootRun
cd editor/post; gradle bootRun
cd editor/gateway; gradle bootRun
cd services/post; gradle bootRun

When you open the blog post editor in the browser, on http://localhost:8080/#/posts, you should get the following screen:

Post Editor with no posts

The post service is still empty, so no blog posts are shown. Let’s add some posts, using the curl command we can find in the wonderful Post Service documentation from the previous blog post:

curl 'http://localhost:9080/posts' -i -X POST \
 -H 'Content-Type: application/json' \
 -d '{"title" : "sample post 1 from REST service","content" : "sample content 1"}'

Now there is a blog post in the posts service, let’s reload the blog post editor:

Post Editor with the post from the service

What happens when we shut down the post service?

Post Editor with fallback

The circuit breaker has tripped, and the fallback method was called instead, resulting in the default list of posts. When we restart the post service, the circuit will be reset, and everything will function as it should again.