Raxol.Agent.Cache behaviour (Raxol Agent v2.6.0)

Copy Markdown View Source

Behaviour for Raxol.Agent.Policy.Cache storage.

Implementations decide the storage medium. Two adapters ship with raxol_agent:

TTL contract

put/4 takes a TTL in milliseconds. Implementations record the absolute expiry (DateTime.utc_now + ttl_ms) and serve get/2 as {:ok, value} while not expired, :miss once expired. Expiry is lazy by default: a get/2 on a stale entry returns :miss and the entry is removed. Adapters that want eager sweep (background GenServer that scans and deletes expired entries) are free to do so without breaking the lazy-expiry contract.

TTL of 0 means "no expiry": the entry lives until explicitly deleted via delete/2 or flush/1. Negative TTL is invalid and raises ArgumentError at the dispatcher (Cache.put/4).

Keys and values

Keys are arbitrary Erlang terms; adapters hash or serialize them as needed. Values are also arbitrary terms; the Postgrex adapter uses :erlang.term_to_binary/1 so values round-trip faithfully across BEAM nodes.

Configuration

When wired into a Policy.Cache, the configuration carries alongside the module:

Policy.Cache.ets(ttl: :timer.minutes(5))
# ↓ resolves to ...
{Raxol.Agent.Cache.Ets, %{table: :raxol_agent_cache}}

Policy.Cache.postgrex(ttl: :timer.minutes(5), conn: MyApp.Postgrex)
# ↓ resolves to ...
{Raxol.Agent.Cache.Postgrex,
 %{conn: MyApp.Postgrex, table: "raxol_agent_cache"}}

Adapters destructure {module, config} and forward config to every callback. A bare module (no tuple) gets %{}.

Summary

Types

Per-call configuration map passed to every callback.

Cache key; arbitrary term.

Time-to-live in milliseconds. 0 means no expiry.

Cache value; arbitrary term.

Callbacks

Remove the entry for key. Idempotent: returns :ok even if the key was not present.

Remove all entries managed by this config. Idempotent.

Return {:ok, value} when key is present and not expired, :miss otherwise. Implementations may garbage-collect expired entries opportunistically on get/2.

Store value under key with a TTL in milliseconds.

Functions

Dispatch delete to the adapter. No-op when adapter is nil.

Dispatch flush to the adapter. No-op when adapter is nil.

Dispatch get to the adapter. Returns {:ok, value} or :miss.

Normalize the cache opt into {module, config} form.

Dispatch put to the adapter.

Types

config()

@type config() :: map()

Per-call configuration map passed to every callback.

key()

@type key() :: term()

Cache key; arbitrary term.

ttl_ms()

@type ttl_ms() :: non_neg_integer()

Time-to-live in milliseconds. 0 means no expiry.

value()

@type value() :: term()

Cache value; arbitrary term.

Callbacks

delete(config, key)

@callback delete(config(), key()) :: :ok

Remove the entry for key. Idempotent: returns :ok even if the key was not present.

flush(config)

@callback flush(config()) :: :ok

Remove all entries managed by this config. Idempotent.

get(config, key)

@callback get(config(), key()) :: {:ok, value()} | :miss

Return {:ok, value} when key is present and not expired, :miss otherwise. Implementations may garbage-collect expired entries opportunistically on get/2.

put(config, key, value, ttl_ms)

@callback put(config(), key(), value(), ttl_ms()) :: :ok

Store value under key with a TTL in milliseconds.

A ttl_ms of 0 means "no expiry" (entry persists until delete/2 or flush/1). Implementations record the absolute expiry timestamp at write time so the get-side check is a single comparison against DateTime.utc_now/0.

Functions

delete(arg1, key)

@spec delete({module(), config()} | nil, key()) :: :ok

Dispatch delete to the adapter. No-op when adapter is nil.

flush(arg1)

@spec flush({module(), config()} | nil) :: :ok

Dispatch flush to the adapter. No-op when adapter is nil.

get(arg1, key)

@spec get({module(), config()} | nil, key()) :: {:ok, value()} | :miss

Dispatch get to the adapter. Returns {:ok, value} or :miss.

Returns :miss when the adapter tuple is nil (no cache configured) so callers can treat "no cache" the same as "cache miss" without branching.

normalize(module)

@spec normalize(module() | {module(), config()} | nil) :: {module(), config()} | nil

Normalize the cache opt into {module, config} form.

Accepts a bare module, a {module, config} tuple, or nil. Returns nil for nil input.

put(arg1, key, value, ttl_ms)

@spec put({module(), config()} | nil, key(), value(), ttl_ms()) :: :ok

Dispatch put to the adapter.

Raises ArgumentError on negative ttl_ms. Returns :ok when the adapter tuple is nil (no-op, no cache configured).