Introducing the WeGO state transition machine (STM)
Edit me

Introduction

Transactional entities exhibit a life cycle. Their lifecycle can be modeled using events. Examples of such entities are orders, payments, customer relationship issues etc. These entities have a workflow associated with them. The workflow dictates the kind of events that these entities undergo. In most organizations, these workflows are extremely dynamic. They can evolve over a period of time.

Hence it is imperative to model the workflow as a first class entity. This allows the workflow to evolve as the organizations evolve.

The state transition machine is an artifact that separates the workflow from the core entity. Workflows can be modeled graphically and fed into a state machine using a JSON (or similar). The state machine ensures that the entity changes state in conformance to the workflow. At various points of the workflow code can get called. This code can do specific actions.

This gives a huge deal of flexibility to the service that is responsible for the entity. The WeGO state transition machine is a simple but effective way of managing entity workflows.

Typical Life Cycle of a State Managed Transactional Entity

These entities get created by an authorized user. The entities mutate their state when events happen to them. If these events can be stored and replayed then the transactional entity can be replayed in full. In that way, state entities offer a natural way of event sourcing Event modeling of such entities makes them auditable.

Modeling State

State entities are modeled using a state transition diagram. (STD) State entity is a graph of states as nodes connected by events which are edges connecting the nodes. An entity can traverse from one state to the next only by handling events that depict the edges.

The WeGO State Machine (STM) - code

The WeGO state machine provides a way to implement a state machine by capturing the entire STD using a JSON.

In code, this is done by invoking the makeStm method.

  actionCatalog := map[string]interface{} 
  // populate action catalog.. see notes below
  fsm, err := stm.MakeStm("test.json",actionCatalog)
  // instantiate a state entity i.e. an entity that returns  GetState and SetState methods
  // Example: if there an exists a type called Order which implements both GetState and SetState
  type Order struct{ State string}
  func (ord *Order)GetState() string{
     return ord.State
   }
   func (ord *Order) SetState(s string){
       ord.State = s
   }
  order := &Order{} 
   // pass eventId and eventParam for 
  o = fsm.Process(ctx, &order, eventId, eventParam).(*Order)
  // o is a pointer to the mutated version of the Order

As seen above, the first step is to create an STM and then use it with an entity such as order that implements the methods in the StateEntity interface defined in the stm package.

Let us look at the test case for more details.

stm_test

stm_test uses a test.json that defines four states created -> confirmed -> fulfilled -> closed

for a testOrder. The testOrder supports the following events to progress from one state to the next

confirm - to progress from created to confirmed state fulfil - to progress from confirmed to fulfilled state close - to progress from fulfilled to closed state

Additionally there is a state called cancelled to which the testOrder can progress from either the created or confirmed states. However, progressing from confirmed to cancelled state incurs a penalty of 25 currency units. This makes the order non closeable from the cancelled state.

This process is represented in the std using the state check-if-closeable which checks if the charges are > 0 to decide to allow the transition.

testOrder struct

testOrder is the name of the state entity in stm_test. This entity needs to implement the method in the StateEntity interface (obviously!) to qualify as a state entity. Besides that, it has specific fields with various order attributes. The field CalledFunctions keeps track of all the functions that have been called in an array. This is useful for testing and allows assertions to be made.

Actions

There are four types of actions supported by the WeGO STM:

  • Transition Actions - an action that is called for every event
  • Preprocessor Action - which is called before a state is entered
  • Postprocessor Action - which is called after a state is exited
  • AutomaticState Action - which is called to determine the event in an automatic state

stm_test - actions

In stm_test there exists a preprocessor and a postprocessor. Additionally all actions are mapped to a struct called action which merely adds an entry to the CalledFunctions array. cancelledConfirmedAction is special since it levies charges on the testOrder. So this action is mapped only to the cancelledConfirmedAction event.

Automatic States

The check-if-closeable state is an automatic state. This means that the stm requires a special action to allow the computation of events given the state of the entity.

The checkIfCloseable type takes care of this requirement. It uses the charges as the basis to determine if the testOrder is eligible to be closed or not.

The test cases

The test cases cover the gamut of functionality as the entity transitions from one state to the other.