Observing executions¶
Every commit harel makes is described by a Step: what caused it, and how the execution moved.
Wrap a store in ObservedStore, and a function of yours hears about every step once it is
committed — to count, alert, audit or notify, without touching the machines or the runners.
from harel import DictStore, DurableRunner, Event, ObservedStore, definition_from_dsl
from harel.engine.execution import Status
defn = definition_from_dsl(
"""
event Approve {}
machine approval {
initial Review
state Review {}
final Approved success
from Review to Approved on Approve
}
""",
"approval",
)
finished = []
def on_step(step, exe):
if step.to_status is Status.DONE and step.from_status is not Status.DONE:
finished.append((exe.id, exe.outcome))
runner = DurableRunner(ObservedStore(DictStore(), on_step), {defn.id: defn})
exe = runner.create(defn.id)
runner.process(exe.id, Event(kind="Approve"))
assert finished == [(exe.id, "success")]
What a step says¶
Step (from harel, or harel.engine.store.base) is a frozen record:
field |
what |
|---|---|
|
|
|
the event, for |
|
for |
|
the execution’s status before and after the commit |
|
its active state before and after |
|
the actions the step ran, by name — none for a step that failed the execution (its effects are dropped; |
The status before is what tells a step into a status apart from one that stays there: a step
into FAILED carries no finished_at — a dead letter can still be redriven — so
from_status/to_status is how to see it happen. A region’s steps come with the region’s own
execution (its parent_id names the parent).
on_step(step, exe) gets a copy of the execution as committed. It runs in the writer’s thread
or loop, right after the commit — keep it quick, or hand the work off. If it raises, the error
is logged and the step stands: it is already committed. AsyncObservedStore wraps an async
store, and takes a plain or a coroutine function.
Transactional stores¶
ObservedStore calls on_step once the wrapped store’s commit has returned — durable, for a
store that commits on its own. A store that writes inside a transaction it doesn’t end (one on
the caller’s connection) has committed nothing yet when commit returns, and the transaction
may still roll back. Such a store reads the step argument of its own commit — every writer
passes one — and defers what it does until that transaction commits.