ExternalService

Hex.pmDocumentation

An Elixir library for safely calling external services and APIs with customizable retry logic, the circuit breaker pattern, optional rate limiting, and an optional concurrency limit. Calls can be synchronous, asynchronous background tasks, or fanned out in parallel for MapReduce-style processing — all under the same protection.

Why?

Calling an external service is risky: the network hiccups, the service is briefly overloaded, or it goes down entirely. ExternalService wraps those calls with four complementary safety mechanisms:

You wrap your call in a function, hand it to ExternalService, and all of the above is applied on every call.

Installation

Add external_service to your dependencies in mix.exs:

def deps do
[{:external_service, "~> 3.0"}]
end

Quick start

Define 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), wait: :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, rate limiting, and (if configured) the concurrency limit 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}
end

Inside 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.

Documentation

Full documentation is on HexDocs. Start with these guides:

Upgrading

Version 3.0 changes four defaults and behaviors, and makes one dependency optional, and renames nothing: your code compiles unchanged and behaves differently. Start with the 3.0 migration guide, whose first section tells you in about a minute which changes affect you.

Version 2.0 was a breaking modernization of the library; see the 2.0 migration guide. The CHANGELOG has the full list of changes for both.

License

Released under the Apache 2.0 license. Originally sponsored by Ropig.