Categorygithub.com/stateforward/go-hsm
repositorypackage
1.0.2
Repository: https://github.com/stateforward/go-hsm.git
Documentation: pkg.go.dev

# Packages

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# README

go-hsm PkgGoDev

Warning This package is currently in alpha stage. While it has test coverage, the API is subject to breaking changes between minor versions until we reach v1.0.0. Please pin your dependencies to specific versions.

Package go-hsm provides a powerful hierarchical state machine (HSM) implementation for Go. State machines help manage complex application states and transitions in a clear, maintainable way.

Installation

go get github.com/stateforward/go-hsm

Key Features

  • Hierarchical state organization
  • Entry, exit, and activity actions for states
  • Guard conditions and transition effects
  • Event-driven transitions
  • Time-based transitions
  • Concurrent state execution
  • Event queuing with completion event priority
  • Multiple state machine instances with broadcast support
  • Event completion tracking with Done channels
  • Tracing support for state transitions

Core Concepts

A state machine is a computational model that defines how a system behaves and transitions between different states. Here are key concepts:

  • State: A condition or situation of the system at a specific moment. For example, a traffic light can be in states like "red", "yellow", or "green".
  • Event: A trigger that can cause the system to change states. Events can be external (user actions) or internal (timeouts).
  • Transition: A change from one state to another in response to an event.
  • Guard: A condition that must be true for a transition to occur.
  • Action: Code that executes when entering/exiting states or during transitions.
  • Hierarchical States: States that contain other states, allowing for complex behavior modeling with inheritance.
  • Initial State: The starting state when the machine begins execution.
  • Final State: A state indicating the machine has completed its purpose.

Why Use State Machines?

State machines are particularly useful for:

  • Managing complex application flows
  • Handling user interactions
  • Implementing business processes
  • Controlling system behavior
  • Modeling game logic
  • Managing workflow states

Usage Guide

Basic State Machine Structure

All state machines must embed the hsm.HSM struct and can add their own fields:

type MyHSM struct {
    hsm.HSM // Required embedded struct
    counter int
    status  string
}

Creating and Starting a State Machine

// Define your state machine type
type MyHSM struct {
    hsm.HSM
    counter int
}

// Create the state machine model
model := hsm.Define(
    "example",
    hsm.State("foo"),
    hsm.State("bar"),
    hsm.Transition(
        hsm.Trigger("moveToBar"),
        hsm.Source("foo"),
        hsm.Target("bar")
    ),
    hsm.Initial("foo")
)

// Create and start the state machine
sm := hsm.Start(context.Background(), &MyHSM{}, &model)

// Create event with completion channel
done := make(chan struct{})
event := hsm.Event{
    Name: "moveToBar",
    Done: done,
}

// Dispatch event and wait for completion
sm.Dispatch(event)
<-done

State Actions

States can have three types of actions:

type MyHSM struct {
    hsm.HSM
    status string
}

hsm.State("active",
    // Entry action - runs once when state is entered
    hsm.Entry(func(ctx context.Context, hsm *MyHSM, event hsm.Event) {
        log.Println("Entering active state")
    }),

    // Activity action - long-running operation with context
    hsm.Activity(func(ctx context.Context, hsm *MyHSM, event hsm.Event) {
        for {
            select {
            case <-ctx.Done():
                return
            case <-time.After(time.Second):
                log.Println("Activity tick")
            }
        }
    }),

    // Exit action - runs when leaving the state
    hsm.Exit(func(ctx context.Context, hsm *MyHSM, event hsm.Event) {
        log.Println("Exiting active state")
    })
)

Choice States

Choice pseudo-states allow dynamic branching based on conditions:

type MyHSM struct {
    hsm.HSM
    score int
}

hsm.State("processing",
    hsm.Transition(
        hsm.Trigger("decide"),
        hsm.Target(
            hsm.Choice(
                // First matching guard wins
                hsm.Transition(
                    hsm.Target("approved"),
                    hsm.Guard(func(ctx context.Context, hsm *MyHSM, event hsm.Event) bool {
                        return hsm.score > 700
                    }),
                ),
                // Default transition (no guard)
                hsm.Transition(
                    hsm.Target("rejected")
                ),
            ),
        ),
    ),
)

Event Broadcasting

Multiple state machine instances can receive broadcasted events:

type MyHSM struct {
    hsm.HSM
    id string
}

sm1 := hsm.Start(context.Background(), &MyHSM{id: "sm1"}, &model)
sm2 := hsm.Start(context.Background(), &MyHSM{id: "sm2"}, &model)

