Skip to content

oullin/workflow

Repository files navigation

⚡️ Go Workflow Engine

Tests Go Report Card MIT License Go Reference

Workflow is a high-performance, industrial-grade state management library for Go. It provides a robust framework for managing the lifecycle of complex domain objects, from simple linear state machines to sophisticated concurrent workflows based on Petri Net theory.

✨ Highlights

  • 🎯 Dual-Mode Engine: Native support for both strict State Machines (single state) and flexible Workflows (concurrent states).
  • 🪝 Granular Event Lifecycle: Hooks into every transition phase: Guard, Leave, Transition, Enter, Entered, Completed, and Announce.
  • 🔌 Decoupled Persistence: Flexible MarkingStore interface allows you to keep your domain models clean from library logic.
  • 🛠️ Multi-Format Definitions: Configure your workflows using a fluent Go API or declarative YAML/JSON.
  • 🛡️ Production Hardened: Thread-safe registry, $O(1)$ state lookups, and automated reachability validation to prevent deadlocks.
  • 📊 Observability: Built-in Graphviz DOT export for visualization and slog-compatible structured logging.

🚀 Installation

go get github.com/oullin/workflow

🏎️ Quick Start

1. Define your Workflow

Whether it's a simple subscription or a complex multi-step pipeline, defining it is straightforward.

definition, _ := workflow.NewDefinitionBuilder().
    AddPlace("draft").
    AddPlace("payment_pending").
    AddPlace("active").
    SetInitialPlaces("draft").
    AddTransition("collect_payment", []string{"draft"}, []string{"payment_pending"}).
    AddTransition("activate", []string{"payment_pending"}, []string{"active"}).
    Build()

2. Drive your State

// Define your domain object
type Subscription struct { State string }

// 1. Setup the Store (Decoupled state management)
markingStore := &store.SingleState[*Subscription]{
    Getter: func(s *Subscription) string { return s.State },
    Setter: func(s *Subscription, state string) { s.State = state },
}

// 2. Initialize the Engine
// The last argument is an optional event dispatcher. Passing nil will create a default one.
engine, _ := workflow.New("subscription_flow", definition, markingStore, nil)

// 3. Apply Transitions
sub := &Subscription{State: "draft"}
_, err := engine.Apply(sub, "collect_payment", nil)

fmt.Println(sub.State) // Output: payment_pending

For a complete working example with guards, audit trails, and a registry, see the examples/ directory.

State Machine vs Workflow Constructor

  • workflow.New() creates a full Petri Net workflow engine where objects can occupy multiple places simultaneously.
  • workflow.NewStateMachine() creates a strict state machine where objects exist in exactly one place at a time. Use this when your domain object has a single linear state field.

YAML Configuration

Instead of the fluent Go API, you can define workflows declaratively in YAML:

workflows:
  subscription_paid:
    type: state_machine
    initial_marking: draft
    places:
      - name: draft
      - name: payment_pending
      - name: active
    transitions:
      collect_payment:
        from: draft
        to: payment_pending
      activate:
        from: payment_pending
        to: active
        guard: payment_captured && identity_verified

Then load and use it:

definitions, err := config.LoadFile("workflows.yaml")
definition := definitions["subscription_paid"]

engine, err := workflow.NewStateMachine("subscription_paid", definition, markingStore, dispatcher)

🧠 Core Concepts

Workflows vs. State Machines

  • State Machine: An object exists in exactly one place. Ideal for simple status flags.
  • Workflow: Implements Petri Nets where an object can occupy multiple places simultaneously. Perfect for parallel approvals or complex multi-path processes.

The Event Lifecycle

Every transition triggers a meticulously ordered sequence of events, allowing you to inject logic exactly where it's needed:

  1. Guard 🛡️: Can we start? (Perfect for validation/authorization)
  2. Leave 🏃: Cleaning up the old state.
  3. Transition ⚙️: The internal state change is happening.
  4. Enter 🏁: Preparing the new state.
  5. Entered ✅: State is persisted (Post-commit hooks).
  6. Completed 🎉: The entire process is finished.
  7. Announce 📢: What's next? (Triggering subsequent workflows).

Guards & Business Logic

Block transitions dynamically based on your business rules:

dispatcher.OnGuard(workflow.EventNameGuardNamed("blog", "publish"), func(event *events.GuardEvent[*Article]) {
    if event.Subject().Title == "" {
        event.SetBlocked(true, "Title is required to publish")
    }
})

🔋 Advanced Features

📈 Auditing

Track every transition automatically with the built-in audit trail:

trail := &audit.Trail[*Subscription]{}
trail.Attach("subscription_flow", dispatcher)

🖼️ Visualization (Graphviz DOT)

Graphviz DOT is a text-based graph description language. Export your workflows to this format for documentation or debugging:

dumper := &workflow.GraphvizDumper{}
dotOutput := dumper.Dump(definition)

// You can then render this using Graphviz CLI:
// echo "$dotOutput" | dot -Tpng -o workflow.png

🧪 Development & Quality

We maintain high standards for code quality and reliability.

make fmt           # Professional formatting
make lint          # Static analysis
make test-unit     # Comprehensive test suite
make test-coverage # 100% focused on critical paths

🤝 Credits

📜 License

This project is licensed under the MIT License - see the LICENSE file for details.

About

Workflow is a high-performance, industrial-grade state management library for Go.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors