Last active
November 23, 2016 15:34
-
-
Save caelifer/b902366b973f169772e21ff3cf438ef4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Live code - https://play.golang.org/p/kfQo0IstlU
Output: