Event store discovery
Chronicle Elixir provides APIs to query information about available event stores and their namespaces from the Chronicle kernel. This is useful for administrative tasks, multi-tenant applications, and debugging. See Event Store and Namespaces for what these concepts mean and how they relate to multi-tenancy — this page covers the Elixir-specific discovery APIs for listing and verifying them at runtime.
Getting Event Stores
Section titled “Getting Event Stores”Query all event store names from the Chronicle kernel:
{:ok, stores} = Chronicle.get_event_stores()# => {:ok, ["my-app", "crm", "analytics"]}This is useful in multi-tenant or multi-application scenarios where you need to discover which event stores are available:
case Chronicle.get_event_stores() do {:ok, stores} -> IO.puts("Available event stores: #{inspect(stores)}") {:error, reason} -> IO.puts("Failed to get event stores: #{inspect(reason)}")endGetting Namespaces
Section titled “Getting Namespaces”Query all namespaces within an event store:
# Use the configured client's event store{:ok, namespaces} = Chronicle.get_namespaces()# => {:ok, ["tenant-1", "tenant-2", "tenant-3"]}
# Specify a different event store{:ok, namespaces} = Chronicle.get_namespaces(event_store: "analytics")This is especially useful in multi-tenant applications:
defmodule MyApp.Tenants do def list_all_tenants() do case Chronicle.get_namespaces() do {:ok, tenant_ids} -> tenant_ids {:error, _} -> [] end end
def migrate_all_tenants(migration_func) do Enum.each(list_all_tenants(), fn tenant_id -> IO.puts("Migrating tenant: #{tenant_id}") migration_func.(tenant_id) end) endendTypical Usage
Section titled “Typical Usage”Multi-Tenant Admin Interface
Section titled “Multi-Tenant Admin Interface”defmodule MyApp.Admin.Dashboard do alias MyApp.Tenants
def get_system_stats() do with {:ok, stores} <- Chronicle.get_event_stores(), {:ok, namespaces} <- Chronicle.get_namespaces() do %{ event_stores: stores, current_store_namespaces: namespaces, total_tenants: Enum.count(namespaces) } else {:error, reason} -> {:error, reason} end end
def list_tenant_info() do with {:ok, tenant_ids} <- Chronicle.get_namespaces() do Enum.map(tenant_ids, fn tenant_id -> %{ id: tenant_id, event_store: "my-app", namespace: tenant_id } end) else {:error, reason} -> {:error, reason} end endendVerifying Environment Before Operations
Section titled “Verifying Environment Before Operations”defmodule MyApp.Operations do def safe_append(tenant_id, event) do with {:ok, namespaces} <- Chronicle.get_namespaces() do if Enum.member?(namespaces, tenant_id) do Chronicle.append("entity-123", event, namespace: tenant_id) else {:error, :tenant_not_found} end end end
def safe_read(tenant_id, entity_id) do with {:ok, namespaces} <- Chronicle.get_namespaces() do if Enum.member?(namespaces, tenant_id) do Chronicle.read_model(MyApp.ReadModels.Entity, entity_id, namespace: tenant_id) else {:error, :tenant_not_found} end end endendMulti-Store Operations
Section titled “Multi-Store Operations”get_event_stores/1 lists every event store on the kernel, but reading from one still goes through a client. The :client option on reads and appends names a running Chronicle.Client, by the name: it was started with; it doesn’t accept an event store name. To work with several event stores, start one named client per store and pass that name:
children = [ Supervisor.child_spec( {Chronicle.Client, name: :orders, connection_string: connection_string, event_store: "orders"}, id: :orders ), Supervisor.child_spec( {Chronicle.Client, name: :billing, connection_string: connection_string, event_store: "billing"}, id: :billing )]
Supervisor.start_link(children, strategy: :one_for_one)Each client needs its own child id, because every Chronicle.Client child spec defaults to the same id. Once the clients are running, pass the name to target one:
{:ok, orders} = Chronicle.all(MyApp.ReadModels.Order, client: :orders)Namespaces are different: get_namespaces/1 takes an :event_store option directly, so one client can inspect any store:
defmodule MyApp.MultiStore do def namespace_counts do with {:ok, stores} <- Chronicle.get_event_stores() do Enum.map(stores, fn store_name -> case Chronicle.get_namespaces(event_store: store_name) do {:ok, namespaces} -> %{store: store_name, namespace_count: length(namespaces)} {:error, reason} -> %{store: store_name, error: reason} end end) end endendOptions
Section titled “Options”Both get_event_stores/1 and get_namespaces/1 accept options:
:client— the client name (default:Chronicle.Client):event_store— forget_namespaces/1, the event store to query (defaults to the configured client’s event store)
# Use a named clientChronicle.get_event_stores(client: :my_app_client)
# Query a specific event store's namespacesChronicle.get_namespaces(event_store: "crm")
# BothChronicle.get_namespaces(client: :analytics, event_store: "analytics")Error Handling
Section titled “Error Handling”Both functions return {:ok, list} on success or {:error, reason} on failure. {:error, :not_connected} means the client hasn’t connected yet or is reconnecting; see Resilience.
case Chronicle.get_namespaces() do {:ok, namespaces} -> IO.puts("Namespaces: #{inspect(namespaces)}")
{:error, :not_connected} -> IO.puts("Chronicle is not connected yet")
{:error, reason} -> IO.puts("Error: #{inspect(reason)}")endPassing a :client name that no running client uses raises ArgumentError (no persistent term stored with this key) instead of returning an error tuple.
See Also
Section titled “See Also”Chronicle.EventStores— low-level event store discoveryChronicle— high-level API- Get started: install the client and start
Chronicle.Client Chronicle.Client: client configuration options- Event Store and Namespaces — the shared concepts behind event stores and namespace-scoped tenancy