How I Decoupled Circuit Breaker from the Code with AOP in Spring Boot for Better Code Maintenance

Hi there, this is my first post, but I want to start this with a new change, rather than writing the whole post in paragraphs, which you always read online, I decided to write these post with mostly bullet points but will also combined with small paragraphs, which in fact easy to focus and read, also concise.

  • Create a sample Downstream API using Mockoon
  • Create a Spring Boot Application to call the API
  • Include a Circuit Breaker for the downstream call
  • Give a brief introduction of AOP (Aspect Oriented Programming)
  • Decouple Circuit Breaker with Aspect Oriented Programming
  • Deal with Supplier from Java Lambdas to handle exceptions

So that you won’t need to touch the business logic whenever you change your circuit breaker from Resilience4j, to something else like Sentinel from Alibaba since Hystrix from Netflix is resting in peace for the moment.

  • Talking about what a circuit breaker is
  • Using best programming practice to create the downstream API call
  • Using best programming practice to handle exceptions
  • Using best programming practice on the logging of the application
  • Discuss full details on the configurations of Resilience4j (But will mention some of the configs and it’s behavior)

That being said, this is more focused on the architecture on how to implement a circuit breaker.

  • Some understand of circuit breakers
  • Some understanding of Java Lambdas
  • Some understanding of how controller and services layers works in Spring Boot

First things first

  • It is all about programming modularity
  • Aspect do the modularization by cut across types and objects
  • Also known as cross cutting concerns in AOP
  • Without modifying the already existing code, AOP inject new behavior to the code
  • AOP framework enables us to implement these type of behavior
  • Spring IoC does not depend on AOP
  • In this example AOP is implemented by annotating a component with @Aspect annotation
  • In this example we are going to execute a downstream calling method
  • It becomes a point of a method execution of the downstream API caller
  • So it’s a point during the execution of an application
  • We are going to introduce new behavior to that point, surrounding it
  • And it’s called Join Point
  • Since Join Point is a point of method execution, we need to introduce a behavior to that point before and after, surrounding that point of execution
  • We are surrounding it, meaning that we can introduce new behavior before executing that point and also after executing that point
  • For example if that Join Point being a method, we can modify arguments before executing that method, and also modify the return values from that method
  • For another example we can even decide if we want to proceed to that Join Point, or can discard return values from the Join Point or can also throw an exception
  • There are few other advices too, like Before advice, After returning advice, After throwing advice, After (finally) advice, but Around advice being the most powerful here

That’s it for the basic theory part, so let’s begin the implementation

Creating the Application

  • Spring Boot 2.4.1
  • Resilience4j 1.6.1 (the Circuit Breaker)
  • JSON Simple 1.1.1 (to Encode/Decode JSON)
  • spring-boot-starter-web is used to import HTTP and web methods
  • spring-boot-starter-aop is used to import Aspect Oriented Framework
  • resilience4j-all, resilience4j-circuitbreaker, resilience4j-spring-boot2 is used to import all the required Resilience4j beans and methods
  • json-simple is used to import all the JSON encode decode functionality
  • Three methods doing the same, calling a downstream API and catch any errors and rethrow as RuntimeException
  • These methods will have a response from a downstream API, but,
  • callTest() will get the response from downstream API, immediately
  • callTestSlow() will get the response but the response will have a delay more than 2 seconds
  • callTestError() will return an error from the downstream API
  • meaning of the @EnableCircuitBreakerScan will be discussed below
  • These three methods call the methods in the service layer mentioned previously
  • Returns a response wrapped in ResponseEntity
  • Rest Controller Advice will handle exceptions whenever application throws exceptions
  • In here, custom defined, CircuitBreakerDownstreamCallException will be handled by the Rest Controller Advice
  • Rest Controller Advice is not directly related to this post anyways, if you haven’t use it before, it’s used to catch application-wide exceptions and handle them efficiently

To understand better on the AOP

  • This is an example of AOP Around Advice
  • @Component is annotated with @Aspect annotation to mark this Component will also have AOP capabilities
  • around method is annotated with @Around to mark, that this method is capable of injecting new behaviors before and after a Join Point declared within this method
  • There is an annotation declared as EnableCircuitBreakerScan in the package of com.skaveesh.dcb.annotation (Project is provided below)
  • We can use this annotation to mark any method within the application, like I have annotated in DownstreamService.java in the above
  • Target Element Type will only be Methods
  • Methods annotated with @EnableCircuitBreakerScan will become the Join Points
  • And those methods will be passed to around method wrapped as ProceedingJoinPoint as a parameter
  • proceedingJoinPoint.getArgs() will get the parameters passed to Join Point method
  • proceedingJoinPoint.proceed(args) will simply execute the Join Point method without any modification
  • Then simply return the value returned by the Join Point
  • In this post, since our service layer methods (Join Points) does not take any parameters we can remove
    Object[] args = proceedingJoinPoint.getArgs()
  • So that makes proceed method to not to take arguments, which will look like this proceedingJoinPoint.proceed()
  • proceed is a overloaded method in the ProceedingJoinPoint interface

