Skip to content

State Machines

Mikołaj Milewski edited this page Apr 5, 2025 · 13 revisions

Overview

State Machine is a Behavior type that is build on a concept of UML State Machine.

State Machine consists of:

  1. Unique name that identifies State Machine Class,
  2. Set of States that are representing stable configurations of State Machine (current state),
  3. Set of Transitions that are connecting States and representing the way to change current state of State Machine.

Features

Stateflows State Machine engine supports following features:

States and Transitions

States are representing stable configurations of State Machine instance. Transitions that are connecting States are representing rules that must be followed to change active State: Trigger describing an Event that causes the change, Guard that checks if change can happen and Effect that causes side effect of change.

Internal Transitions

States can define Internal Transitions that can react to incoming Event without changing current States configuration.

Default Transitions

States can define Default Transitions which are automaticaly triggered whenever they are available in current States configuration.

Else Transitions

All kinds of Transitions are supporting else Transitions to enable developers to model if-else or switch-else scenarios of States flow.

Choice and Junction

States flow can also be controlled by using Choice and Junction pseudostates.

Hierarchy

States can be nested via Composite States and Orthogonal States, creating tree-like structure. Current state of State Machine is represented by tree of States that are currently active.

Orthogonality

Orthogonal States have multiple Regions that contain States. States from different Regions can be active at the same time, enabling developers to model independent scenarios within one model - just like CapsLock and NumLock can have independent state in one keyboard.

Fork and Join

States that are orthognal to each other can be entered simultanously using Fork and exited simultanously using Join pseudostates.

Multi-level Transitions

Transitions can connect any two States, regardless of their nesting scope and relation (except for States located in orthogonal Regions).

Scheduling

Transitions can be triggered by Time Events, which are automaticaly dispatched by Stateflows.

Event deferral

Any State can declare to defer an Event of specific type; if such Event will be sent to State Machine instance when given State is active, Event will be stored for future use - and dispatched immediatelly when it will no longer be deferred by any active State.

Exceptions handling

Any Exception thrown by State Machine code is sent to it as an Event and can trigger any Transition.

Behavior embedding

State Machine can embed other Behaviors in its structure, delegating long-running tasks to Actions and Acitivities to be executed outside State Machine instance.

Observability

Developer can register a class that implements IStateMachineObserver or IStateMachineInterceptor interface in order to observe the internal flow of State Machine as well as to influence it.

Versioning

State Machine definition can be versioned, adding new version does not influence instances which are already running.

Redefinition

State Machine definition can be reused and extended by other State Machine.

Definition

Let's consider simple example of State Machine that represents generic business object called 'Document'. It has:

  • two states, 'New' and 'Confirmed',
  • one transition that changes state from 'New' to 'Confirmed', triggered by event 'Confirm'.

This is how it looks in UML:

stateDiagram-v2

[*] --> New
New --> Confirmed : Confirm
Loading

Equivalent Stateflows notation of State Machine can be done directly in Program.cs file:

using Stateflows;
using Stateflows.StateMachines;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddStateflows(b => b
        .AddStateMachine("Document", b => b
            .AddInitialState("New", b => b
                .AddTransition<Confirm>("Confirmed")
            )
            .AddState("Confirmed")
        )
    );

Above example State Machine directly in Program.cs file - it is the simplest way to define it. Yet, for most scenarions it is recommended to define State Machines as separate classes:

using Stateflows.StateMachines;

namespace Example
{
    public class Document : IStateMachine
    {
        public void Build(IStateMachineBuilder builder) => builder
            .AddInitialState("New", b => b
                .AddTransition<Confirm>("Confirmed")
            )
            .AddState("Confirmed");
    }
}

...and then register it in Program.cs file:

using Stateflows;
using Example;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddStateflows(b => b
        .AddStateMachine<Document>()
    );

Type-based definition presented above requires class that implements interface IStateMachine.

Notation structure

Stateflows heavily uses so-called lambda builder pattern to model nested structures. General idea is to have a lambda with fluent interface builder per nesting level.

    // not actual method names ;-)
    .AddContainer(b => b // 'b' stands for 'builder'
        .AddItem(b => b // next level of structure - next lambda builder
            .AddSubitem()
        )
        .AddItem() // lambda builders are often optional in Stateflows
    )

Such approach is used to define Transitions within States, States within Composite States etc.

Interacting with State Machines

State Machine Behavior can be interacted with by sending Events to it. In order to do that, Behavior handle must be located using IStateMachineLocator:

using Stateflows;
using Stateflows.StateMachines;

namespace Example
{
    public class DocumentService(IStateMachineLocator locator) // IStateMachineLocator is available via Dependency Injection
    {
        public Task ConfirmAsync(int documentId)
        {
            if (locator.TryLocateStateMachine(new StateMachineId("Document", documentId.ToString()), out var document))
            {
                await document.SendAsync(new Confirm());
            }
        }
    }
}

Overview
Installation
Behaviors
   State Machines
       Building blocks
           States
               State
               Composite State
               Orthogonal State
               Final State
           Pseudostates
               Choice
               Junction
               Fork
               Join
           Transitions
               Transition
               Default Transition
               Internal Transition
       Concepts
           Evaluation of Transitions
   Activities
       Building blocks
           Nodes
               Action Node
               Decision Node
               Merge Node
               Initial Node
               Final Node
               Input Node
               Output Node
               Fork Node
               Join Node
               Accept Event Action Node
               Send Event Action Node
               Data Store Node
               Structured Activity Node
               Iterative Activity Node
               Parallel Activity Node
           Flows
               Data Flow
               Control Flow
       Concepts
           Implicit fork and join
   Actions

Clone this wiki locally