Skip to content

Instantly share code, notes, and snippets.

@eulerfx
Last active December 17, 2025 15:37
Show Gist options
  • Select an option

  • Save eulerfx/4ac420a14422ac960222 to your computer and use it in GitHub Desktop.

Select an option

Save eulerfx/4ac420a14422ac960222 to your computer and use it in GitHub Desktop.
The relationship between state machines and event sourcing

A state machine is defined as follows:

  • Input - a set of inputs
  • Output - a set of outputs
  • State - a set of states
  • S0 ∈ S - an initial state
  • T : Input * State -> Output * State - a transition function

If you model your services (aggregates, projections, process managers, sagas, whatever) as state machines, one issue to address is management of State. There must be a mechanism to provide State to the state machine, and to persist resulting State for subsequent retrieval. One way to address this is by storing State is a key-value store. Another way is to use a SQL database. Yet another way is event sourcing. The benefit of even sourcing is that you never need to store State itself. Instead, you rely on the Output of a service to reconstitute state. In order to do that, the state machine transition function needs to be factored into two functions as follows:

exec  : Input * State -> Output
apply : Output * State -> State

These two functions can be combined to yield the original transition function with the added benefit that the apply function can be used to reconstitute state based on past outputs. This can be done as follows:

state = fold apply S0 outputs

Where fold is a left fold and outputs is a set of outputs retrieved from an event store (such as @GetEventStore).

In order for this to be correct, the apply functions needs to be deterministic. This is where the event semantic becomes helpful.

Alternatively, the transition function can be factored a slightly different way to support command sourcing as follows:

exec  : Input * State -> Output
apply : Input * State -> State

These functions can also be combined to yield the state machine transition function. The difference from event sourcing, is that we rely on past inputs rather than past outputs to reconstitute state. Note also that the apply functions has to be deterministic. Also, with command sourcing, the Inputs must be stored in a durable log (such as @GetEventStore).

An example in F# here.

@azerum
Copy link

azerum commented Dec 17, 2025

Note that the meaning of "Output" changes as we move from State Machines to Event Sourcing:

In (Mealy) state machines, "Output" usually represent a side-effect of a transition. If you know just "currentState :: State" and "nextOutput :: Output", you may not be able to derive "nextState: State" - side-effect may have too little information for that. We only have transition :: State -> Input -> (State, Output)

In event sourcing, output actually represents entire transition. Knowing currentState and output, we use apply :: State -> Output -> State to get the new state. "Output" is more like a "Transition". Essentially, exec decides what transition (called Output) to make based on current state and input, and apply actually does that transition

It seems like the benefit of this in that one can store S0 :: State and outputs: [Output] in DB and replay state exactly how it happened historically, even if exec has changed since. But I have not figured that out yet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment