# `Cache.MultiLayer`
[🔗](https://github.com/mikaak/elixir_cache/blob/main/lib/cache/multi_layer.ex#L1)

Multi-layer caching strategy that cascades through multiple cache layers.

Keys are read from fastest to slowest, with automatic backfill on cache hits
from slower layers. Writes go slowest-first to avoid polluting fast layers
with data that failed to persist in slow ones.

## Usage

Pass a list of layers as the strategy config. Each element can be:

- A module that implements `Cache` (already running, not supervised by this adapter)
- An adapter module (e.g. `Cache.ETS`) — will be auto-started and supervised
- A tuple `{AdapterModule, opts}` — adapter with inline opts

```elixir
defmodule MyApp.LayeredCache do
  use Cache,
    adapter: {Cache.MultiLayer, [Cache.ETS, MyApp.RedisCache]},
    name: :layered_cache,
    opts: []
end
```

## `__MODULE__` in Layers

You may include `__MODULE__` in the layer list to position the current
module's own underlying cache within the chain. If `__MODULE__` is omitted,
no local cache is created for the defining module—it acts as a pure facade.

```elixir
defmodule MyApp.LayeredCache do
  use Cache,
    adapter: {Cache.MultiLayer, [Cache.ETS, __MODULE__, MyApp.RedisCache]},
    name: :layered_cache,
    opts: [uri: "redis://localhost"]
end
```

## Read Behaviour

Layers are iterated fastest → slowest (list order). On a hit from layer N,
the value is backfilled into layers 1..N-1.

## Write Behaviour

Layers are written slowest → fastest (reverse list order). If a slow write
fails, the write stops and an error is returned — preventing polluting faster
layers with potentially-unsaved data.

## Fetch Callback (Optional)

If all layers miss, an optional fetch callback can supply the value. The
fetched value is then backfilled into all layers.

Define it as a module callback or pass it via opts:

```elixir
defmodule MyApp.LayeredCache do
  use Cache,
    adapter: {Cache.MultiLayer, [Cache.ETS, MyApp.RedisCache]},
    name: :layered_cache,
    opts: [on_fetch: &__MODULE__.fetch/1]

  def fetch(key) do
    {:ok, "value_for_#{key}"}
  end
end
```

## Cross-Node Coherence (Optional)

Node-local fast layers (e.g. `Cache.ETS`) go stale on every node except the
writer. Setting `broadcast_mode` keeps them coherent: after a successful
`put`/`delete`, every other node running this cache (tracked via `:pg` —
see `Cache.MultiLayer.Coordinator`) is notified and applies the change to
its own `broadcast_layers`.

```elixir
defmodule MyApp.LayeredCache do
  use Cache,
    adapter: {Cache.MultiLayer, [MyApp.EtsLayer, MyApp.RedisCache]},
    name: :layered_cache,
    opts: [
      backfill_ttl: :timer.seconds(30),
      broadcast_mode: :invalidate,
      broadcast_layers: [MyApp.EtsLayer]
    ]
end
```

`:invalidate` sends only the key — remote nodes drop their local entry and
lazily re-read through the shared layer. `:replicate` ships the value so
remote local layers are updated immediately; use it only for small values.
Delivery is best-effort: always keep `backfill_ttl` (and layer TTLs) as the
correctness floor for members that miss a message.

## Options

* `:on_fetch` - Optional fetch callback invoked on total cache miss. Receives the key, returns `{:ok, value}` or `{:error, reason}`.

* `:backfill_ttl` - TTL in milliseconds to use when backfilling layers on a hit from a slower layer. Defaults to nil (no expiry).

* `:broadcast_mode` - Cross-node coherence for writes: `:invalidate` deletes the key from other nodes' `broadcast_layers`; `:replicate` pushes the written value to them. Best-effort delivery.

* `:broadcast_layers` (list of `t:atom/0`) - Node-local layer modules the broadcast applies to on other nodes. Required with `broadcast_mode`; must not include the shared (slowest) layer.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
