mudasobwa
Finitomata :: FSM boilerplate based on callbacks
Finitomata provides a boilerplate for FSM implementation, allowing to concentrate on the business logic rather than on the process management and transitions/events consistency tweaking.
It reads a description of the FSM from a string in PlantUML format.
It validates the FSM is consistent, namely it has a single initial state, one or more final states, and no orphan states. If everything is OK, it generates a GenServer that could be used both alone, and with provided supervision tree. This GenServer requires to implement three callbacks
-
on_transition/4— mandatory -
on_failure/3— optional -
on_terminate/1— optional
All the callbacks do have a default implementation, that would perfectly handle transitions having a single to state and not requiring any additional business logic attached.
Example:
defmodule MyFSM do
@fsm """
[*] --> s1 : to_s1
s1 --> s2 : to_s2
s1 --> s3 : to_s3
s2 --> [*] : ok
s3 --> [*] : ok
"""
use Finitomata, @fsm
def on_transition(:s1, :to_s2, event_payload, state_payload),
do: {:ok, :s2, state_payload}
end
Most Liked
mudasobwa
Package published to finitomata | Hex
(25ec5990045d025fba2bb1c59a92e08fc8a045fdd347fcdcb885f22c75a1f2d2)
Introduces Infinitomata module as a drop-in replacement of Finitomata.{start_fsm/4,transition/4,state/3}. It transparently runs in a cluster, leveraging process groups :pg to keep track of spawned instances.
Example from tests:
defmodule InfinitomataTest do
use ExUnit.Case, async: true
@moduletag :distributed
setup do
{_peers, _nodes} = Enfiladex.start_peers(3) # start 3 peers
Enfiladex.block_call_everywhere(Infinitomata, :start_link, [])
end
test "many instances (distributed)" do
for i <- 1..10 do
Infinitomata.start_fsm("FSM_#{i}", Finitomata.Test.Log, %{instance: i})
end
assert Infinitomata.count(Infinitomata) == 10
for i <- 1..10 do
Infinitomata.transition("FSM_#{i}", :accept)
end
assert %{"FSM_1" => %{}} = Infinitomata.all(Infinitomata)
for i <- 1..10 do
Infinitomata.transition("FSM_#{i}", :__end__)
end
Process.sleep(1_000)
assert Infinitomata.count(Infinitomata) == 0 # all finished ending state
assert Infinitomata.all(Infinitomata) == %{}
end
end
mmmries
Thanks @mudasobwa your breakdown definitely improved my thinking. I can see how a generator might be a pain to deal with in source control etc. And the distinction between run-time, compile-time and deploy-time is helpful as well. Thanks again for the library, I’ll give it a run
mudasobwa
To support my own rant about making our libraries test-friendly (based on finitomata example), I implemented the test scaffold generation, based on the FSM shape, collected during the compile time.
mix finitomata.generate.test --module MyApp.FSM
The above would generate a scaffold for checking all the possible paths in the FSM declaration. Enjoy.
https://hexdocs.pm/finitomata/Finitomata.ExUnit.html#module-test-scaffold-generation








