项目作者: dyrkin

项目描述 :
Finite State Machine for Go inspired by Akka FSM
高级语言: Go
项目地址: git://github.com/dyrkin/fsm.git
创建时间: 2018-11-21T18:17:32Z
项目社区:https://github.com/dyrkin/fsm

开源协议:MIT License

下载


Finite State Machine for Go

Overview

The FSM (Finite State Machine) is best described in the Erlang design principles

A FSM can be described as a set of relations of the form:

State(S) x Event(E) -> Actions (A), State(S’)

These relations are interpreted as meaning:

If we are in state S and the event E occurs, we should perform the actions A and make a transition to the state S’.

A Simple Example

The code is taken from the article Akka Finite State Machine (FSM) and At Most Once Semantics

  1. import (
  2. "fmt"
  3. "github.com/dyrkin/fsm"
  4. )
  5. //states
  6. const InitialState = "Initial"
  7. const AwaitFromState = "AwaitFrom"
  8. const AwaitToState = "AwaitTo"
  9. const DoneState = "Done"
  10. //messages
  11. type Transfer struct {
  12. source chan int
  13. target chan int
  14. amount int
  15. }
  16. const Done = "Done"
  17. const Failed = "Failed"
  18. //data
  19. type WireTransferData struct {
  20. source chan int
  21. target chan int
  22. amount int
  23. client *fsm.FSM
  24. }
  25. func newWireTransfer(transferred chan bool) *fsm.FSM {
  26. wt := fsm.NewFSM()
  27. wt.StartWith(InitialState, nil)
  28. wt.When(InitialState)(
  29. func(event *fsm.Event) *fsm.NextState {
  30. transfer, transferOk := event.Message.(*Transfer)
  31. if transferOk && event.Data == nil {
  32. transfer.source <- transfer.amount
  33. return wt.Goto(AwaitFromState).With(
  34. &WireTransferData{transfer.source, transfer.target, transfer.amount, wt},
  35. )
  36. }
  37. return wt.DefaultHandler()(event)
  38. })
  39. wt.When(AwaitFromState)(
  40. func(event *fsm.Event) *fsm.NextState {
  41. data, dataOk := event.Data.(*WireTransferData)
  42. if dataOk {
  43. switch event.Message {
  44. case Done:
  45. data.target <- data.amount
  46. return wt.Goto(AwaitToState)
  47. case Failed:
  48. go data.client.Send(Failed)
  49. return wt.Stay()
  50. }
  51. }
  52. return wt.DefaultHandler()(event)
  53. })
  54. wt.When(AwaitToState)(
  55. func(event *fsm.Event) *fsm.NextState {
  56. data, dataOk := event.Data.(*WireTransferData)
  57. if dataOk {
  58. switch event.Message {
  59. case Done:
  60. transferred <- true
  61. return wt.Stay()
  62. case Failed:
  63. go data.client.Stay()
  64. }
  65. }
  66. return wt.DefaultHandler()(event)
  67. })
  68. return wt
  69. }

The basic strategy is to instantiate FSM and specifying the possible states:

  • NewFSM() instantiates new FSM
  • StartWith() defines the initial state and initial data
  • Then there is one When(<state>)(<event handler fn>) declaration per state to be handled.

In this case will start in the Initial state with all values uninitialized. The only type of message which can be received in the Initial state is the initial Transfer request at which point a withdraw amount is sent to the source account and the state machine transitions to the AwaitFrom state.

When the system is in the AwaitFrom state the only two messages that can be received are Done or Failure from the source account. If the Done business acknowledgement is received the system will send a deposit amount to the target account and transition to the AwaitTo state.

When the system is in the AwaitTo state the only two messages that can be received are the Done or Failure from the target account.

To run the code above you can use the following code:

  1. func main() {
  2. transferred := make(chan bool)
  3. wireTransfer := newWireTransfer(transferred)
  4. transfer := &Transfer{
  5. source: make(chan int),
  6. target: make(chan int),
  7. amount: 30,
  8. }
  9. source := func() {
  10. withdrawAmount := <-transfer.source
  11. fmt.Printf("Withdrawn from source account: %d\n", withdrawAmount)
  12. wireTransfer.Send(Done)
  13. }
  14. target := func() {
  15. topupAmount := <-transfer.target
  16. fmt.Printf("ToppedUp target account: %d\n", topupAmount)
  17. wireTransfer.Send(Done)
  18. }
  19. go source()
  20. go target()
  21. go wireTransfer.Send(transfer)
  22. if done := <-transferred; !done {
  23. panic("Something went wrong")
  24. }
  25. fmt.Println("DONE")
  26. }

It will produce the following output:

Withdrawn from source account: 30
ToppedUp target account: 30
DONE