# Infrastructure - `:fuse` Integration
This document provides a detailed breakdown of how the `foundation` library integrates the `:fuse` circuit breaker library. It explains the design choices, the abstraction layers, and how developers can use the feature effectively.
## Overview
The standard `:fuse` integration pattern involves two main steps:
1. Starting the `:fuse` OTP application.
2. Installing one or more "fuse descriptions" that define the circuit breaker's behavior.
The `foundation` library adheres to this pattern but provides a robust abstraction layer through its `Infrastructure` and `CircuitBreaker` modules. This abstraction offers several advantages:
- **Centralized Control:** Simplifies management of multiple circuit breakers.
- **Dynamic Installation:** Allows fuses to be installed at any point in the application lifecycle, not just on startup.
- **Standardized Errors:** Converts `:fuse`'s responses and errors into the `Foundation.Error` struct for consistent error handling.
- **Observability:** Automatically emits telemetry events for circuit breaker state changes and operations.
## The Integration in Detail
### 1. Starting the `:fuse` Application Supervisor
The `:fuse` documentation recommends starting its application supervisor via a release script or by adding `:fuse` to `extra_applications` in `mix.exs`.
The `foundation` library uses a more on-demand approach to ensure the `:fuse` application is running precisely when needed. This is handled within the unified `Infrastructure` facade.
**File:** `foundation/infrastructure/infrastructure.ex`
Instead of being a dependency in the application supervisor tree, `:fuse` is started by this function:
```elixir
# The public API function to initialize all infrastructure components.
def initialize_all_infra_components() do
# This calls the private implementation...
end
# The private implementation where :fuse is started.
defp initialize_all_infra_components(config) do
# ...
# Ensure Fuse application is started
case Application.ensure_all_started(:fuse) do
{:ok, _apps} ->
:ok
{:error, reason} ->
raise "Failed to start Fuse application: #{inspect(reason)}"
end
# ...
end
```
**Key Points:**
- The public API to start the infrastructure is `Foundation.Infrastructure.initialize_all_infra_components/0`.
- This function calls the internal logic that uses **`Application.ensure_all_started(:fuse)`**.
- This call is idempotent and safe to make multiple times. It guarantees that the `:fuse` application and its top-level supervisor (`fuse_sup`) are running before any fuses are installed.
### 2. Installing a Fuse Description
Directly calling `fuse:install/2` is abstracted away by the `CircuitBreaker` module. This provides a cleaner, more descriptive API and allows `foundation` to add its own logic (like telemetry) around the installation process.
**File:** `foundation/infrastructure/circuit_breaker.ex`
The `start_fuse_instance/2` function is the `foundation` equivalent of `fuse:install/2`.
```elixir
# The public API for installing a fuse.
@spec start_fuse_instance(fuse_name(), fuse_options()) :: :ok | {:error, Error.t()}
def start_fuse_instance(name, options \\ []) do
# ... (logic to build fuse_options tuple)
# The core call to the :fuse library.
case :fuse.install(name, fuse_options) do
:ok ->
emit_telemetry(:fuse_installed, %{name: name, options: fuse_options})
:ok
# ... (error and exception handling)
end
end
```
**Key Points:**
- The function constructs the `fuse_options` tuple exactly as required by `:fuse`, making it a transparent wrapper.
- It provides a simpler keyword-list based configuration (`tolerance: 5, refresh: 30_000`) instead of requiring the user to build the nested tuples manually.
- It handles the `:already_installed` error gracefully, which is a common occurrence in supervised applications.
## End-to-End Workflow
Here is the complete sequence of events for using a circuit breaker through the `foundation` library:
```mermaid
sequenceDiagram
participant App as "Application Startup"
participant Infra as "Foundation.Infrastructure"
participant CB as "CircuitBreaker Module"
participant Fuse as ":fuse OTP App"
App->>+Infra: initialize_all_infra_components()
Note over Infra: Calls the public arity-0 function.
Infra->>Infra: calls private initialize_all_infra_components(%{})
Infra->>+Fuse: Application.ensure_all_started(:fuse)
Note over Fuse: Starts fuse_sup supervisor.
Fuse-->>-Infra: {:ok, _}
Infra-->>-App: {:ok, []}
App->>+CB: start_fuse_instance(:db_fuse, opts)
CB->>+Fuse: :fuse.install(:db_fuse, fuse_opts)
Fuse-->>-CB: :ok
CB-->>-App: :ok
App->>+Infra: execute_protected(:db_call, [circuit_breaker: :db_fuse], fun)
Infra->>+CB: execute(:db_fuse, fun)
CB->>+Fuse: :fuse.ask(:db_fuse, :sync)
Fuse-->>-CB: :ok (or :blown)
Note over CB: Executes `fun` if circuit is closed.
CB-->>-Infra: result
Infra-->>-App: result
```
1. **Application Startup:** The application initializes the foundation infrastructure via `Foundation.Infrastructure.initialize_all_infra_components/0`.
2. **`:fuse` Startup:** The infrastructure module ensures the `:fuse` OTP application is running.
3. **Fuse Installation:** The application installs a specific, named circuit breaker for a service (e.g., a database) by calling `CircuitBreaker.start_fuse_instance/2`.
4. **Protected Execution:** The application wraps external calls using `Infrastructure.execute_protected/3`, which in turn calls `CircuitBreaker.execute/3`. This module handles the interaction with the `:fuse` library, translating its responses into `foundation`'s standard format.
## Comparison Summary
This table summarizes how `foundation` implements the standard `:fuse` patterns.
| `:fuse` Documentation Pattern | `foundation` Library Implementation |
| :------------------------------ | :---------------------------------- |
| Start `:fuse` in release script or `extra_applications`. | The `:fuse` application is started on-demand via **`Application.ensure_all_started(:fuse)`** inside `Infrastructure.initialize_all_infra_components/0`. |
| Call `fuse:install/2` in `application:start/1` callback. | The `fuse:install/2` call is wrapped inside **`CircuitBreaker.start_fuse_instance/2`**. This allows for dynamic installation of fuses. |
| Direct calls to `:fuse` module (e.g., `fuse:ask/2`). | Calls are abstracted through the `CircuitBreaker` wrapper module, which provides telemetry and standardized error handling. |
By using these abstractions, `foundation` provides a more integrated, observable, and developer-friendly way to leverage the power of `:fuse`'s circuit breaker capabilities.