Skip to content

Instantly share code, notes, and snippets.

@caelifer
Last active November 23, 2016 15:34
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 caelifer/b902366b973f169772e21ff3cf438ef4 to your computer and use it in GitHub Desktop.
Save caelifer/b902366b973f169772e21ff3cf438ef4 to your computer and use it in GitHub Desktop.
package main
import (
"fmt"
"log"
"sync"
)
func main() {
// Create OMS instance
oms := NewOMS()
// Observer callback generator
generateObserverFor := func(state State) (State, func(*Order)) {
return state, func(o *Order) {
fmt.Printf("order(%d) transitioned to new state: %v\n", o.ID(), state)
}
}
// Install observers for all known state transitions
oms.SetObserver(generateObserverFor(StateNew))
oms.SetObserver(generateObserverFor(StatePendingAccept))
oms.SetObserver(generateObserverFor(StateAccepted))
oms.SetObserver(generateObserverFor(StatePartiallyFilled))
oms.SetObserver(generateObserverFor(StateFilled))
oms.SetObserver(generateObserverFor(StateCanceled))
oms.SetObserver(generateObserverFor(StateRejected))
// Run test cases
for _, tc := range []struct {
order *Order
states []State
}{
{
oms.NewOrder(),
[]State{StatePendingAccept, StateAccepted, StateFilled},
},
{
oms.NewOrder(),
[]State{StatePendingAccept, StateAccepted, StateCanceled},
},
{
oms.NewOrder(),
[]State{StatePendingAccept, StateAccepted, StatePartiallyFilled, StateFilled},
},
{
oms.NewOrder(),
[]State{StatePendingAccept, StateRejected},
},
{
oms.NewOrder(),
[]State{StatePendingAccept, StateFilled},
},
} {
for _, s := range tc.states {
// log.Print(oms)
if err := oms.SetNewState(tc.order, s); err != nil {
log.Fatalf("FATAL: Order(%d) %v", tc.order.ID(), err)
}
}
}
}
type State int
func (s State) String() string {
switch s {
case StateNew:
return "StateNew"
case StatePendingAccept:
return "StatePendingAccept"
case StateAccepted:
return "StateAccepted"
case StatePartiallyFilled:
return "StatePartiallyFilled"
case StateFilled:
return "StateFilled"
case StateRejected:
return "StateRejected"
case StateCanceled:
return "StateCanceled"
}
return "StateUnknown"
}
const (
StateNew State = iota
StatePendingAccept
StateAccepted
StatePartiallyFilled
StateFilled
StateRejected
StateCanceled
)
type Transition struct {
state State
trans []*Transition
}
func NewTransition(state State) *Transition {
return &Transition{state: state}
}
func (g *Transition) Add(node *Transition) {
g.trans = append(g.trans, node)
}
func (g Transition) TransitionTo(state State) (*Transition, error) {
for _, tt := range g.trans {
if tt.state == state {
return tt, nil
}
}
return nil, fmt.Errorf("bad state transition [%v -> %v]", g.state, state)
}
func (g Transition) State() State {
return g.state
}
type OrderStateMachine struct {
orders map[*Order]*Transition
observers map[State][]func(*Order)
}
func NewOMS() OrderStateMachine {
return OrderStateMachine{
orders: make(map[*Order]*Transition),
observers: make(map[State][]func(*Order)),
}
}
var oid int
var rootOrderStateGraph *Transition
func init() {
rootOrderStateGraph = initOrderStateGraph()
}
func (sm OrderStateMachine) NewOrder() *Order {
oid++
order := NewOrder(oid)
sm.orders[order] = rootOrderStateGraph
sm.notifyObservers(order, StateNew)
return order
}
func (sm OrderStateMachine) SetObserver(state State, ob func(*Order)) {
obs := sm.observers[state]
sm.observers[state] = append(obs, ob)
}
func (sm OrderStateMachine) SetNewState(o *Order, state State) error {
currentState, ok := sm.orders[o]
// Make sure order is known
if !ok {
return fmt.Errorf("unknown order (%p) id: %d", o, o.ID())
}
// Set new state
currentState, err := currentState.TransitionTo(state)
if err != nil {
return err
}
// Update state machine
sm.orders[o] = currentState
// Notify observers
sm.notifyObservers(o, state)
return nil
}
func (sm OrderStateMachine) notifyObservers(o *Order, state State) {
// Notify all observers for the new state
var wg sync.WaitGroup
for _, f := range sm.observers[state] {
wg.Add(1)
go func(f func(*Order)) {
defer func() {
if r := recover(); r != nil {
log.Printf("WARN: notifier failed: %v", r)
}
wg.Done()
}()
f(o)
}(f)
}
wg.Wait()
}
func initOrderStateGraph() *Transition {
// Start is always OrderNew
root := NewTransition(StateNew)
// New -> Canceled -> STOP
canceled := NewTransition(StateCanceled)
root.Add(canceled)
// New -> Rejected -> STOP
rejected := NewTransition(StateRejected)
root.Add(rejected)
// PendingAccept sub-graph
pendingAccept := NewTransition(StatePendingAccept)
// New -> PendingAccept ...
root.Add(pendingAccept)
// PendingAccept -> Canceled -> STOP
pendingAccept.Add(canceled)
// PendingAccept -> Rejected -> STOP
pendingAccept.Add(rejected)
// Accepted sub-graph
accepted := NewTransition(StateAccepted)
// PendingAccept -> Accepted ...
pendingAccept.Add(accepted)
// Accepted -> Canceled
accepted.Add(canceled)
// Accepted -> Rejected
accepted.Add(rejected)
// Accepted -> Filled -> STOP
filled := NewTransition(StateFilled)
accepted.Add(filled)
// PartiallyFilled sub-graph
partiallyFilled := NewTransition(StatePartiallyFilled)
// Accepted -> PartiallyFilled ...
accepted.Add(partiallyFilled)
// PartiallyFilled -> Canceled -> STOP
partiallyFilled.Add(canceled)
// PartiallyFilled -> Rejected -> STOP
partiallyFilled.Add(rejected)
// PartiallyFilled -> Filled -> STOP NB(Can the Filled state be canceled?)
partiallyFilled.Add(filled)
return root
}
// Domain object
type Order struct {
id int
}
func NewOrder(id int) *Order {
return &Order{id: id}
}
func (o Order) ID() int {
return o.id
}
@caelifer
Copy link
Author

caelifer commented Nov 22, 2016

Live code - https://play.golang.org/p/kfQo0IstlU

Output:

order(1) transitioned to new state: StateNew
order(2) transitioned to new state: StateNew
order(3) transitioned to new state: StateNew
order(4) transitioned to new state: StateNew
order(5) transitioned to new state: StateNew
order(1) transitioned to new state: StatePendingAccept
order(1) transitioned to new state: StateAccepted
order(1) transitioned to new state: StateFilled
order(2) transitioned to new state: StatePendingAccept
order(2) transitioned to new state: StateAccepted
order(2) transitioned to new state: StateCanceled
order(3) transitioned to new state: StatePendingAccept
order(3) transitioned to new state: StateAccepted
order(3) transitioned to new state: StatePartiallyFilled
order(3) transitioned to new state: StateFilled
order(4) transitioned to new state: StatePendingAccept
order(4) transitioned to new state: StateRejected
order(5) transitioned to new state: StatePendingAccept
2009/11/10 23:00:00 FATAL: Order(5) bad state transition [StatePendingAccept -> StateFilled]

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