Skip to content

Instantly share code, notes, and snippets.

@exupero
Created October 23, 2018 00:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save exupero/e71a2ddcf99686cfc3c32b0f249e5ada to your computer and use it in GitHub Desktop.
Save exupero/e71a2ddcf99686cfc3c32b0f249e5ada to your computer and use it in GitHub Desktop.
A state machine example
; Constructing a physical building is, ideally, a linear process, but in reality there can be twists
; and turns and backtracking to previous steps. Here's a state machine that models a simple,
; hypothetical construction process:
(def construction-process
{:concept {:revised-concept :concept
:township-approved :township-permission
:township-rejected :terminated}
:township-permission {:blueprints-delivered :blueprints}
:blueprints {:code-violations :blueprints-in-revision
:code-approval :ready-for-construction}
:blueprints-in-revision {:blueprints-delivered :blueprints}
:ready-for-construction {:construction-began :constructing}
:constructing {:inspection-violations :fixing-construction
:inspection-approval :constructing
:finish-constructing :finished-construction}
:fixing-construction {:inspection-violations :fixing-construction
:inspection-approval :constructing}
:finished-construction {:ribbon-cutting :complete}
:complete {}
:terminated {}})
; Here's a function that will take an arbitrary state machine map like above, a current state within
; that machine, and an event that transitions to a new state. It returns the state after the event:
(defn next-state [process current-state event]
(get-in process [current-state event]))
; Here are some stubbed database functions. Flesh these out according to your database solution:
(defn get-building-by-id [id]
{:current-state :concept})
(defn set-building-by-id [id building]
building)
; Finally, here's a function that retrieves a building's data and uses several events to transition
; the building from its current state to a new state. Any events that are illegal from a given
; state throw an error.
(defn process-events [building-id events]
(let [{:keys [current-state] :as building} (get-building-by-id building-id)
new-state (reduce
(fn [state event]
(or (next-state construction-process state event)
(throw (Exception. "Invalid event for the current state."))))
current-state
events)]
(set-building-by-id building-id (assoc building :current-state new-state))))
; And here's some code that exercises the above function.
(comment
(process-events 0 [:township-approved
:blueprints-delivered
:code-violations
; :blueprints-delivered
; :code-approval
; :construction-began
; :inspection-violations
; :inspection-approval
; :finish-constructing
; :ribbon-cutting
])
)
; A few nice things about defining a multi-step process as a hash-map of states and event
; transitions:
; - Self-documenting for non-technical users
; - Can be used to generate flow charts (e.g. via graphviz)
; - Can be stored in a database or in document storage (e.g. S3)
; - Can be altered using a simple form UI
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment