This example demonstrates an operations REST API that can toggle a running application’s circuit-breaker between FORCED_OPEN, DISABLED, and CLOSED states in order to deny all traffic, allow all traffic, or resume normal automatic operation, respectively.

Prerequisites

The reader should be familiar with the general circuit-breaker usage example.

Example program structure

This example is structurally similar the general circuit-breaker usage example.

This example adds a REST API controller that allows an operator to manipulate the state of the circuit-breaker protecting the GET / call (see CircuitBreakerController.java). The API allows the operator to manually change the state of a circuit breaker to one of the FORCED_OPEN, DISABLED, or CLOSED states. While FORCED_OPEN, a breaker will deny all traffic. While DISABLED, the breaker will instead allow all traffic. In either case the breaker will not change state on its own and must be forced CLOSED to resume normal operation.

To change the state of a breaker, make a post request like the following:

curl localhost:8080/circuitbreakers/mycircuitbreaker -H 'Content-Type: application/json' -d '{"updateState": "CLOSED"}'
Note
The resilience4j team is, as of 2020-July-11, working on Spring Boot Actuator support to expose circuit breaker state transitions to operators via a REST API. This has not yet been released. Our implementation should resemble theirs.

Example in action

From the root of this repository, invoke the following:

./gradlew -p circuit-breaker/example/operations-api bootRun

As in the general circuit-breaker usage example, when the application starts, the Requester’s circuit breaker is initially in the `CLOSED state and will therefore allow GET localhost:8080/ requests through. The controller responds after a 200 ms delay by default, which the Client considers to be a healthy response time. As such, the following healthy logs are initially printed:

...
21:19:50.218 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 222 ms
21:19:51.214 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 217 ms
21:19:52.213 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 217 ms

If we wish to stop all traffic through the breaker nonetheless, however, we can issue a POST request to the operations endpoint and put the breaker in the FORCED_OPEN state:

curl localhost:8080/circuitbreakers/mycircuitbreaker -H 'Content-Type: application/json' -d '{"updateState": "FORCED_OPEN"}'

At this point, every request to the circuit breaker will fail with a CallNotPermitted error, and so the Requester will log errors until we change the breaker state again:

...
21:37:45.631 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 221 ms
21:37:46.630 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 221 ms
21:37:47.626 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 216 ms
21:37:48.453 INFO  [reactor-http-epoll-4] com.github.tomboyo.example.Requester: Breaker mycircuitbreaker transition: CLOSED -> FORCED_OPEN
21:37:48.453 INFO  [reactor-http-epoll-4] com.github.tomboyo.example.CircuitBreakerController: Forced mycircuitbreaker breaker state to FORCED_OPEN
21:37:48.618 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 209 ms
21:37:49.413 ERROR [scheduling-1] com.github.tomboyo.example.Requester: Call failed: CircuitBreaker 'mycircuitbreaker' is FORCED_OPEN and does not permit further calls
21:37:50.410 ERROR [scheduling-1] com.github.tomboyo.example.Requester: Call failed: CircuitBreaker 'mycircuitbreaker' is FORCED_OPEN and does not permit further calls
21:37:51.410 ERROR [scheduling-1] com.github.tomboyo.example.Requester: Call failed: CircuitBreaker 'mycircuitbreaker' is FORCED_OPEN and does not permit further calls

We could next force the breaker into the DISABLED state:

curl localhost:8080/circuitbreakers/mycircuitbreaker -H 'Content-Type: application/json' -d '{"updateState": "DISABLED"}'

This will allow requests through no matter what, and so the requester should go back to logging Call OK lines.

...
21:39:52.410 ERROR [scheduling-1] com.github.tomboyo.example.Requester: Call failed: CircuitBreaker 'mycircuitbreaker' is FORCED_OPEN and does not permit further calls
21:39:52.595 INFO  [reactor-http-epoll-5] com.github.tomboyo.example.Requester: Breaker mycircuitbreaker transition: FORCED_OPEN -> DISABLED
21:39:52.595 INFO  [reactor-http-epoll-5] com.github.tomboyo.example.CircuitBreakerController: Forced mycircuitbreaker breaker state to DISABLED
21:39:53.620 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 211 ms
21:39:54.624 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 215 ms

If we then instruct the REST API to take a long time to respond (using the configuration API introduced in the general example),

curl localhost:8080/ -H 'Content-Type: application/json' -d '{"delay_millis": 700}'

the breaker will not trip and will allow requests through regardless:

...
21:41:39.615 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 206 ms
21:41:40.618 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 208 ms
21:41:41.618 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 208 ms
21:41:43.117 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 708 ms
21:41:44.117 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 708 ms
21:41:45.118 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 708 ms

When we are done manually overriding the breaker behavior, we can set it back to CLOSED:

curl localhost:8080/circuitbreakers/mycircuitbreaker -H 'Content-Type: application/json' -d '{"updateState": "CLOSED"}'

The breaker will resume normal operation at this point.

...
21:42:40.121 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 712 ms
21:42:41.116 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 707 ms
21:42:42.117 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 707 ms
21:42:42.223 INFO  [reactor-http-epoll-7] com.github.tomboyo.example.Requester: Breaker mycircuitbreaker transition: DISABLED -> CLOSED
21:42:42.223 INFO  [reactor-http-epoll-7] com.github.tomboyo.example.CircuitBreakerController: Forced mycircuitbreaker breaker state to CLOSED
21:42:43.120 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Breaker mycircuitbreaker transition: CLOSED -> OPEN
21:42:43.120 INFO  [scheduling-1] com.github.tomboyo.example.Requester: Call OK in 711 ms
21:42:43.410 ERROR [scheduling-1] com.github.tomboyo.example.Requester: Call failed: CircuitBreaker 'mycircuitbreaker' is OPEN and does not permit further calls