A long-running GenServer holds orders, positions, strategy state, and operational flags for a system trading real capital. The post walks the actual state struct, the three categories of data inside it, the persistence model (write to the database before the broker, push heavy work to Oban, never persist candles), and the design principles that fell out of running it in production — including why mid-roll restarts refuse to resume rather than guess.
strategy,market_data_source,account_manager— each is a map with amodulekey and additional context.
I suggest to flatten them into the parent struct, use tuples, or use new structs. A map is a bad choice because with a map I have to write the keys and I will make typos.
trading?,rolling?,portfolio_heat_warned,portfolio_heat_blocked— state machine flags that control what
the GenServer is allowed to do.
Boolean flags are bad choices to encode state machine states (sooner you will find some combinations are illegal). Ideally, they should use enum types but since Elixir does not have a true enum type I’d just use atoms.
Thanks Derek — both fair, and both in the direction I’d take this code if I were refactoring it now.
On the maps: bare maps with conventional keys are a typo waiting to happen, you’re right. They ended up that way because each handle carries module-specific context whose shape varies by implementation — a strategy handle for order-flow logic holds different keys than one for a calendar roll. A struct with module: and a free-form context: map would give me a static field on the part that actually dispatches without forcing a schema on the part that legitimately varies. That’s the refactor I’d make.
On the booleans: the case is strongest for portfolio_heat_warned / portfolio_heat_blocked, which are an escalation ladder rather than two independent flags. Collapsing them into portfolio_heat: :ok | :warned | :blocked makes the “blocked but not warned” state unrepresentable, which is exactly what you’d want. trading? and rolling? are closer to genuinely independent — rolling can be in progress while trading is administratively halted — but I’d still take a tagged status to force the question of which combinations are actually reachable.
Appreciate the careful read.
Either we call if FSM, or we have flags, tertium non datur. State machine is not an object having state field. There should be a single source of truth and any flags spread the responsibility inevitably resulting in a diverged inconsistent state sooner or later.
FSM has transitions and the transition callback in the only place where the decision might have happened.






