// Dispatch event to all state machines
hsm.DispatchAll(sm1, hsm.NewEvent("globalEvent"))

Transitions

Transitions define how states change in response to events:

type MyHSM struct {
    hsm.HSM
    data []string
}

hsm.Transition(
    hsm.Trigger("submit"),
    hsm.Source("draft"),
    hsm.Target("review"),
    hsm.Guard(func(ctx context.Context, hsm *MyHSM, event hsm.Event) bool {
        return len(hsm.data) > 0
    }),
    hsm.Effect(func(ctx context.Context, hsm *MyHSM, event hsm.Event) {
        log.Println("Transitioning from draft to review")
    })
)

Hierarchical States

States can be nested to create hierarchical state machines:

type MachineHSM struct {
    hsm.HSM
    status string
}

model := hsm.Model(
    hsm.State("operational",
        hsm.State("idle"),
        hsm.State("running"),
        hsm.Initial("idle"),
        hsm.Transition(
            hsm.Trigger("start"),
            hsm.Source("idle"),
            hsm.Target("running")
        )
    ),
    hsm.State("maintenance"),
    hsm.Initial("operational")
)

Time-Based Transitions

Create transitions that occur after a time delay:

type TimerHSM struct {
    hsm.HSM
    timeout time.Duration
}

hsm.Transition(
    hsm.After(func(ctx context.Context, hsm *TimerHSM) time.Duration {
        return hsm.timeout
    }),
    hsm.Source("active"),
    hsm.Target("timeout")
)

Event Completion Tracking

Track event completion using Done channels:

type ProcessHSM struct {
    hsm.HSM
    result string
}

// Create event with completion channel
done := make(chan struct{})
event := hsm.Event{
    Name: "process",
    Data: payload,
    Done: done,
}

// Dispatch event
sm.Dispatch(event)

// Wait for processing to complete
select {
case <-done:
    log.Println("Event processing completed")
case <-time.After(time.Second):
    log.Println("Timeout waiting for event processing")
}

Tracing Support

Enable tracing for debugging state transitions:

type TracedHSM struct {
    hsm.HSM
    id string
}

// Create tracer
trace := func(ctx context.Context, step string, data ...any) (context.Context, func(...any)) {
    log.Printf("[TRACE] %s: %+v", step, data)
    return ctx, func(...any) {}
}

// Start state machine with tracing
sm := hsm.Start(ctx, &TracedHSM{id: "machine-1"}, &model, hsm.Config{
    Trace: trace,
    Id:    "machine-1",
})

OpenTelemetry Integration

The package's Trace interface can be used to integrate with OpenTelemetry:

type TelemetryHSM struct {
    hsm.HSM
    serviceName string
}

// Example implementation of hsm.Trace interface using OpenTelemetry
func NewOTelTracer(name string) hsm.Trace {
    provider := initTracerProvider(name)
    tracer := provider.Tracer(name)

    return func(ctx context.Context, step string, data ...any) (context.Context, func(...any)) {
        attrs := []attribute.KeyValue{
            attribute.String("step", step),
        }

        ctx, span := tracer.Start(ctx, step, trace.WithAttributes(attrs...))
        return ctx, func(...any) {
            span.End()
        }
    }
}

// Usage with state machine
sm := hsm.Start(ctx, &TelemetryHSM{serviceName: "payment"}, &model, hsm.Config{
    Trace: NewOTelTracer("payment_processor"),
    Id:    "payment-1",
})

Roadmap

Current and planned features:

  • Event-driven transitions
  • Time-based transitions with delays
  • Hierarchical state nesting
  • Entry/exit/activity actions
  • Guard conditions
  • Transition effects
  • Choice pseudo-states
  • Event broadcasting
  • Concurrent activities
  • Scheduled transitions (at specific dates/times)
    hsm.Transition(
        hsm.At(time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC)),
        hsm.Source("active"),
        hsm.Target("expired")
    )
    
  • History support (shallow and deep)
    hsm.State("parent",
        hsm.History(), // Shallow history
        hsm.DeepHistory(), // Deep history
        hsm.State("child1"),
        hsm.State("child2")
    )
    

Learn More

For deeper understanding of state machines:

License

MIT - See LICENSE file

Contributing

Contributions are welcome! Please ensure:

  • Tests are included
  • Code is well documented
  • Changes maintain backward compatibility
  • Signature changes follow the new context+event pattern