Gearbox - A functional state machine with an easy-to-use API, inspired by both Fsm and Machinery

Hey! Let me start off by giving you some context why I built Gearbox.

I work in an ecommerce company where we have a domain model called Transaction, but we sometimes see invalid state transitions for our transactions when it shouldn’t happen. For example, we saw a transaction goes from success to rejected, which isn’t a possible transition in our domain - if it’s marked as success, you should only be able to refund that transaction, not reject it.

We digged in a little and figured that this is happening due to some failure in some expire worker.

In that domain, we first create a transaction with pending_payment status, we then prompt user for payment and await the callback from payment provider. However at the same time, we queue a worker to timeout this payment in X minutes, if payment hasn’t been made.

In this particular scenario, the callback comes in, we mark the transaction as success, which is all good and expected for user, however our timeout worker continued to run after X minutes and it changed the transaction from success to timeout, a bug in the system.

We could of course update our worker to handle that logic to first check for that status of transaction, but that’s a lot of defensive programming, and what about when we add more states? The number of possible transitions grows exponentially, and if we were to defensively check against every single possible states, we’d have so many different cases to check for, that’s a code smell.

This is where something like a state machine comes in, the main idea of a state machine is really to eliminate impossible state transition. For example, let’s see how Gearbox solves this problem for us.

With Gearbox, you have to first tell your machine, what possible states can happen in your system:

defmodule Machine do
  use Gearbox,
    states: ~w(one two three four five) # finite states
end

We then have to very explicitly declare the possible transitions in the machine.

defmodule Machine do
    use Gearbox,
      states: ~w(pending_payment success timeout refunded) # finite states
+     transitions: %{
+       "pending_payment" => ~w(success timeout),
+       "success" => ~w(refunded),
+     }
end

Now when I look at the machine, I know:

  • exactly what can happen between two states
  • exactly what possible states can transition from pending
  • what are my final/acceptable states (states that can’t transition to something else)
  • I can be sure that my transaction will never be able to make illegal state transitions (pending_payment => refunded is not valid)

Does that sort of make sense to you? @thojanssens1 If not feel free to ask more questions!

Here’s some resources, but there are a lot of examples out in the wild, you can just Google for Finite State Machine :slight_smile:

There are honestly a lot of studies into FSM as well, and frankly I am not even well-versed in them all - I just needed to solve my own problem and thus born Gearbox :slight_smile:

7 Likes