Botica.Flags.Store (Botica v2.2.0)

Copy Markdown View Source

ETS-backed storage for Botica.Flags.Flag structs.

Architecture

  • The ETS table :botica_flags is :public and read_concurrency: true so reads are O(1) and lock-free, even under heavy concurrent load. The :public choice is deliberate: it lets callers read directly without bouncing through the GenServer, which is the whole point of having an ETS backend. Mutations still go through the GenServer (put/1, delete/1) so the table stays consistent.
  • The GenServer is also where flag lifecycle events (defined, enabled, disabled, rollout-changed) are emitted via telemetry.

Usage

In most cases you should not call this module directly — use the Botica.Flags facade instead. Direct access is allowed when you need the cheapest possible read and you're certain the Store is up:

iex> Botica.Flags.Store.get(:my_flag)
{:ok, %Botica.Flags.Flag{...}}

iex> Botica.Flags.Store.all()
[%Botica.Flags.Flag{...}, ...]

Summary

Functions

Direct ETS read of all flags. Returns a list (may be empty), sorted by updated_at descending so most recently touched flags appear first.

Returns a specification to start this module under a supervisor.

Total number of registered flags. Cheap ETS count.

Remove a flag from the registry. 5s timeout — see put/1 for rationale.

Direct ETS read — no GenServer round-trip. Returns {:ok, flag} or :error.

GenServer-mediated write. Serialised to avoid race conditions between concurrent definitions / enable / disable / set calls. 5s timeout is generous for an in-memory ETS write and surfaces a stalled server fast.

Starts the Botica.Flags.Store GenServer. Called automatically by Botica.Application. Use start_link/1 from custom supervision trees when you need to override the default application config.

Diagnostic snapshot: total writes since the GenServer started plus the current ETS size. Useful for mix botica:config and for debugging flag registration.

Returns the underlying ETS table name. Useful in tests for clearing the registry between cases: Store.table() |> :ets.delete_all_objects().

Functions

all()

@spec all() :: [Botica.Flags.Flag.t()]

Direct ETS read of all flags. Returns a list (may be empty), sorted by updated_at descending so most recently touched flags appear first.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

count()

@spec count() :: non_neg_integer()

Total number of registered flags. Cheap ETS count.

delete(name)

@spec delete(atom()) :: :ok

Remove a flag from the registry. 5s timeout — see put/1 for rationale.

get(name)

@spec get(atom()) :: {:ok, Botica.Flags.Flag.t()} | :error

Direct ETS read — no GenServer round-trip. Returns {:ok, flag} or :error.

put(flag)

@spec put(Botica.Flags.Flag.t()) :: :ok

GenServer-mediated write. Serialised to avoid race conditions between concurrent definitions / enable / disable / set calls. 5s timeout is generous for an in-memory ETS write and surfaces a stalled server fast.

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

Starts the Botica.Flags.Store GenServer. Called automatically by Botica.Application. Use start_link/1 from custom supervision trees when you need to override the default application config.

stats()

@spec stats() :: %{writes: non_neg_integer(), count: non_neg_integer()}

Diagnostic snapshot: total writes since the GenServer started plus the current ETS size. Useful for mix botica:config and for debugging flag registration.

table()

@spec table() :: :ets.tab()

Returns the underlying ETS table name. Useful in tests for clearing the registry between cases: Store.table() |> :ets.delete_all_objects().