Finitomata.Persistency behaviour (Finitomata v0.40.0)

Copy Markdown View Source

The behaviour to be implemented by a persistent storage to be used with Finitomata (pass the implementation as persistency: Impl.Module.Name to use Finitomata.)

Once declared, the FSM attempts to load its current state from the storage on start via load/1, and writes back on every successful transition via store/3 (and store_error/4 on a failed one).

Built-in adapters

Two zero-dependency adapters ship with the library and only require their owner process to be added to the supervision tree:

For database-backed persistence, implement this behaviour directly, or implement the Finitomata.Persistency.Persistable protocol for the carried struct and use the Finitomata.Persistency.Protocol adapter (see examples/ecto_integration).

The load/1 contract

Finitomata.Engine does not call load/1 with a bare id; it builds a {type, fields} descriptor from the start payload (a module, a struct, or a %{type: type, id: id} map) and expects back a {lifecycle, {state, payload}} tuple. A :loaded lifecycle skips the entry transition and resumes from the persisted state; :created/:unknown proceed through the normal entry flow.

Summary

Types

A persisted transition failure

The lifecycle of the loaded entity, driving whether the entry transition runs

The entity descriptor Finitomata.Engine passes to load/1

A persisted FSM snapshot: the current state and the carried payload

Callbacks

The function to be called from init/1 callback upon FSM start to load the state and payload from the persistent storage.

The function to be called from on_transition/4 handler to allow storing the state and payload to the persistent storage

The function to be called from on_transition/4 handler on non successful transition to allow storing the failed attempt to transition to the persistent storage

Types

error()

@type error() :: %{reason: any(), info: map(), payload: Finitomata.State.payload()}

A persisted transition failure

lifecycle()

@type lifecycle() :: :loaded | :created | :unknown

The lifecycle of the loaded entity, driving whether the entry transition runs

load_descriptor()

@type load_descriptor() :: {module(), keyword() | map()} | Finitomata.fsm_name()

The entity descriptor Finitomata.Engine passes to load/1

snapshot()

@type snapshot() :: {Finitomata.Transition.state() | nil, Finitomata.State.payload()}

A persisted FSM snapshot: the current state and the carried payload

transition_info()

@type transition_info() :: %{
  from: Finitomata.Transition.state(),
  to: Finitomata.Transition.state(),
  event: Finitomata.Transition.event(),
  event_payload: Finitomata.event_payload(),
  object: Finitomata.State.payload()
}

Callbacks

load(descriptor)

@callback load(descriptor :: load_descriptor()) ::
  {lifecycle(),
   {Finitomata.Transition.state() | nil, Finitomata.State.payload()}}

The function to be called from init/1 callback upon FSM start to load the state and payload from the persistent storage.

It receives the {type, fields} descriptor built from the start payload and returns {lifecycle, {state, payload}}, where state is nil for a freshly created entity.

store(id, object, transition)

@callback store(
  id :: Finitomata.fsm_name(),
  object :: Finitomata.State.payload(),
  transition :: transition_info()
) :: :ok | {:ok, Finitomata.State.payload()} | {:error, any()}

The function to be called from on_transition/4 handler to allow storing the state and payload to the persistent storage

store_error(id, object, reason, transition)

(optional)
@callback store_error(
  id :: Finitomata.fsm_name(),
  object :: Finitomata.State.payload(),
  reason :: any(),
  transition :: transition_info()
) :: :ok | {:error, any()}

The function to be called from on_transition/4 handler on non successful transition to allow storing the failed attempt to transition to the persistent storage