Runs a function through a circuit breaker, a retry, a fallback or a policy by annotating it.
Instead of wrapping every call in Rexsilient.CircuitBreaker.run/3, a
function can be marked with @circuit_breaker so that all its calls go
through the circuit breaker:
defmodule MyApp.Payments do
use Rexsilient.Decorator
@circuit_breaker :payments
def charge(order) do
PaymentsClient.charge(order)
end
endMyApp.Payments.charge(order) now behaves like:
Rexsilient.CircuitBreaker.run(:payments, fn -> PaymentsClient.charge(order) end)The circuit breaker must be declared with Rexsilient and registered as
usual; see Rexsilient.CircuitBreaker for its results and how failures are
counted.
@retry and @fallback work the same way, with Rexsilient.Retry.run/2
and Rexsilient.Fallback.run/3. The fallback function is called with the
decorated function's arguments.
Options
To pass options to Rexsilient.CircuitBreaker.run/3, give a keyword list
with the circuit breaker's name under :name:
@circuit_breaker name: :prices, on_open: {:error, :unavailable}
def fetch_price(sku) do
PricesClient.fetch(sku)
end@retry, @fallback and @policy take only a name.
Stacking
@fallback, @retry and @circuit_breaker can be used on the same
function. They always nest in the same order, whatever the order they are
written in:
@fallback :prices
@retry :prices
@circuit_breaker :prices
def fetch_price(sku) do
PricesClient.fetch(sku)
endbehaves like:
Rexsilient.Fallback.run(:prices, fn sku ->
Rexsilient.Retry.run(:prices, fn ->
Rexsilient.CircuitBreaker.run(:prices, fn -> PricesClient.fetch(sku) end)
end)
end, sku)Every attempt goes through the breaker and is counted. The retry stops as soon as the breaker is open, and the fallback replaces whatever outcome is left: the last attempt's failure or the breaker's rejection.
When the same stack is used on several functions, declare it once as a
policy with Rexsilient.policy/2 and use @policy:
# in the module that uses Rexsilient
policy :prices do
fallback :prices
retry :prices
circuit_breaker :prices
end
# in the decorated module
@policy :prices
def fetch_price(sku) do
PricesClient.fetch(sku)
endA policy nests in the same order. @policy cannot be combined with the
other decorators on the same function.
For another order, or the same pattern twice, decorate one function per layer, each calling the next:
# The breaker counts a whole retry sequence as one call.
@circuit_breaker :prices
def fetch_price(sku), do: fetch_price_with_retry(sku)
@doc false
@retry :prices
def fetch_price_with_retry(sku), do: PricesClient.fetch(sku)The inner function must be public, so calling it directly skips the outer layer.
Rules
The attribute goes right before the function's first clause and applies to every clause of that name and arity.
Only public functions (
def) can be decorated. Using it ondefpor a macro fails compilation, and so does an attribute that is not followed by a function.Recursive calls from inside the function go through the decorators too.