FSML is a XML based wrapper on top of https://github.com/looplab/fsm.
It provides the capabilities to define state machine of an entity in XML format and also supports features to handle error states and tasks execution on events and successful transition.
$ go get github.com/zain-bahsarat/fsml
import (
"fmt"
"strings"
"github.com/zain-bahsarat/fsml"
)
// Every object which is passed to the statemachine has to implement
// `Stateful` interface otherwise it will throw error
// -------------------------------------
// type Stateful interface{
// GetState() string
// SetState(state string) error
// }
// -------------------------------------
// create an entity which implements stateful interface
type order struct {
state string
fsml.Stateful
}
func (o *order) SetState(state string) error {
o.state = state
return nil
}
func (o *order) GetState() string {
return o.state
}
func main() {
statemachineDef := getSimpleStatemachineDef()
reader := strings.NewReader(statemachineDef)
sm, err := fsml.New(reader)
if err != nil {
fmt.Printf("error= %+v\n", sm)
}
o := &order{}
o.SetState("new")
if err := sm.Trigger("DummyEvent", o); err != nil {
fmt.Printf("error= %+v\n", err)
}
if !sm.Can("UndefinedEvent", o) {
fmt.Printf("cannot trigger UndefinedEvent on %+v\n", o)
}
}
// state machine definition
func getSimpleStatemachineDef() string {
return `<Schema>
<States>
<new>
<Events>
<DummyEvent targetState="pending" errorState="error"></DummyEvent>
</Events>
</new>
<pending></pending>
<error></error>
</States>
</Schema>`
}
Here is an example of state machine definition in xml format.
<Schema>
<States>
<new>
<Events>
<DummyEvent targetState="pending" errorState="error"></DummyEvent>
</Events>
</new>
<pending></pending>
<error></error>
</States>
</Schema>
Above statemachine has three states new
, pending
and error
and event named DummyEvent
- Schema
Root Node
- States
Container Node
- All the state definitions will be inside this node
- Events
Container Node
- Task
- OnStateSet
Default Event
- OnAfterEvent
Default Event
- OnBeforeEvent
Default Event
Default events can used inside each state to define default behaviors when state is updated. you can also define global default events.
<Schema>
<OnStateSet></OnStateSet>
<States>
<new>
<OnStateSet></OnStateSet>
<Events>
<DummyEvent targetState="pending" errorState="error"></DummyEvent>
</Events>
</new>
<pending></pending>
<error></error>
</States>
</Schema>
Custom events can be deined inside Events
Node. There is an option to define targetState
(required) and errorState
which will take effect based on transition result
Task
Node is defined inside Custom Event or Default Event when we want to execute some task on them. If all tasks defined inside event are executed successfully then state will be changed to targetState
otherwise it will be errorState
Every task needs to implement fsml.Task
interface to be accessible by statemachine. check examples/tasks.go
type task struct {}
func (t *task) Name() string {
return "increment"
}
func (t *task) Execute(i interface{}) error {
entity := i.(*item)
entity.count++
return nil
}
......
statemachine.AddTask(&task{})
<new>
<OnStateSet></OnStateSet>
<Events>
<DummyEvent targetState="pending" errorState="error">
<Task>increment</Task>
<Task>increment</Task>
</DummyEvent>
</Events>
</new>
FSML is licensed under Apache License 2.0