Circuit Breaker Pattern Implemented with Rx in Mobile or Web Applications
This time, the turn is for the Circuit breaker pattern. That is a well-known pattern in distributed microservices architectures.
Before seeing the implementation detail, let's see the context of this pattern.
Context
"Circuit breakers can be used to stop the flow of messages to an actor when something unusual happens". It is an appropriate description given by Hugh McKee in the report Designing Reactive Systems: The Role of Actors in Distributed Architecture. Martin Fowler also describes this pattern in one of his articles.
In its essence; the Circuit breaker is a finite state machine whose default state is generally closed. According to the external responses we are received, the state's alternate between closed, half-open, and open. External responses could come from a backend API, local or remote repository, a third-party integration, or any other source that provides a service.
The design of an application should consider the resilience factor when interacting with a distributed services architecture; in other words, the application considers the scenarios in which the services are unavailable, present a failure, or require a long time to be active again.
The adoption of techniques such as circuit breaker, exponential backoffs, or graceful degradation contributes to resilience or fault tolerance and coping with cascading failure scenarios.
The back-end field can obtain this pattern's benefits from multiple frameworks and microservices platforms such as Anthos Service Mesh, Istio, Hystrix, Akka, Spring, and Lagom.
This article will use integrating service into the FaaS model and implementing the pattern in the front-end scope. This approach uses functional and reactive programming techniques through Rx extensions (RxJava, RxSwift, or RxJS) to implement the circuit breaker pattern on the mobile or Web client-side.
Implementation
For the implementation, I have created a microservice using Cloud Functions (I could have also used Lambda, Cloud Run, or Fargate).
This service delivers three types of responses: successful, failed, or latency response. The circuit breaker's mission is to react according to the service's response and make decisions about it; decisions such as blocking future invocations of the service for a certain period, delivering an immediate response to the client, or trying the service query again. You can check the code of the function in this repository.
I have written the article using Java code to show the implementation in Android, and its repository is RxCircuitBreaker in Android; however, you can also find the implementation for iOS in Swift in the following repository RxCircuitBreaker in iOS.
The concepts are the same in the different implementations. It remains for the reader to write a library in Kotlin and share it for Android and iOS.
To create the state machine, I relied on the State pattern (GoF) using Enums structures to simplify the implementation.
public enum CircuitBreakerState {
CLOSED {
@Override
public CircuitBreakerState success() {
return CLOSED;
}
@Override
public CircuitBreakerState fail() {
return OPEN;
}
},
OPEN {
@Override
public CircuitBreakerState success() {
return HALF_OPEN;
}
@Override
public CircuitBreakerState fail() {
return OPEN;
}
},
HALF_OPEN {
@Override
public CircuitBreakerState success() {
return CLOSED;
}
@Override
public CircuitBreakerState fail() {
return OPEN;
}
};
public abstract CircuitBreakerState success();
public abstract CircuitBreakerState fail();
}
The main component is the structure called CircuitBreaker. That has been implemented using a Builder Pattern (GoF) and contains the pattern's main logic. It works as a black box by receiving requests and delivering responses.
public class CircuitBreaker {
private static final String TAG = "CircuitBreaker";
private final String name;
private final LocalPersistence localPersistence;
private CircuitBreakerState status;
private final long resetTimeout;
private final long callTimeout;
private final int attempt;
private CircuitBreaker(Builder builder) {
name = builder.name;
localPersistence = builder.localPersistence;
status = builder.status;
resetTimeout = builder.resetTimeout;
callTimeout = builder.callTimeout;
attempt = builder.attempt;
}
public <T> Single<T> callService(Single<T> service) {
switch (this.status) {
case OPEN:
return openActions();
case HALF_OPEN:
return halfOpenActions(service);
case CLOSED:
return closeActions(service);
default:
return service;
}
}
...
...
}
For demonstration purposes, the events (reset - success) are separated in the half-open and closed states. However, the reader may notice that they are events that can be unified.
I rely on the benefits of Rx extensions to react to half-open and closed states through the following logic:
private <T> Single<T> closeActions(Single<T> observable) {
return observable
.timeout(callTimeout, TimeUnit.SECONDS)
.retry(attempt)
.onErrorResumeNext(Single::error)
.doOnError(throwable -> trip())
.doOnSuccess(user -> success());
}
When requesting the execution on the client-side, it would be like this:
private void callServiceWithCircuitBreaker(String typeSimulation) {
showLoading(true);
CircuitBreaker circuitBreaker = circuitBreakerManager
.getCircuitBreaker("circuit-breaker-8", localPersistence);
compositeDisposable.add(
CircuitBreakersManager.callWithCircuitBreaker(this.service(typeSimulation), circuitBreaker)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
logs(response, circuitBreaker.getStatus().toString(), true);
...
},
throwable -> {
logs(throwable.getMessage(), circuitBreaker.getStatus().toString(), false);
...
}));
}
Demos on Android and iOS
First, a satisfactory response is obtained from the service, and this causes the circuit to be in a closed state. Later an error response is received, this induces the open state in the circuit, and it will remain so until the waiting time configured to retry to close it is reached. The circuit is in this state (open) will return immediate responses (without actually calling the service) to requests.
Once the retry time has elapsed and a satisfactory response has been obtained, the circuit will go into the half-open state; thus, if the next response is successful, the circuit will close again; otherwise, it will open again.
Figure 1: Simulation on Android
Figure 2: Simulation on iOS
Conclusions
By including this pattern, we add resilience to our solutions. In those cases where it is considered necessary to protect the application concerning a service, this pattern could be an option.
Only for services? You could even protect a task that calls a local resource, from an SDK, from a local persistence.
There could be more scenarios that would apply for their respective use; you never know.
That is an example of lightweight implementation, and the reader could use variants of it or build more elaborate frameworks.
This use could also be applied in Web applications using the same pattern concept. For now, I owe you the implementation in RxJS, I'll be working on it.
ConversionConversion EmoticonEmoticon