Simple State Machine Examples
This document provides simple, easy-to-understand examples of state machines using mkunion. These examples are designed to help you grasp the core concepts before moving on to more complex scenarios.
Traffic Light Example
A traffic light is a classic example of a state machine with three states: Red, Yellow, and Green.
Model Definition
example/traffic/model.go
package traffic
//go:tag mkunion:"TrafficState,no-type-registry"
type (
RedLight struct{}
YellowLight struct{}
GreenLight struct{}
)
//go:tag mkunion:"TrafficCommand,no-type-registry"
type (
NextCMD struct{} // Move to next state in sequence
)
// Simple traffic light with no dependencies
type Dependencies struct{}
Transition Function
example/traffic/traffic_light.go
// Transition defines the traffic light state transitions
func Transition(ctx context.Context, deps Dependencies, cmd TrafficCommand, state TrafficState) (TrafficState, error) {
return MatchTrafficCommandR2(cmd,
func(c *NextCMD) (TrafficState, error) {
return MatchTrafficStateR2(state,
func(s *RedLight) (TrafficState, error) {
return &GreenLight{}, nil
},
func(s *YellowLight) (TrafficState, error) {
return &RedLight{}, nil
},
func(s *GreenLight) (TrafficState, error) {
return &YellowLight{}, nil
},
)
},
)
}
Testing
example/traffic/traffic_light_test.go
func TestTrafficLightTransitions(t *testing.T) {
deps := Dependencies{}
ctx := context.Background()
// Test red -> green transition
state, err := Transition(ctx, deps, &NextCMD{}, &RedLight{})
assert.NoError(t, err)
assert.IsType(t, &GreenLight{}, state)
// Test green -> yellow transition
state, err = Transition(ctx, deps, &NextCMD{}, &GreenLight{})
assert.NoError(t, err)
assert.IsType(t, &YellowLight{}, state)
// Test yellow -> red transition
state, err = Transition(ctx, deps, &NextCMD{}, &YellowLight{})
assert.NoError(t, err)
assert.IsType(t, &RedLight{}, state)
}
Complete Test Suite
example/traffic/traffic_light_test.go
func TestTrafficLightMachine(t *testing.T) {
suite := machine.NewTestSuite[Dependencies](Dependencies{}, NewMachine)
suite.Case(t, "traffic light cycle", func(t *testing.T, c *machine.Case[Dependencies, TrafficCommand, TrafficState]) {
// Start with a red light (default)
c.
GivenCommand(&NextCMD{}).
ThenState(t, &GreenLight{}).
ForkCase(t, "continue cycle", func(t *testing.T, c *machine.Case[Dependencies, TrafficCommand, TrafficState]) {
c.
GivenCommand(&NextCMD{}).
ThenState(t, &YellowLight{}).
ForkCase(t, "complete cycle", func(t *testing.T, c *machine.Case[Dependencies, TrafficCommand, TrafficState]) {
c.
GivenCommand(&NextCMD{}).
ThenState(t, &RedLight{})
})
})
})
// Generate state diagrams
if suite.AssertSelfDocumentStateDiagram(t, "traffic_light_test.go") {
suite.SelfDocumentStateDiagram(t, "traffic_light_test.go")
}
}
Example Usage
The traffic light state machine can be used in applications:
example/traffic/traffic_light.go
// Example demonstrates using the traffic light state machine
func Example() {
// Create a new traffic light starting at red
m := NewMachine(Dependencies{}, &RedLight{})
// Cycle through the lights
ctx := context.Background()
for i := 0; i < 6; i++ {
state := m.State()
fmt.Printf("Current light: %T\n", state)
err := m.Handle(ctx, &NextCMD{})
if err != nil {
fmt.Printf("Error: %v\n", err)
break
}
}
// Output:
// Current light: *traffic.RedLight
// Current light: *traffic.GreenLight
// Current light: *traffic.YellowLight
// Current light: *traffic.RedLight
// Current light: *traffic.GreenLight
// Current light: *traffic.YellowLight
}
Key Concepts Demonstrated
The traffic light example illustrates fundamental state machine concepts:
- States without data: Pure states that represent distinct conditions
- Simple transitions: Clear, predictable state changes in response to commands
- Exhaustive matching: Generated match functions ensure all states are handled
- Dependency injection: Even simple examples follow the pattern for consistency
- Testability: Easy to test with mkunion's testing framework
Next Steps
- Review the comprehensive Order Service example for a more complex scenario
- Learn about testing strategies for state machines
- Explore advanced patterns for composition and async operations