StatifierRouter.Delivery (StatifierRouter v0.1.0)

Copy Markdown View Source

The delivery module StatifierRouter.Config names by default: one binding's delivery of one event, as one transaction on the host's repo (ADR-0003, section 1).

deliver/4 is the seam StatifierRouter.route/3 calls. It reads four options of the configuration:

  • :store - the %StatifierPersistence.Storage{} executions are kept in. It must be built over the configuration's own :repo, so that StatifierPersistence.Executions.create/4 and StatifierPersistence.Executions.step/5 write through the delivery's transaction rather than opening their own (statifier_persistence's README, "Writing inside a caller's transaction").
  • :executor - the StatifierPersistence.Executor both doors hand their effects to.
  • :resolver - the StatifierRouter.Resolver, a module or an arity-2 fun, answering {content_hash, machine} or {:error, reason} for (scope, document): the chart a new execution of document starts on (ADR-0002, section 4). It is called only when the delivery is about to create an execution.
  • :chart_resolver - (content_hash) -> {:ok, machine} or :error: the compiled chart an existing execution started on, which step/5 is handed. An existing execution keeps the chart it started on, so this is looked up by the content hash its record carries, never by its document.

What one delivery does

The transaction first claims (binding_id, message_id) with StatifierRouter.Dedupe.claim/4, its first write under every create mode. When the pair's row is present and unexpired, the delivery is a duplicate, and the transaction writes the ledger row and nothing else: no address row is read or written (ADR-0003, section 6). Past the claim, the binding's create decides the rest (ADR-0003, section 4):

  • :if_absent reads the address row for (scope, document, key). When there is none, it mints an execution id and inserts the row with it, an insert that inserts nothing on a conflict with the unique index and does not fail the transaction; when it inserted nothing, another delivery's row won the race, and a following statement reads that row (ADR-0003, section 3). For the row it inserted, it asks the resolver for the chart and calls create/4 under the minted id; for a row it read, it reads the execution's status.
  • :never reads the address row and never inserts one. When there is none, the outcome is {:dropped, binding_id, :no_execution}: nothing is created or stepped, and the ledger row and the claim's dedupe row are the delivery's only writes. For a row it read, it reads the execution's status, as :if_absent does.
  • :always_new neither reads nor writes an address row (ADR-0002, section 7). It mints an execution id, asks the resolver for the chart and calls create/4 under that id, for every delivery; the minted id is the only handle on the execution.

Then it calls step/5 with the event, unless the execution is terminal, writes the ledger row (ADR-0004, section 4) and commits.

The outcomes are {:created_and_delivered, binding_id, execution_id} when this delivery created the execution, {:delivered, binding_id, execution_id} when it stepped one that existed, {:duplicate, binding_id} when the claim found the pair already handled, {:dropped, binding_id, :no_execution} for a :never binding whose address has no row, and {:dropped, binding_id, :finished} when the execution was terminal: read terminal before the step, created already terminal, or answered {:discarded, execution} by step/5 (ADR-0004, section 3). A terminal sighting through an address row stamps the row's terminal_seen_at when it is still empty (ADR-0002, section 5); the row itself is left in place, and removing it is StatifierRouter.Addresses.reap/2's. Every outcome but the duplicate commits the dedupe row the claim wrote, a drop included, and a duplicate's ledger row carries the key and no execution id (ADR-0004, section 4).

Nothing here writes the input log: step/5 appends the event it steps (ADR-0003, section 1).

An {:error, reason} from create/4, step/5 or StatifierPersistence.Storage.fetch_execution/2, {:error, {:unresolved_document, document, reason}} when the resolver answers {:error, reason}, and {:error, {:chart_not_resolved, content_hash}} when the chart resolver answers :error, roll the whole transaction back and are returned. A raise rolls it back the same way and propagates; nothing here rescues it. Effects the executor was handed before a rollback stay fired (ADR-0003, section 2).

The execution id is a UXID with the prefix ex, minted by UXID.generate!/1; nothing is derived from the address (ADR-0002, section 3).

Summary

Functions

Delivers one event for one binding under key, as the module documentation describes, and answers with the outcome or {:error, reason}.

Functions

deliver(config, binding, key, delivery)

Delivers one event for one binding under key, as the module documentation describes, and answers with the outcome or {:error, reason}.