Nowadays, I usually write Elixir in my spare time. Not only that, actually whenever I encounter a problem at work, or have some random new ideas, I always think about how I can appraoch it in Elixir - or rather OTP style.
The more I try to design or architect things, the more often I find myself reaching out to
gen_statem (a successor to
gen_fsm) instead of
gen_server. A lot of things just naturally evolves over time between different states, and they should respond to things differently according to what state they are in.
The only downside is
gen_statem does not have a wrapper in Elixir like
GenServer, so you can’t
use GenServer and import a bunch of default callbacks - doesn’t really matter anyway. I gave this a real try in a rewrite of card_labeler, but before that I also spend a little time to figure out how I can make the state machine to make state transitions on its own. And this is my topic today.
States and Events (Messages)
By default, a state machine can only make state transition when handling incoming events, but in my cases I want it to do something when entering a state and directly transit itself to the next state when finished. For example the states I imagined for card_labeler include:
preparing: initialization, but because this reaches to external API, I shouldn’t put it in
resting: sleep for sometime, either because I don’t want to be rate limited or I simply don’t need to update that frequently
tracking: track issue changes using GitHub API
updating: update the project board accordingly using the tracked update
And it should transit between these states on its own, that’s why I came up with this fancy name “autonomous state machine”. I’m not really sure whether this totally makes sense or whatnot, but organizing different actions using different states makes my code a lot cleaner.
Once again I used my Elixir playground to test how I can achieve this. By reading the documentation closer, the key is to use state enter calls. This basically sends a state machine itself a message (
:enter) when trasitting to a new state. To enable it just add it in the
callback_mode setup, like:
def callback_mode, do: [:state_functions, :state_enter]
Note: by enabling it you need to handle the
:enter message for all states.
You can checkout my full example here and by running it (
mix run --no-halt lib/statem.exs) you should see logs like this:
19:51:06.138 [debug] inited 19:51:06.138 [debug] preparing 19:51:06.138 [debug] prepared 19:51:06.138 [debug] resting from preparing 19:51:06.141 [debug] tracking 19:51:06.141 [debug] tracked 19:51:06.141 [debug] updating 19:51:06.141 [debug] updated 19:51:06.141 [debug] resting from updating 19:51:11.142 [debug] tracking 19:51:11.142 [debug] tracked 19:51:11.142 [debug] updating 19:51:11.142 [debug] updated 19:51:11.142 [debug] resting from updating 19:51:16.142 [debug] tracking 19:51:16.142 [debug] tracked 19:51:16.142 [debug] updating 19:51:16.142 [debug] updated 19:51:16.142 [debug] resting from updating
And that’s all, I should have written this thing a long time ago. Anyway, I plan to write more small and simple posts like this whenever I discover something new in Elixir.