|
| 1 | +# Kogito Serverless Workflow Fault Tolerance |
| 2 | + |
| 3 | +This example shows how to configure fault tolerance alternatives when you work with workflows. |
| 4 | + |
| 5 | +## Circuit Breaker Pattern |
| 6 | + |
| 7 | +Every REST call executed by a workflow can be configured to use the Circuit Breaker Pattern. |
| 8 | + |
| 9 | + |
| 10 | + |
| 11 | +The internals of the pattern configuration and execution are based on the [MicroProfile Fault Tolerance](https://github.com/eclipse/microprofile-fault-tolerance/) specification, |
| 12 | +and the corresponding [Smallrye Fault Tolerance](https://github.com/smallrye/smallrye-fault-tolerance) implementation shipped with Quarkus. |
| 13 | + |
| 14 | +The following example shows how to configure the [call-echo](src/main/resources/call-echo.sw.yml) workflow to use the Circuit Breaker Pattern to execute the `circuitBreakerEcho` operation provided by the [external-service](src/main/resources/specs/external-service.yaml). |
| 15 | + |
| 16 | +### Build time configurations |
| 17 | + |
| 18 | + |
| 19 | +To enable and configure the Circuit Breaker you must follow this procedure: |
| 20 | + |
| 21 | +1. Add the following maven dependency to your project: |
| 22 | +```` |
| 23 | +<dependency> |
| 24 | + <groupId>io.quarkus</groupId> |
| 25 | + <artifactId>quarkus-smallrye-fault-tolerance</artifactId> |
| 26 | +</dependency> |
| 27 | +```` |
| 28 | + |
| 29 | +2. Enable the Circuit Breaker |
| 30 | + |
| 31 | +When a workflow uses REST calls, behind the scene, a set of classes are generated to support these invocations. |
| 32 | +In general, the generation is transparent, and you don't need to pay attention on it. |
| 33 | + |
| 34 | +However, to enable the Circuit Breaker, you must provide some configurations that are related to that generation, and impacts the generated code. |
| 35 | + |
| 36 | +The first step is to find the generated Java class name that is registered as the Microprofile Rest Client to access the external service. |
| 37 | + |
| 38 | +The following picture shows how this class name is calculated at code generation time: |
| 39 | + |
| 40 | + |
| 41 | + |
| 42 | +We recommend that you build the project and inspect the generated sources under the directory `target/generated-sources/open-api-stream/org/kie/kogito/openapi`: |
| 43 | + |
| 44 | + |
| 45 | + |
| 46 | + |
| 47 | +By default, when the Circuit Breaker is not enabled, the generated signature for the Java method corresponding to the `circuitBreakerEcho` operation will look like this: |
| 48 | + |
| 49 | +```` |
| 50 | +@io.quarkiverse.openapi.generator.markers.OperationMarker(name="", openApiSpecId="external_service_yaml", operationId="circuitBreakerEcho", method="POST", path="/external-service/circuit-breaker") |
| 51 | +@jakarta.ws.rs.POST |
| 52 | +@jakarta.ws.rs.Path("/circuit-breaker") |
| 53 | +@jakarta.ws.rs.Consumes({"text/plain"}) |
| 54 | +@jakarta.ws.rs.Produces({"application/json"}) |
| 55 | +@io.quarkiverse.openapi.generator.annotations.GeneratedMethod("circuitBreakerEcho") |
| 56 | +public EchoResponse circuitBreakerEcho( |
| 57 | + String body |
| 58 | +); |
| 59 | +```` |
| 60 | + |
| 61 | +Given the class name `org.kie.kogito.openapi.externalservice.api.ExternalServiceResourceApi`, and the method `circuitBreakerEcho`, |
| 62 | +to enable the Circuit Breaker, you must add the following entry in the `application.properties`: |
| 63 | + |
| 64 | +```` |
| 65 | +org.kie.kogito.openapi.externalservice.api.ExternalServiceResourceApi/circuitBreakerEcho/CircuitBreaker/enabled=true |
| 66 | +```` |
| 67 | + |
| 68 | +In general, for any other operation you must follow this pattern: |
| 69 | + |
| 70 | +`<classname>/<methodname>/CircuitBreaker/enabled=true` |
| 71 | + |
| 72 | +* classname: is the fully qualified name of the generated class. |
| 73 | +* methodname: is the name of the method corresponding to the given operation. |
| 74 | + |
| 75 | + |
| 76 | +> **NOTE:** Every operation must be configured individually, and different operations might be located in different classes. |
| 77 | +
|
| 78 | +After adding the configuration, the next build will generate the following signature: |
| 79 | + |
| 80 | +```` |
| 81 | +@io.quarkiverse.openapi.generator.markers.OperationMarker(name="", openApiSpecId="external_service_yaml", operationId="circuitBreakerEcho", method="POST", path="/external-service/circuit-breaker") |
| 82 | +@jakarta.ws.rs.POST |
| 83 | +@jakarta.ws.rs.Path("/circuit-breaker") |
| 84 | +@jakarta.ws.rs.Consumes({"text/plain"}) |
| 85 | +@jakarta.ws.rs.Produces({"application/json"}) |
| 86 | +@io.quarkiverse.openapi.generator.annotations.GeneratedMethod("circuitBreakerEcho") |
| 87 | +@org.eclipse.microprofile.faulttolerance.CircuitBreaker |
| 88 | +public EchoResponse circuitBreakerEcho( |
| 89 | + String body |
| 90 | +); |
| 91 | +```` |
| 92 | + |
| 93 | +If the `@org.eclipse.microprofile.faulttolerance.CircuitBreaker` annotation is present in the generated method signature, |
| 94 | +it means that the configuration was set correct. |
| 95 | + |
| 96 | +If you don't see the annotation, we recommend that you navigate and look into the generated sources to find the correct class name, or any potential typo in the property name, etc. |
| 97 | + |
| 98 | +3. Configure other behavioural properties: |
| 99 | + |
| 100 | +The build time behavioural properties are used to define configurations like thresholds and delays, for more information [see](https://quarkus.io/version/3.20/guides/smallrye-fault-tolerance#configuration-reference). |
| 101 | + |
| 102 | +To create these properties you must follow this pattern: |
| 103 | + |
| 104 | +`quarkus.fault-tolerance."<classname>/<methodname>".<property>=<value>` |
| 105 | +* classname: is the fully qualified name of the generated class. |
| 106 | +* methodname: is the name of the method corresponding to the given operation. |
| 107 | + |
| 108 | +Below is the configuration used for the example: |
| 109 | + |
| 110 | +```` |
| 111 | +# Build time property to add the Circuit Breaker capability to the corresponding generated class/method, by annotating |
| 112 | +# it with the eclipse microprofile @org.eclipse.microprofile.faulttolerance.CircuitBreaker. |
| 113 | +# NOTE: this property doesn't follow the quarkus.fault-tolerance.xxx prefix, you must use this format instead. |
| 114 | +org.kie.kogito.openapi.externalservice.api.ExternalServiceResourceApi/circuitBreakerEcho/CircuitBreaker/enabled=true |
| 115 | +
|
| 116 | +# Configuration values for the sake of current example. Produces a quick circuit Open, and a long delayed transitioning |
| 117 | +# to the Half Closed state. Don't consider this as suggested configurations for a production system. |
| 118 | +
|
| 119 | +# Use a rolling window of 4 consecutive requests |
| 120 | +quarkus.fault-tolerance."org.kie.kogito.openapi.externalservice.api.ExternalServiceResourceApi/circuitBreakerEcho".circuit-breaker.request-volume-threshold=4 |
| 121 | +# With a ratio of .5, if 2 requests fail, the circuit is Opened. |
| 122 | +quarkus.fault-tolerance."org.kie.kogito.openapi.externalservice.api.ExternalServiceResourceApi/circuitBreakerEcho".circuit-breaker.failure-ratio=0.5 |
| 123 | +
|
| 124 | +# Wait 2 minutes before the Open circuit goes to the Half Open state. |
| 125 | +quarkus.fault-tolerance."org.kie.kogito.openapi.externalservice.api.ExternalServiceResourceApi/circuitBreakerEcho".circuit-breaker.delay=2 |
| 126 | +quarkus.fault-tolerance."org.kie.kogito.openapi.externalservice.api.ExternalServiceResourceApi/circuitBreakerEcho".circuit-breaker.delay-unit=minutes |
| 127 | +```` |
| 128 | + |
| 129 | +### Executing the example |
| 130 | + |
| 131 | +#### Prerequisites |
| 132 | + |
| 133 | +You will need: |
| 134 | +- Java 17+ installed |
| 135 | +- Environment variable JAVA_HOME set accordingly |
| 136 | +- Maven 3.9.6+ installed |
| 137 | + |
| 138 | +##### Executing in Quarkus Dev mode |
| 139 | + |
| 140 | +To see the Circuit Breaker behaviour, we recommend that you follow this procedure: |
| 141 | + |
| 142 | +1. Start the workflow: |
| 143 | + |
| 144 | +```` |
| 145 | +mvn quarkus:dev |
| 146 | +```` |
| 147 | + |
| 148 | +2. Execute the workflow to verify a regular execution (without errors): |
| 149 | + |
| 150 | +```` |
| 151 | +curl -X 'POST' \ |
| 152 | +'http://localhost:8080/call-echo' \ |
| 153 | +-H 'accept: */*' \ |
| 154 | +-H 'Content-Type: application/json' \ |
| 155 | +-d '{ "echo" : "Hello!" }' |
| 156 | +```` |
| 157 | + |
| 158 | +You will see an output like this: |
| 159 | + |
| 160 | +```` |
| 161 | +{ |
| 162 | + "id": "2de57de7-38f7-4a33-bb7e-670bedefbcf1", |
| 163 | + "workflowdata": { |
| 164 | + "echo": "Hello!", |
| 165 | + "echoResult": { |
| 166 | + "id": "cf3405af-c903-4a7f-86cc-d2287d383d36", |
| 167 | + "echo": "Hello!", |
| 168 | + "createdAt": "2025-08-04T12:03:35.705894795+02:00[Europe/Madrid]" |
| 169 | + } |
| 170 | + } |
| 171 | +} |
| 172 | +```` |
| 173 | + |
| 174 | +3. Configure the external service `circuitBreakerEcho` operation to fail using the following command: |
| 175 | + |
| 176 | +```` |
| 177 | +curl -X 'POST' \ |
| 178 | + 'http://localhost:8080/external-service/admin' \ |
| 179 | + -H 'accept: */*' \ |
| 180 | + -H 'Content-Type: application/json' \ |
| 181 | + -d '{ |
| 182 | + "operation": "circuitBreakerEcho", |
| 183 | + "enabled": false |
| 184 | +}' |
| 185 | +```` |
| 186 | +> **NOTE:** The external service was designed to expose an `/admin` endpoint that can be used to program the `circuitBreakerEcho` operation intentionally to fail. |
| 187 | +
|
| 188 | +4. Execute the workflow again with the following command: |
| 189 | + |
| 190 | +```` |
| 191 | +curl -X 'POST' \ |
| 192 | +'http://localhost:8080/call-echo' \ |
| 193 | +-H 'accept: */*' \ |
| 194 | +-H 'Content-Type: application/json' \ |
| 195 | +-d '{ "echo" : "Hello!" }' |
| 196 | +```` |
| 197 | + |
| 198 | +This time you will see an output with the HTTP 500 error code. This means that the workflow has executed the regular |
| 199 | +call to the external service `circuitBreakerEcho` operation, and has got the error. The circuit is in the `Closed` status. |
| 200 | +```` |
| 201 | +{ |
| 202 | + "errorCode": "500", |
| 203 | + "failedNodeId": "6", |
| 204 | + "id": "5e731491-8850-44de-a3c5-a68fd2817af6", |
| 205 | + "message": "HTTP 500 Internal Server Error" |
| 206 | +} |
| 207 | +```` |
| 208 | + |
| 209 | +5. Repeat 3 or more executions of the workflow execution command. And, this time, you will start to see the following result: |
| 210 | + |
| 211 | +```` |
| 212 | +{ |
| 213 | + "failedNodeId": "6", |
| 214 | + "id": "f3f12ca3-2a1e-4267-9d82-a0ee3d9acd80", |
| 215 | + "message": "org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException - org.kie.kogito.openapi.externalservice.api.ExternalServiceResourceApi#circuitBreakerEcho circuit breaker is open" |
| 216 | +} |
| 217 | +```` |
| 218 | + |
| 219 | +This means that circuit has transitioned to the `Open` status, and thus, no call to external service is produced. |
| 220 | +Instead, the fault tolerance layer is producing the error. |
| 221 | + |
| 222 | +6. Configure the external service `circuitBreakerEcho` operation to go back to normal execution with the following command: |
| 223 | + |
| 224 | +```` |
| 225 | +curl -X 'POST' \ |
| 226 | + 'http://localhost:8080/external-service/admin' \ |
| 227 | + -H 'accept: */*' \ |
| 228 | + -H 'Content-Type: application/json' \ |
| 229 | + -d '{ |
| 230 | + "operation": "circuitBreakerEcho", |
| 231 | + "enabled": true |
| 232 | +}' |
| 233 | +```` |
| 234 | + |
| 235 | +This time, the external service will not fail if executed, however, if you repeat the workflow execution during the next 2 minutes, |
| 236 | +you will continue getting the org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException, since |
| 237 | +the circuit will remain in the `Open` status because of the following configuration: |
| 238 | + |
| 239 | +```` |
| 240 | +# Wait 2 minutes before an Open circuit goes to the Half Open state. |
| 241 | +quarkus.fault-tolerance."org.kie.kogito.openapi.externalservice.api.ExternalServiceResourceApi/circuitBreakerEcho".circuit-breaker.delay=2 |
| 242 | +quarkus.fault-tolerance."org.kie.kogito.openapi.externalservice.api.ExternalServiceResourceApi/circuitBreakerEcho".circuit-breaker.delay-unit=minutes |
| 243 | +```` |
| 244 | + |
| 245 | +After this time, the circuit will transition to the `Half Closed`, and `Closed` status as expected. And the workflow execution |
| 246 | +will be successful again. |
| 247 | + |
0 commit comments