Rexsilient (Rexsilient v1.0.0)

Copy Markdown View Source

Rexsilient is a resilience toolkit for Elixir. Its patterns protect the calls an application makes to its dependencies:

Declaring patterns

Patterns are declared in any module that uses Rexsilient. A module may declare as many as needed, of any kind:

defmodule MyApp.Resilience do
  use Rexsilient

  alias Rexsilient.CircuitBreaker.{Recover, Strategy}

  circuit_breaker :payments do
    strategy Strategy.Count, threshold: 5
    recover Recover.BasicTimeout,
      reset_timeout: :timer.seconds(30),
      probing_timeout: :timer.seconds(10)
  end

  retry :payments do
    attempts 4
    strategy Rexsilient.Retry.Strategy.Exponential, base_delay: 100, max_delay: 5_000
  end

  fallback :prices do
    function :cached_price
  end

  def cached_price(sku), do: Cache.last_price(sku)
end

Declarations are checked at compile time: a missing required setting, a setting used outside the block it belongs to, or one set twice fails compilation with an error that points at the offending line. The options of each setting are checked when the declaration is registered.

The module is then passed to Rexsilient in your supervision tree, which registers its declarations when it starts:

children = [
  {Rexsilient, modules: [MyApp.Resilience]},
  MyAppWeb.Endpoint
]

List it before the processes that use the declarations, so they exist when they are first used. See child_spec/1 for the options.

Settings

Modules and options are evaluated at compile time, so aliases and expressions such as :timer.seconds(30) are allowed.

Using patterns

The easiest way is to annotate the functions that call the dependency, with Rexsilient.Decorator:

defmodule MyApp.Payments do
  use Rexsilient.Decorator

  @retry :payments
  @circuit_breaker :payments
  def charge(order) do
    PaymentsClient.charge(order)
  end
end

Without a decorator, calls go through Rexsilient.CircuitBreaker.run/3, Rexsilient.Retry.run/2 and Rexsilient.Fallback.run/3.

Classifying failures

use Rexsilient defines classify/1 and classify/2, which decide which outcomes count as failures for every pattern declared in the module: by default, :error, {:error, _} and any raise, throw or exit. Override them, or name other functions with classify_with/1; see Rexsilient.Classify.

Summary

Functions

Sets the total number of calls a retry makes, the first one included: a positive integer, or :infinity to leave the limit to the strategy.

Returns a child specification that registers the declarations of the given modules and starts the processes they need.

References the circuit breaker name in a policy. Can only be used inside policy/2.

Declares a circuit breaker called name.

Sets the name of the functions, in the declaring module, that decide which outcomes are failures, instead of classify/1 and classify/2. A missing func_name/1 or func_name/2 falls back to the classify one of the same arity; see Rexsilient.Classify.

References the fallback name in a policy. Can only be used inside policy/2.

Declares a fallback called name.

Sets the function a fallback calls when the call fails: a name, for a function in the declaring module, or {module, name} for one in another module. It is called with the same arguments as the failed call.

Declares a policy called name: a fallback, a retry and a circuit breaker, each referenced by the name it is declared with, used together with @policy name from Rexsilient.Decorator.

Sets how long the breaker stays open before it lets a probe call through, and how long a probe may run before the breaker lets another one through.

References the retry name in a policy. Can only be used inside policy/2.

Declares a retry called name.

Sets the strategy of a breaker or a retry.

Functions

attempts(number)

(macro)

Sets the total number of calls a retry makes, the first one included: a positive integer, or :infinity to leave the limit to the strategy.

Can only be used inside retry/2.

Examples

attempts 4

child_spec(opts)

@spec child_spec(opts :: [modules: [module()], notifiers: [module()]]) ::
  Supervisor.child_spec()

Returns a child specification that registers the declarations of the given modules and starts the processes they need.