Before proceeding further, I want to show you something for better understanding

  • This is the service layer shown in above, but this time Resilience4j circuit breaker is coupled with service layer
  • What if the circuit breaker is changed from Resilience4j to some other?
  • We will have to change the service layer and it’s methods according to future needs
  • Downstream call would not probably be a Supplier in some other circuit breakers
  • This is the not the way we should implement if we intend to decouple the code
return Decorators.ofSupplier(supplier)
.withCircuitBreaker(circuitBreaker)
.withFallback(fallback)
.get();
  • This code block here shows how Resilience4j circuit breaker calls a Supplier with a fallback method
  • fallback method handles the exceptions
  • Will be using the same thing when we implement this with AOP

this::fallback is same as (Throwable ex) -> this.fallback(ex), but it’s called method reference

So proceedingJoinPoint::proceed also same as
() -> proceedingJoinPoint.proceed()

Next thing we will see how I overcame this whole “coupled” thing by using AOP

  • As I shown before, around method is there, also execute and fallback too.
  • And the Throwable from around method signature is gone
  • Also there’s a new method showed up — rethrowSupplier
  • rethrowSupplier handles the Throwables inside itself, so that’s why it’s removed from the around method signature
Object proceed() throws Throwable;
  • proceed method from ProceedingJoinPoint is declared as in the above code block, but there’s a catch — it throws a Throwable
  • Supplier<T> parameter of execute method which is shown in the below code block cannot handle proceed method because of that reason
@FunctionalInterface
public interface Supplier<T> {
T get();
}
  • Also we cannot modify Decorators.ofSupplier(supplier) to take custom Supplier that accept Throwables, because it is declared inside the resilience4j-all library
  • We can’t also modify proceed method because it’s from AOP framework
  • So that left us with an option which is to create a method that accept a custom Supplier with Throwable which is equivalent to proceed method and returns a Supplier without the Throwable which can then be passed to the execute method
  • That method we are going to create is rethrowSupplier
  • You see a new Supplier named SupplierWithException is defined inside the class — SupplierUtil
  • It’s same as the Object proceed() throws Throwable method from ProceedingJoinPoint
  • rethrowSupplier returns the get
  • Our Join Points will be passed to here and the same time the try catch block handles the Throwables too
  • Throwable will be handled as a unchecked exception by the throwAsUnchecked method
  • Config that I used are explained within the application.yml as you can see
  • Instance name myAPOConfiguredMyCircuitBreaker is used in CircuitBreakerAOPConfig.java as the config that the circuit breaker should use
  • Let’s create equivalent downstream API defined in the DownstreamService.java
  • You can use any API mock tool you like
  • http://localhost:3000/test is just a API call that returns success such as 200
  • http://localhost:3000/testslow API call that returns success code but should be delayed more than 2 seconds
  • Only, more than 2 seconds calls will be recorded by Resilience4j as slow API calls, as per the config we defined
  • http://localhost:3000/testerror API call that returns any error. In this case it’s a 400
  • You can set the response body as any JSON; It doesn’t matter here
test API call
testslow API call
  • Notice that route specific latency is 2500ms in above screenshot
testerror API call
  • There’s is no effect on the how the program should behave whether it’s decoupled or not — because it’s all about the implementation
  • After running the application,
  • If you call the http://localhost:9999/calltestendpoint it will give you a result as usual
  • If you call the http://localhost:9999/calltestslowendpoint it will give you a result delayed around 2500ms
  • If you call it three times circuit will be in opened state
  • If you call the http://localhost:9999/calltesterrorendpoint it will give you an error
  • This is how I decoupled the circuit breaker with custom annotation using AOP
  • You can use that AOP class to define another circuit breaker in the future
  • Best part is you wont need to touch the service layer if you do so
  • We have learned about AOP
  • Then we created a application using Spring Boot to call the downstream API and get responses
  • We implemented a circuit breaker to the code
  • We decoupled the circuit breaker with annotations using AOP
  • Then we created a Mockoon mock API to test the code

I Am a Passionate Software Engineer

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store