CLOVIS
09/23/2023, 9:43 PMCircuitBreaker
only considers exceptions to be cause for breaking? In my case, exceptions are already caught into an Either
upstream, so I'd rather break on Either.Left
.simon.vergauwen
09/25/2023, 6:09 AMprotectRaise
, or Either
. Naming will be tricky because we already have protectEither
which returns Either
instead of rethrowing the ExecutionRejected
2. It's possible not possible to distinct between CancellationException
from Kotlin(X) or Raise, but we could perhaps do that. If we do that, we could automatically increment the counters. By doing this we could perhaps also take that into account in Schedule#retry
..🤔
What I mean with 2
is:
either {
schedule.retry { foo().bind() } // retry on Left
cb.protect { foo().bind() } // increment failure counter
}
CLOVIS
09/25/2023, 8:37 AMcontext(Raise<E>)
inline fun <E> CircuitBreaker.protect(block: context(Raise<E>) () -> Unit)
that has a try-catch on the specific Arrow CancellationException?simon.vergauwen
09/25/2023, 8:41 AMprotect
, currently NonFatal
are not being counted so CancellationException
is not being countedCLOVIS
09/25/2023, 8:50 AMFlow
).
In my projects using this architecture, all reads go through caches. Therefore, I think it would be a great place to use Arrow resilience: if for some reason the external service is dead, the cache should use CircuitBreaker (and maybe a retry? not sure) to failfast, serving the last cached value to the user until the service goes back up.
Reading the Resilience documentation, this seems appropriate to me, but when trying to implement it, there is one challenge; the operation which may fail is not the one that should be retried.
For example, here's how I would implement a naïve RetryCache:
class RetryCache<I, F, V>(val upstream: Cache<I, F, V>) : Cache<I, F, V> by upstream {
override val get(id: I): Flow<ProgressiveOutcome<F, V>> = upstream[id]
// subscribe to all results…
.onEach {
if (it is ProgressiveOutcome.Failure)
expire(id) // force a retry if the latest result failed, while still subscribing so we see the next result
}
}
I'm not really sure how to adapt that to fit the CircuitBreaker
's expectations.simon.vergauwen
09/25/2023, 9:26 AMupstream
, right?CircuitBreaker
, no?CLOVIS
09/25/2023, 11:15 AMsimon.vergauwen
09/25/2023, 11:26 AMIt's layered, and each layer asks the previous to do the operation.But you cannot wrap the operation, can you? 🤔
CLOVIS
09/25/2023, 11:27 AMupstream[]
. But it's a flow.override fun get(id: I) = breaker.protect {
upstream.get(id)
}
simon.vergauwen
09/25/2023, 11:28 AMSchedule
could be composed with flow.retry(schedule)
so I guess circuit-breaker
could do the same.. 🤔retryWhen
operator again to see what it does