To go in more details of my exact use-case: as part of the Pedestal project I just mentioned, I have a bunch of Cache implementations that layer on top of each other to create complex strategies. Caches are reactive, that is, you can subscribe to a value, and when it gets refreshed, all subscribers are notified (classic
Flow
).
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.