An Elixir library for safely calling external services and APIs with customizable retry logic, the circuit breaker pattern, and optional rate limiting. Calls can be synchronous, asynchronous background tasks, or fanned out in parallel for MapReduce-style processing — all under the same protection.
Calling an external service is risky: the network hiccups, the service is briefly
overloaded, or it goes down entirely. ExternalService wraps those calls with
three complementary safety mechanisms:
- Retries smooth over transient failures by trying again with configurable backoff and jitter.
- A circuit breaker protects against a persistently failing service: once failures cross a threshold the breaker opens and calls fail fast instead of piling up against a service that is already down.
- A rate limiter keeps you under the call quota the service imposes.
You wrap your call in a function, hand it to ExternalService, and all of the
above is applied on every call.
Add external_service to your dependencies in mix.exs:
def deps do
[{:external_service, "~> 2.0"}]
endDefine a module for the service, configuring it declaratively, and start it under your supervisor:
defmodule MyApp.Stripe do
use ExternalService,
circuit_breaker: [tolerate: 5, within: :timer.seconds(1), reset: :timer.seconds(5)],
rate_limit: [limit: 100, per: :timer.seconds(1)],
retry: [max_attempts: 5, backoff: :exponential, jitter: true]
def charge(params) do
call fn ->
case Stripe.charge(params) do
{:ok, result} -> {:ok, result}
{:error, %{status: status}} when status in 500..599 -> :retry
other -> other
end
end
end
end# In your supervision tree:
children = [MyApp.Stripe]
Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)Now call it from anywhere; retries, circuit breaking, and rate limiting are applied automatically:
case MyApp.Stripe.charge(%{amount: 1000, currency: "usd", source: token}) do
{:ok, result} ->
handle_success(result)
{:error, %ExternalService.RetriesExhausted{}} ->
{:error, :payment_unavailable}
{:error, %ExternalService.CircuitBreakerOpen{}} ->
{:error, :payment_unavailable}
{:error, reason} ->
{:error, reason}
endInside the function you pass to call/1, return :retry or {:retry, reason}
to ask for another attempt; any other value is treated as success and returned
as-is.
Full documentation is on HexDocs. Start with these guides:
- Getting Started — your first reliable call.
- The Module Front Door — everything
use ExternalServicegenerates, plus supervision and overrides. - Circuit Breakers — how the breaker trips, resets, and how to introspect it.
- Retries — backoff, jitter, attempt/time budgets, and retrying on exceptions.
- Rate Limiting — staying under a quota.
- Error Handling —
callvscall!and the structured error types. - Telemetry — observing calls, retries, and trips.
- Migrating to 2.0 — upgrading from 1.x.
Version 2.0 is a breaking modernization of the library. See the migration guide and the CHANGELOG for the full list of changes.
Released under the Apache 2.0 license. Originally sponsored by Ropig.