Description
Failsafe currently allows most event handlers to be declared globally. Ex:
RetryPolicy retryPolicy = new RetryPolicy();
CircuitBreaker circuitBreaker = new CircuitBreaker();
Failsafe
.with(retryPolicy)
.with(circuitBreaker)
.onRetry(f -> refreshContext())
.get(this::connect);
Rather than per policy:
RetryPolicy retryPolicy = new RetryPolicy()
.onRetry(f -> refreshContext());
CircuitBreaker circuitBreaker = new CircuitBreaker();
Failsafe
.with(retryPolicy)
.with(circuitBreaker)
.get(this::connect);
One obvious problem with the current approach is that some events don't apply to all policies (ex: onRetry does nothing for a CircuitBreaker
). So why the current approach? It was mostly based on the idea that policy configuration (RetryPolicy
) should be separate from execution logic (Failsafe...
) which presumably includes event handling. A common RetryPolicy
might be reused for lots of different types of executions, but the thinking was that if you start to embed event handling logic into it then maybe it wouldn't be.
Option 1
The first option is to leave things as they are based on the idea that separating event handling from policy config is a good thing and that it will give us some policy reuse.
Option 2
Another option is to move the policy specific event handling to the policies. So the retry related event handling would be moved to RetryPolicy. This would leave just the completion related events at the global level. Ex:
RetryPolicy retryPolicy = new RetryPolicy()
.onRetry(f -> refreshContext());
CircuitBreaker circuitBreaker = new CircuitBreaker();
Failsafe
.with(retryPolicy)
.with(circuitBreaker)
.onSuccess(cxn -> log.info("Connected to {}", cxn))
.get(this::connect);
Option 3
Another option is to move all event handling to the policy level, including (some) completion events:
RetryPolicy retryPolicy = new RetryPolicy()
.onRetry(f -> refreshContext())
.onSuccess(cxn -> log.info("Connected to {}", cxn))
CircuitBreaker circuitBreaker = new CircuitBreaker()
.onFailure(f ->log.warn("The CircuitBreaker logged a failure"))
Failsafe
.with(retryPolicy)
.with(circuitBreaker)
.get(this::connect);
In practice, each policy makes its own decision about whether an execution was a success or failure, and different Exceptions/results may be failures for different policies (ex: a CircuitBreaker
might handle TimeoutException
s whereas a RetryPolicy
might handle ConnectExceptions
). In that sense, having a global onSuccess
event handler might not make sense, since it can only be called if all of the policies were successful and loses some detail.
That said, the onComplete
event handler may still need to be global, since it provides a completion callback for async executions, similar to CompletableFuture.whenComplete
, and while success/failure is a policy-specific decision, completion is a global event.
Thoughts?