Orkestra.Projection.Storage.Postgres (orkestra v0.2.0)

Copy Markdown View Source

PostgreSQL storage adapter implementing Orkestra.Projection.Storage.

write/4

Returns an Ecto.Multi.t() fragment — a pure, composable data structure describing the read-model writes for a single event. The Projector GenServer (Plan 03) appends this fragment to its checkpoint upsert Multi before calling Repo.transaction/1, enabling the atomic co-write required by STORE-03.

The Repo is never referenced here. It is injected by the calling GenServer at transaction time, so the adapter has no direct dependency on any specific Ecto.Repo module. This preserves the STORE-03 boundary: the GenServer owns the transaction; the adapter produces only a composable descriptor.

Step Naming Convention

All read-model steps in the Multi returned by write/4 must use the :read_model_ prefix (e.g. :read_model_insert, :read_model_update). This prevents name collisions when the GenServer appends the read-model Multi to its own checkpoint Multi using Ecto.Multi.append/2. The GenServer's reserved step names are :checkpoint, :halted_checkpoint, and :dead_letter — none of which start with :read_model_.

This naming convention is a contract between the adapter and the GenServer. Consumers implementing the :handler option for write/4 should follow the same prefix convention in their Multi step names.

Evolution Note

write/4 currently takes a 3-arity :handler function injected by the caller (the per-projector DSL or test). The Phase 3 DSL will wire this automatically from the projector definition, eliminating the need to pass :handler explicitly at every call site. This is the planned Phase-2/3 seam.

STORE-02 Compliance

This adapter satisfies STORE-02 (Postgres read-model writes) by returning a composable Ecto.Multi.t() that integrates with the GenServer's checkpoint transaction. All writes go through Ecto parameterized queries / changesets — no raw string-built SQL (T-02-04).

STORE-04 Compliance

reset/2 clears all read-model rows for a projector on the injected Repo, enabling rebuild from position 0.

Summary

Functions

Deletes all read-model rows for projector_name on the injected Repo.

Returns a composable Ecto.Multi.t() fragment for the read-model write.

Functions

reset(projector_name, opts)

Deletes all read-model rows for projector_name on the injected Repo.

Requires :repo (the Ecto.Repo module) and :schema (the Ecto schema module for the read-model table) as options. The schema module must have a projector_name field.

Used during projector rebuild (STORE-04) to clear the read model before replaying the event stream from position 0.

Returns :ok on success.

write(projector_name, event, position, opts)

Returns a composable Ecto.Multi.t() fragment for the read-model write.

Requires a :handler option — a 3-arity function with signature:

(projector_name :: String.t(), event :: map(), position :: non_neg_integer())
-> {:ok, Ecto.Multi.t()} | {:error, term()}

The handler must build a Multi whose step names all start with :read_model_ to avoid clashing with the GenServer's reserved :checkpoint, :halted_checkpoint, and :dead_letter steps (see module doc).

Returns {:ok, multi} on success or {:error, reason} if the handler returns an error.