Optima.Net.StateMachines
2.0.0
Prefix Reserved
See the version list below for details.
dotnet add package Optima.Net.StateMachines --version 2.0.0
NuGet\Install-Package Optima.Net.StateMachines -Version 2.0.0
<PackageReference Include="Optima.Net.StateMachines" Version="2.0.0" />
<PackageVersion Include="Optima.Net.StateMachines" Version="2.0.0" />
<PackageReference Include="Optima.Net.StateMachines" />
paket add Optima.Net.StateMachines --version 2.0.0
#r "nuget: Optima.Net.StateMachines, 2.0.0"
#:package Optima.Net.StateMachines@2.0.0
#addin nuget:?package=Optima.Net.StateMachines&version=2.0.0
#tool nuget:?package=Optima.Net.StateMachines&version=2.0.0
Optima.Net.StateMachines
Optima.Net.StateMachines is a lightweight, declarative, and idempotent state machine framework for .NET 8 and later.
It is designed to model event-driven lifecycles in a passive and deterministic way. Undefined transitions are ignored, transitions are defined as data, and the same event applied multiple times will always yield the same state.
Overview
Core Principles:
- Declarative configuration: transitions are defined as data (event → from → to).
- Passive semantics: undefined transitions are ignored, not thrown.
- Idempotent operations: reapplying an event results in the same state.
- Deterministic evaluation: same input produces the same output.
- Domain-agnostic: contains no domain logic, persistence, or infrastructure code.
- Extensible: uses open class-based tokens instead of enums.
Building a Complete Example
The following walkthrough builds a complete working scenario involving:
- Order state machine
- Billing tracker state machine
- Shipping tracker state machine
- Aggregator that combines all of the above
- Console program demonstrating the workflow
Note: Although this scenario uses real-world concepts (orders, billing, shipping), the logic is drastically simplified to illustrate the mechanics of the framework only.
- Define State and Event Tokens
Each machine begins in a default state of New. The machine is constructed in this state automatically. Once it leaves New, it can never return.
public class OrderState : StateToken
{
private OrderState(string code) : base(code) { }
public static readonly OrderState New = new("New");
public static readonly OrderState Created = new("Created");
public static readonly OrderState Paid = new("Paid");
public static readonly OrderState Shipped = new("Shipped");
public static readonly OrderState Completed = new("Completed");
public static readonly OrderState Cancelled = new("Cancelled");
}
public class OrderTransition : TransitionToken
{
private OrderTransition(string code) : base(code) { }
public static readonly OrderTransition Create = new("Create");
public static readonly OrderTransition Pay = new("Pay");
public static readonly OrderTransition Ship = new("Ship");
public static readonly OrderTransition Deliver = new("Deliver");
public static readonly OrderTransition Cancel = new("Cancel");
}
Billing:
public class BillingState : StateToken
{
private BillingState(string code) : base(code) { }
public static readonly BillingState New = new("New");
public static readonly BillingState Pending = new("Pending");
public static readonly BillingState Paid = new("Paid");
public static readonly BillingState Refunded = new("Refunded");
}
public sealed class BillingTransition : TransitionToken
{
private BillingTransition(string code) : base(code) { }
public static readonly BillingTransition Authorize = new("Authorize");
public static readonly BillingTransition Capture = new("Capture");
public static readonly BillingTransition Refund = new("Refund");
}
Shipping:
public class ShippingState : StateToken
{
private ShippingState(string code) : base(code) { }
// Initial state before any action
public static readonly ShippingState New = new("New");
// Preparing order for shipment
public static readonly ShippingState Preparing = new("Preparing");
// Order is packed and waiting for carrier pickup
public static readonly ShippingState Pending = new("Pending");
// Package has been shipped and is in transit
public static readonly ShippingState InTransit = new("InTransit");
// Package is with the delivery agent and out for final delivery
public static readonly ShippingState OutForDelivery = new("OutForDelivery");
// Package delivered successfully to customer
public static readonly ShippingState Delivered = new("Delivered");
}
public class ShippingTransition : TransitionToken
{
private ShippingTransition(string code) : base(code) { }
// Pre-dispatch step: warehouse preparation
public static readonly ShippingTransition PrepareShipping = new("PrepareShipping");
// Warehouse marks package as ready for carrier pickup
public static readonly ShippingTransition MarkReady = new("MarkReady");
// Carrier pickup and transit
public static readonly ShippingTransition Dispatch = new("Dispatch");
// Final delivery leg
public static readonly ShippingTransition OutForDelivery = new("OutForDelivery");
// Completion
public static readonly ShippingTransition Deliver = new("Deliver");
}
- Build State Machines
Order machine:
public OrderStateMachine()
{
var transitions = TransitionMapBuilder<OrderState, OrderTransition>.Create()
.When(OrderTransition.Create).From(OrderState.New).To(OrderState.Created)
.When(OrderTransition.Pay).From(OrderState.Created).To(OrderState.Paid)
.When(OrderTransition.Ship).From(OrderState.Paid).To(OrderState.Shipped)
.When(OrderTransition.Deliver).From(OrderState.Shipped).To(OrderState.Completed)
.When(OrderTransition.Cancel).From(OrderState.Created).To(OrderState.Cancelled)
.Build();
MapRange(transitions);
}
Billing machine:
public BillingTrackerStateMachine()
{
var transitions = TransitionMapBuilder<BillingState, BillingTransition>.Create()
.When(BillingTransition.Authorize).From(BillingState.New).To(BillingState.Pending)
.When(BillingTransition.Capture).From(BillingState.Pending).To(BillingState.Paid)
.When(BillingTransition.Refund).From(BillingState.Paid).To(BillingState.Refunded)
.Build();
MapRange(transitions);
}
Shipping machine:
public ShippingTrackerStateMachine()
{
Map(ShippingTransition.PrepareShipping, ShippingState.New, ShippingState.Preparing);
Map(ShippingTransition.MarkReady, ShippingState.Preparing, ShippingState.Pending);
Map(ShippingTransition.Dispatch, ShippingState.Pending, ShippingState.InTransit);
Map(ShippingTransition.OutForDelivery, ShippingState.InTransit, ShippingState.OutForDelivery);
Map(ShippingTransition.Deliver, ShippingState.OutForDelivery, ShippingState.Delivered);
}
- Define AggregateStatus Extension (Domain Side)
public static class AggregateStatuses
{
public static readonly AggregateStatus Unknown = new AppAggregateStatus("Unknown");
public static readonly AggregateStatus Pending = new AppAggregateStatus("Pending");
public static readonly AggregateStatus Active = new AppAggregateStatus("Active");
public static readonly AggregateStatus Completed = new AppAggregateStatus("Completed");
public static readonly AggregateStatus Failed = new AppAggregateStatus("Failed");
}
/// <summary>
/// Concrete subclass of AggregateStatus for the application.
/// </summary>
internal sealed class AppAggregateStatus : AggregateStatus
{
public AppAggregateStatus(string code) : base(code) { }
}
- Build an Aggregator
public class OrderAggregator : StateAggregator<AggregateStatus>
{
public override AggregateStatus Evaluate(params StateToken[] subStates)
{
// Defensive default
if (subStates == null || subStates.Length == 0)
return AggregateStatus.Get("Pending");
// Any failure across subsystems
if (subStates.Any(s =>
s.Equals(OrderState.Cancelled) ||
s.Equals(BillingState.Refunded)))
return AggregateStatus.Get("Failed");
// All subsystems complete/delivered/paid ? Completed
if (subStates.All(s =>
s.Equals(OrderState.Completed) ||
s.Equals(ShippingState.Delivered) ||
s.Equals(BillingState.Paid)))
return AggregateStatus.Get("Completed");
// At least one subsystem is "active" (order created, payment or shipping started)
if (subStates.Any(s =>
s.Equals(OrderState.Created) ||
s.Equals(OrderState.Paid) ||
s.Equals(OrderState.Shipped) ||
s.Equals(ShippingState.InTransit) ||
s.Equals(ShippingState.OutForDelivery) ||
s.Equals(BillingState.Pending)))
return AggregateStatus.Get("Active");
// Otherwise, still pending (hasn't even been created)
return AggregateStatus.Get("Pending");
}
}
- Console Example (Demonstrating Full Workflow)
var orderMachine = new OrderStateMachine();
var billingMachine = new BillingTrackerStateMachine();
var shippingMachine = new ShippingTrackerStateMachine();
var aggregator = new OrderAggregator();
var orderState = OrderState.New;
var billingState = BillingState.New;
var shippingState = ShippingState.New;
// initialize the aggregate statuses
_ = AggregateStatuses.Unknown;
Console.WriteLine("=== ORDER WORKFLOW ===");
Console.WriteLine($"Initial states: Order={orderState}, Billing={billingState}, Shipping={shippingState}");
Console.WriteLine($"Aggregate Status: {aggregator.Evaluate(orderState, billingState, shippingState).Code}\n");
// --- Order Creation / Authorization / Prepare Shipping ---
orderState = orderMachine.Apply(OrderTransition.Create, orderState);
billingState = billingMachine.Apply(BillingTransition.Authorize, billingState);
shippingState = shippingMachine.Apply(ShippingTransition.PrepareShipping, shippingState);
Console.WriteLine("After creation:");
Console.WriteLine($"Order={orderState}, Billing={billingState}, Shipping={shippingState}");
Console.WriteLine($"Aggregate Status: {aggregator.Evaluate(orderState, billingState, shippingState).Code}\n");
// --- Mark Ready for Dispatch ---
shippingState = shippingMachine.Apply(ShippingTransition.MarkReady, shippingState);
Console.WriteLine("After marking ready for dispatch:");
Console.WriteLine($"Shipping={shippingState}");
Console.WriteLine($"Aggregate Status: {aggregator.Evaluate(orderState, billingState, shippingState).Code}\n");
// --- Payment & Capture ---
orderState = orderMachine.Apply(OrderTransition.Pay, orderState);
billingState = billingMachine.Apply(BillingTransition.Capture, billingState);
Console.WriteLine("After payment:");
Console.WriteLine($"Order={orderState}, Billing={billingState}");
Console.WriteLine($"Aggregate Status: {aggregator.Evaluate(orderState, billingState, shippingState).Code}\n");
// --- Dispatch & Transit ---
shippingState = shippingMachine.Apply(ShippingTransition.Dispatch, shippingState);
orderState = orderMachine.Apply(OrderTransition.Ship, orderState);
Console.WriteLine("After dispatch:");
Console.WriteLine($"Order={orderState}, Shipping={shippingState}");
Console.WriteLine($"Aggregate Status: {aggregator.Evaluate(orderState, billingState, shippingState).Code}\n");
// --- Out for Delivery ---
shippingState = shippingMachine.Apply(ShippingTransition.OutForDelivery, shippingState);
Console.WriteLine("Out for delivery:");
Console.WriteLine($"Shipping={shippingState}");
Console.WriteLine($"Aggregate Status: {aggregator.Evaluate(orderState, billingState, shippingState).Code}\n");
// --- Delivered ---
shippingState = shippingMachine.Apply(ShippingTransition.Deliver, shippingState);
orderState = orderMachine.Apply(OrderTransition.Deliver, orderState);
Console.WriteLine("After delivery:");
Console.WriteLine($"Order={orderState}, Shipping={shippingState}");
Console.WriteLine($"Aggregate Status: {aggregator.Evaluate(orderState, billingState, shippingState).Code}\n");
Example console output:
=== ORDER WORKFLOW ===
Initial states: Order=New, Billing=New, Shipping=New
Aggregate Status: Pending
After creation:
Order=Created, Billing=Pending, Shipping=Preparing
Aggregate Status: Active
After marking ready for dispatch:
Shipping=Pending
Aggregate Status: Active
After payment:
Order=Paid, Billing=Paid
Aggregate Status: Active
After dispatch:
Order=Shipped, Shipping=InTransit
Aggregate Status: Active
Out for delivery:
Shipping=OutForDelivery
Aggregate Status: Active
After delivery:
Order=Completed, Shipping=Delivered
Aggregate Status: Completed
Public API Summary
StateToken Represents an extensible symbolic value for states or events.
Transition<TState, TEvent> Represents a transition rule (Event, From, To).
TransitionMapBuilder<TState, TEvent> Fluent builder for defining transitions.
IStateMachine<TState, TEvent> Defines the passive state machine interface.
- Apply(TEvent event, TState current)
- GetNextStates(TState from)
StateMachine<TState, TEvent> Abstract implementation using internal transition map.
IStateAggregator<TAggregateState> Defines an aggregator interface for computing meta-states.
StateAggregator<TAggregateState> Abstract implementation base class for aggregators.
AggregateStatus (partial) Extensible type for representing aggregate outcomes such as Pending, Active, Completed, Failed.
License
MIT License (c) 2026 Optima Engineering
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
RELEASENOTES.md