Skip to content

bykof/stateful

Repository files navigation

Welcome to stateful 👋

Travis CI License: MIT

Twitter: michaelbykovski

Create easy state machines with your existing code

Table of Contents

  1. Documentation
  2. Usage
  3. Draw graph
  4. Wildcards
  5. Examples
  6. Credits

Documentation

You can find the documentation here: https://pkg.go.dev/github.com/bykof/stateful?tab=doc

Usage

It is very easy to use stateful. Just create a struct and implement the stateful interface

import "github.com/bykof/stateful"

type (
    MyMachine struct {
        state   stateful.State
        amount  int
    }
	
    AmountParams struct {
        Amount  int
    }
)

func NewMyMachine() MyMachine {
    return MyMachine{
        state:  A,
        amount: 0,
    }
}

// State implement interface stateful 
func (mm MyMachine) State() stateful.State {
    return mm.state
}

//  SetState implement interface stateful
func (mm *MyMachine) SetState(state stateful.State) error {
    mm.state = state
    return nil
}

Declare some proper states:

const (
    A = stateful.DefaultState("A")
    B = stateful.DefaultState("B")
)

Then add some transitions to the machine:

// Declare a transition of you machine and return the new state of the machine. 
func (mm *MyMachine) FromAToB(transitionArguments stateful.TransitionArguments) (stateful.State, error) {
    amountParams, ok := transitionArguments.(AmountParams)
    if !ok {
        return nil, errors.New("could not parse AmountParams")
    }
	
    mm.amount += amountParams.Amount
    return B, nil
} 

func (mm *MyMachine) FromBToA(transitionArguments stateful.TransitionArguments) (stateful.State, error) {
    amountParams, ok := transitionArguments.(AmountParams)
    if !ok {
        return nil, errors.New("could not parse AmountParams")
    }
	
    mm.amount -= amountParams.Amount
    return A, nil
}

// The state machine will check, if you transfer to a proper and defined state in the machine. See below. 
func (mm *MyMachine) FromAToNotExistingC(_ stateful.TransitionArguments) (stateful.State, error) {
	return stateful.DefaultState("C")
}

And now initialize the machine:

myMachine := NewMyMachine()
stateMachine := &stateful.StateMachine{
    StatefulObject: &myMachine,
}

stateMachine.AddTransition(
    // The transition function 
    myMachine.FromAToB,
    // SourceStates
    stateful.States{A},
    // DestinationStates
    stateful.States{B},
)

stateMachine.AddTransition(
    myMachine.FromBToA,
    stateful.States{B},
    stateful.States{A},
)

Everything is done! Now run the machine:

_ := stateMachine.Run(
    // The transition function
    myMachine.FromAToB, 
    // The transition params which will be passed to the transition function
    stateful.TransitionArguments(AmountParams{Amount: 1}),
)

_ = stateMachine.Run(
    myMachine.FromBToA, 
    stateful.TransitionArguments(AmountParams{Amount: 1}),
)

err := stateMachine.Run(
   myMachine.FromBToA, 
   stateful.TransitionArguments(AmountParams{Amount: 1}),
)

// We cannot run the transition "FromBToA" from current state "A"... 
if err != nil {
    log.Fatal(err) // will print: you cannot run FromAToB from state A
}

// We cannot transfer the machine with current transition to returned state "C"
err = stateMachine.Run(
    myMachine.FromAToNotExistingC, 
    stateful.TransitionArguments(nil),
)

if err != nil {
    log.Fatal(err) // will print: you cannot transfer to state C 
}

That's it!

Draw graph

You can draw a graph of your state machine in dot format of graphviz.

Just pass in your created statemachine into the StateMachineGraph.

import "github.com/bykof/stateful/src/statefulGraph"
stateMachineGraph := statefulGraph.StateMachineGraph{StateMachine: *stateMachine}
_ = stateMachineGraph.DrawGraph()

This will print following to the console:

digraph  {
	A->B[ label="FromAToB" ];
	B->A[ label="FromBToA" ];
	A;
	B;
	
}

which is actually this graph:

MyMachine Transition Graph

Wildcards

You can also address wildcards as SourceStates or DestinationStates

stateMachine.AddTransition(
    myMachine.FromBToAllStates,
    stateful.States{B},
    stateful.States{stateful.AllStates},
)

This will give you the opportunity to jump e.g. B to AllStates.

Keep in mind that AllStates creates a lot of complexity and maybe a missbehavior. So use it only if you are knowing what you are doing

Examples

Have a look at the examples: examples

Credits

Thank you calhoun for the sweet gopher image!

Run tests

go test ./...

Author

👤 Michael Bykovski

Show your support

Give a ⭐️ if this project helped you!

📝 License

Copyright © 2019 Michael Bykovski.
This project is MIT licensed.