Options

  • :modules - the modules that declare with Rexsilient. Required. Raises ArgumentError if two declarations of the same kind share a name.

  • :notifiers - the modules told what the patterns are doing; see Rexsilient.Notifier. Defaults to [Rexsilient.Notifier.TelemetryNotifier]; [] sends nothing.

Examples

children = [
  {Rexsilient, modules: [MyApp.Resilience], notifiers: [MyApp.Notifier]}
]

circuit_breaker(name)

(macro)

References the circuit breaker name in a policy. Can only be used inside policy/2.

circuit_breaker(name, list)

(macro)

Declares a circuit breaker called name.

name must be an atom, unique among all breakers registered on the node. The block sets the breaker's settings; see Settings.

Examples

circuit_breaker :payments do
  strategy Rexsilient.CircuitBreaker.Strategy.Count, threshold: 5
  recover Rexsilient.CircuitBreaker.Recover.BasicTimeout,
    reset_timeout: :timer.seconds(30),
    probing_timeout: :timer.seconds(10)
end

classify_with(func_name)

(macro)

Sets the name of the functions, in the declaring module, that decide which outcomes are failures, instead of classify/1 and classify/2. A missing func_name/1 or func_name/2 falls back to the classify one of the same arity; see Rexsilient.Classify.

Can only be used inside circuit_breaker/2, fallback/2 or retry/2.

Examples

classify_with :classify_payments

fallback(name)

(macro)

References the fallback name in a policy. Can only be used inside policy/2.

fallback(name, list)

(macro)

Declares a fallback called name.

name must be an atom, unique among all fallbacks registered on the node. The block sets the fallback's settings; see Rexsilient.Fallback.

Examples

fallback :prices do
  function :cached_price
end

function(func)

(macro)

Sets the function a fallback calls when the call fails: a name, for a function in the declaring module, or {module, name} for one in another module. It is called with the same arguments as the failed call.

Can only be used inside fallback/2.

Examples

function :cached_price

function {MyApp.Cache, :last_price}

policy(name, list)

(macro)

Declares a policy called name: a fallback, a retry and a circuit breaker, each referenced by the name it is declared with, used together with @policy name from Rexsilient.Decorator.

The block lists the declarations with fallback/1, retry/1 and circuit_breaker/1. Each one is optional, but at least one is required. They always nest in the same order, whatever the order they are written in:

fallback( retry( circuit_breaker( call ) ) )

The referenced declarations may live in any module registered with Rexsilient. A name that is not declared raises ArgumentError when the policy is registered.

Examples

policy :prices do
  fallback :prices
  retry :prices
  circuit_breaker :prices
end

recover(module, opts)

(macro)

Sets how long the breaker stays open before it lets a probe call through, and how long a probe may run before the breaker lets another one through.

opts are passed to module and only read by it. Can only be used inside circuit_breaker/2.

Examples

recover Rexsilient.CircuitBreaker.Recover.BasicTimeout,
  reset_timeout: :timer.seconds(30),
  probing_timeout: :timer.seconds(10)

retry(name)

(macro)

References the retry name in a policy. Can only be used inside policy/2.

retry(name, list)

(macro)

Declares a retry called name.

name must be an atom, unique among all retries registered on the node. The block sets the retry's settings; see Rexsilient.Retry.

Examples

retry :payments do
  attempts 4
  strategy Rexsilient.Retry.Strategy.Exponential, base_delay: 100, max_delay: 5_000
end

strategy(module, opts)

(macro)

Sets the strategy of a breaker or a retry.

Inside circuit_breaker/2, it decides when the breaker opens; see Rexsilient.CircuitBreaker.Strategy. Inside retry/2, it decides how long to wait before the next attempt, or whether to stop; see Rexsilient.Retry.Strategy.

opts are passed to module and only read by it. Can only be used inside circuit_breaker/2 or retry/2.

Examples

strategy Rexsilient.CircuitBreaker.Strategy.Count, threshold: 5

strategy Rexsilient.Retry.Strategy.Exponential, base_delay: 100, max_delay: 5_000