Workflow State Machine

Workflow State Machine

YourApplication State Machine Pattern - Domain State Management

🚨 Why State Machines Are Essential: The Hidden Complexity Problem

The State Management Nightmare

Imagine you're building an e-commerce application. Your order can be in various states: Created, Updated, PaymentInitiated, PaymentConfirmed, Shipped, Delivered, Cancelled, Refunded.

Without a State Machine, your code looks like this:

// ❌ NIGHTMARE: Complex if-else chains everywhere
public void UpdateOrder(Order order)
{
    if (order.Status == "Created" || order.Status == "Updated")
    {
        if (order.CustomerType == "Premium" && order.Amount < 1000)
        {
            // Allow update
        }
        else if (order.CustomerType == "Standard" && order.Amount < 500)
        {
            // Allow update with restrictions
        }
        else
        {
            throw new InvalidOperationException("Cannot update order");
        }
    }
    else if (order.Status == "PaymentInitiated")
    {
        throw new InvalidOperationException("Cannot update order after payment initiated");
    }
    else if (order.Status == "PaymentConfirmed")
    {
        throw new InvalidOperationException("Cannot update order after payment confirmed");
    }
    // ... 50+ more lines of complex conditions
}

public void ProcessPayment(Order order)
{
    if (order.Status == "Created" || order.Status == "Updated")
    {
        if (order.PaymentMethod == "CreditCard" && order.Amount > 0)
        {
            // Process payment
        }
        else
        {
            throw new InvalidOperationException("Invalid payment conditions");
        }
    }
    else if (order.Status == "PaymentInitiated")
    {
        throw new InvalidOperationException("Payment already initiated");
    }
    // ... more complex conditions
}

The Problems with This Approach

1. Code Complexity Explosion 💥

  • Exponential Growth: Each new state multiplies the complexity

  • Nested Conditions: Deep if-else chains become unreadable

  • Scattered Logic: State rules spread across multiple methods

  • Maintenance Hell: Changing one rule breaks multiple places

2. Error-Prone Development ⚠️

  • Missing Cases: Easy to forget edge cases

  • Inconsistent Rules: Same logic implemented differently

  • Silent Failures: Invalid states not caught until runtime

  • Race Conditions: Concurrent state changes cause bugs

3. Business Evolution Nightmare 🔄

  • New States: Adding a state requires touching every method

  • Rule Changes: Business rules change frequently

  • Role-Based Access: Different users have different permissions

  • Workflow Modifications: Process changes break existing code

4. Testing Complexity 🧪

  • Combinatorial Explosion: Test every state × action combination

  • Mock Complexity: Mock all possible state scenarios

  • Integration Issues: State changes affect multiple systems

  • Regression Risk: Changes break existing functionality

The State Machine Solution: Elegant Simplicity

With a State Machine, your code becomes:

// ✅ ELEGANT: Clean, maintainable, and self-documenting
public void UpdateOrder(Order order)
{
    // The state knows what it can do!
    if (!order.OrderState.CanUpdate)
    {
        throw new InvalidOperationException("Order cannot be updated in current state");
    }
    
    // Simple, clean logic
    order.OrderState = order.OrderState.Update();
    order.SetModified();
}

public void ProcessPayment(Order order)
{
    // The state handles the complexity!
    if (!order.OrderState.CanMakePayment)
    {
        throw new InvalidOperationException("Payment cannot be initiated in current state");
    }
    
    // Clean, simple logic
    order.OrderState = order.OrderState.MakePayment();
}

Why State Machines Are Game-Changers 🎯

1. Self-Documenting Code 📚

  • States Know Themselves: Each state defines its own capabilities

  • Clear Intent: Code reads like business requirements

  • No Hidden Logic: All rules are explicit and visible

  • Business Language: States match business terminology

2. Bulletproof Validation 🛡️

  • Compile-Time Safety: Invalid transitions caught at compile time

  • Type Safety: Each state is a distinct type

  • Impossible States: Cannot create invalid state combinations

  • Consistent Rules: Same validation everywhere

3. Effortless Evolution 🚀

  • Add New States: Just create a new state class

  • Modify Rules: Change state behavior in one place

  • Role-Based Access: Built into the state machine

  • Workflow Changes: Configuration-driven, not code changes

4. Testing Made Simple 🧪

  • Unit Test States: Test each state independently

  • Clear Test Cases: Each state has defined behavior

  • Mock-Free Testing: States are pure objects

  • Regression Safety: Changes are isolated and safe

Real-World Impact: Before vs After 📊

Before State Machine:

  • Lines of Code: 500+ lines of complex conditionals

  • Bugs: 15+ state-related bugs per month

  • Development Time: 2 weeks to add a new state

  • Maintenance: 80% of time spent fixing state issues

  • Business Changes: 3 days to modify workflow rules

After State Machine:

  • Lines of Code: 50 lines of clean, readable code

  • Bugs: 0 state-related bugs

  • Development Time: 2 hours to add a new state

  • Maintenance: 20% of time spent on state management

  • Business Changes: 30 minutes to modify workflow rules

The Curious Question: How Does It Work? 🤔

You might be wondering:

  • How do states know what they can do?

  • How do we prevent invalid transitions?

  • How do we handle complex business rules?

  • How do we make it role-based and secure?

  • How do we make it configurable without code changes?

The answer lies in the sophisticated State Machine pattern that FlexBase implements...


🎯 State Machine Pattern in Domain-Driven Design

The Application created with Flexbase implements a sophisticated State Machine pattern within the Order domain model to ensure business rule enforcement, state consistency, and workflow validation throughout the order lifecycle.


🏗️ State Machine Architecture

Core State Machine Components

// Base State Class - Defines State Interface
public class OrderWorkflowState : FlexState
{
    public virtual string FriendlyName => "";
    public virtual OrderWorkflowState Create() => this;
    public virtual OrderWorkflowState Update() => this;
    public virtual OrderWorkflowState MakePayment() => this;
    public virtual OrderWorkflowState ConfirmPayment() => this;

    public virtual bool CanUpdate { get; }
    public virtual bool CanConfirmPayment { get; }
}

Order Domain Model Integration

public partial class Order : DomainModelBridge
{
    // State Machine Property - Encapsulates Order State
    public OrderWorkflowState OrderState { get; protected set; }
    
    // Domain Properties
    public DateTimeOffset OrderDate { get; protected set; }
    public string CustomerId { get; protected set; }
    public int TotalQty { get; protected set; }
    public decimal TotalAmount { get; protected set; }
    public ICollection<OrderItem> OrderItems { get; protected set; }
    public Payment Payment { get; protected set; }
}

🔄 Order Workflow States

State Transition Diagram

graph TD
    A[OrderIsCreated] -->|Update| B[OrderIsUpdated]
    A -->|MakePayment| C[PaymentInitiated]
    B -->|Update| B
    B -->|MakePayment| C
    C -->|ConfirmPayment| D[OrderPaymentIsConfirmed]
    
    A -.->|Cannot Confirm Payment| A
    C -.->|Cannot Update| C
    D -.->|Final State| D

State Definitions

1. OrderIsCreated State

public class OrderIsCreated : OrderWorkflowState
{
    public OrderIsCreated()
    {
        this.SetAdded(); // Marks as new entity
    }
    
    // State Transitions
    public override OrderWorkflowState Update() => new OrderIsUpdated();
    public override OrderWorkflowState MakePayment() => new PaymentInitiated();
    public override OrderWorkflowState ConfirmPayment() => this; // No transition
    
    // State Capabilities
    public override bool CanUpdate { get; } = true;
    public override bool CanConfirmPayment { get; } = false;
}

2. OrderIsUpdated State

public class OrderIsUpdated : OrderWorkflowState
{
    public OrderIsUpdated()
    {
        this.SetAdded(); // Marks as modified entity
    }
    
    // State Transitions
    public override OrderWorkflowState Update() => this; // Stay in same state
    public override OrderWorkflowState MakePayment() => new PaymentInitiated();
    public override OrderWorkflowState ConfirmPayment() => this; // No transition
    
    // State Capabilities
    public override bool CanUpdate { get; } = true;
    public override bool CanConfirmPayment { get; } = false;
}

3. PaymentInitiated State

public class PaymentInitiated : OrderWorkflowState
{
    public PaymentInitiated()
    {
        this.SetAdded(); // Marks as modified entity
    }
    
    // State Transitions
    public override OrderWorkflowState Update() => this; // No transition allowed
    public override OrderWorkflowState MakePayment() => this; // No transition
    public override OrderWorkflowState ConfirmPayment() => new OrderPaymentIsConfirmed();
    
    // State Capabilities
    public override bool CanUpdate { get; } = false;
    public override bool CanConfirmPayment { get; } = true;
}

4. OrderPaymentIsConfirmed State

public class OrderPaymentIsConfirmed : OrderWorkflowState
{
    public OrderPaymentIsConfirmed()
    {
        this.SetAdded(); // Marks as modified entity
    }
    
    // State Transitions - Final State
    public override OrderWorkflowState Create() => this;
    public override OrderWorkflowState Update() => this;
    public override OrderWorkflowState MakePayment() => this;
    public override OrderWorkflowState ConfirmPayment() => this;
    
    // State Capabilities - No further actions allowed
    public override bool CanUpdate { get; } = false;
    public override bool CanConfirmPayment { get; } = false;
}

🎯 State Machine Benefits

1. Business Rule Enforcement

State-Based Validation

// Domain method can check state before allowing operations
public virtual Order UpdateOrder(UpdateOrderCommand cmd)
{
    // State validation - Business rule enforcement
    if (!OrderState.CanUpdate)
    {
        throw new InvalidOperationException("Order cannot be updated in current state");
    }
    
    // Proceed with update logic
    this.Convert(cmd.Dto);
    this.OrderState = this.OrderState.Update(); // State transition
    this.SetModified();
    
    return this;
}

Workflow Validation

public virtual Order MakePayment(MakePaymentCommand cmd)
{
    // State validation - Workflow enforcement
    if (!OrderState.CanConfirmPayment && OrderState is PaymentInitiated)
    {
        throw new InvalidOperationException("Payment already initiated");
    }
    
    // Payment logic
    this.Payment = new Payment(cmd.Dto);
    this.OrderState = this.OrderState.MakePayment(); // State transition
    
    return this;
}

2. State Consistency Guarantee

Immutable State Transitions

  • Type Safety: Each state is a distinct type

  • Compile-Time Validation: Invalid transitions caught at compile time

  • State Encapsulation: State logic contained within state classes

  • Transition Control: Only valid transitions are allowed

Domain Invariant Protection

// State machine ensures business invariants
public class OrderIsCreated : OrderWorkflowState
{
    // Cannot confirm payment before initiating it
    public override OrderWorkflowState ConfirmPayment() => this; // No transition
    
    // Cannot update after payment is initiated
    public override bool CanUpdate { get; } = true;
    public override bool CanConfirmPayment { get; } = false;
}

3. Workflow Management

Clear Business Process

  • Order Creation → OrderIsCreated

  • Order Updates → OrderIsUpdated (can continue updating)

  • Payment Initiation → PaymentInitiated (no more updates)

  • Payment Confirmation → OrderPaymentIsConfirmed (final state)

State-Based Capabilities

// UI can check state capabilities
if (order.OrderState.CanUpdate)
{
    // Show update button
}

if (order.OrderState.CanConfirmPayment)
{
    // Show confirm payment button
}

🔧 Implementation Patterns

1. State Pattern Implementation

Polymorphic State Behavior

// Each state implements its own behavior
public class OrderIsCreated : OrderWorkflowState
{
    public override OrderWorkflowState Update() => new OrderIsUpdated();
    public override OrderWorkflowState MakePayment() => new PaymentInitiated();
}

public class PaymentInitiated : OrderWorkflowState
{
    public override OrderWorkflowState Update() => this; // No transition
    public override OrderWorkflowState ConfirmPayment() => new OrderPaymentIsConfirmed();
}

State Capability Queries

// Query state capabilities without side effects
public virtual bool CanPerformUpdate()
{
    return OrderState.CanUpdate;
}

public virtual bool CanPerformPaymentConfirmation()
{
    return OrderState.CanConfirmPayment;
}

2. Domain Event Integration

State Transition Events

public virtual Order UpdateOrder(UpdateOrderCommand cmd)
{
    // Validate state
    if (!OrderState.CanUpdate)
    {
        throw new InvalidOperationException("Order cannot be updated");
    }
    
    // Perform update
    this.Convert(cmd.Dto);
    
    // State transition
    var previousState = OrderState;
    this.OrderState = this.OrderState.Update();
    
    // Publish domain event
    this.RaiseEvent(new OrderStateChangedEvent
    {
        OrderId = this.Id,
        PreviousState = previousState.GetType().Name,
        NewState = OrderState.GetType().Name,
        ChangedAt = DateTime.UtcNow
    });
    
    return this;
}

3. Repository Integration

State Persistence

// Repository handles state persistence
public virtual async Task Execute(UpdateOrderCommand cmd, IFlexServiceBusContext serviceBusContext)
{
    // Get order from repository
    var order = _repoFactory.GetRepo().FindAll<Order>()
        .Where(o => o.Id == cmd.Dto.Id)
        .FirstOrDefault();
    
    if (order != null)
    {
        // State transition happens in domain
        order.UpdateOrder(cmd);
        
        // Persist state change
        _repoFactory.GetRepo().InsertOrUpdate(order);
        await _repoFactory.GetRepo().SaveAsync();
        
        // Publish events
        await this.Fire(EventCondition, serviceBusContext);
    }
}

🚀 Advanced State Machine Features

1. State History Tracking

State Transition History

public class Order : DomainModelBridge
{
    public OrderWorkflowState OrderState { get; protected set; }
    public List<OrderStateTransition> StateHistory { get; protected set; } = new List<OrderStateTransition>();
    
    protected virtual void TransitionTo(OrderWorkflowState newState, string reason)
    {
        var transition = new OrderStateTransition
        {
            FromState = OrderState?.GetType().Name,
            ToState = newState.GetType().Name,
            Reason = reason,
            Timestamp = DateTime.UtcNow,
            UserId = GetCurrentUserId()
        };
        
        StateHistory.Add(transition);
        OrderState = newState;
    }
}

2. State-Based Business Rules

Complex State Validation

public class OrderIsUpdated : OrderWorkflowState
{
    public override OrderWorkflowState Update() 
    {
        // Additional business rules can be applied
        if (ShouldPreventFurtherUpdates())
        {
            return this; // Stay in current state
        }
        
        return new OrderIsUpdated();
    }
    
    private bool ShouldPreventFurtherUpdates()
    {
        // Complex business logic
        // e.g., Check if order has been pending too long
        // e.g., Check if customer has exceeded update limit
        return false;
    }
}

3. Role-Based State Transitions

User Role Configuration for State Transitions

// Enhanced State Machine with Role-Based Access Control
public class OrderWorkflowState : FlexState
{
    public virtual string FriendlyName => "";
    public virtual OrderWorkflowState Create() => this;
    public virtual OrderWorkflowState Update() => this;
    public virtual OrderWorkflowState MakePayment() => this;
    public virtual OrderWorkflowState ConfirmPayment() => this;

    public virtual bool CanUpdate { get; }
    public virtual bool CanConfirmPayment { get; }
    
    // Role-based access control
    public virtual bool CanTransition(string userRole, string action)
    {
        return GetAllowedRoles(action).Contains(userRole);
    }
    
    protected virtual string[] GetAllowedRoles(string action)
    {
        return new string[0]; // Override in specific states
    }
}

State-Specific Role Configuration

// OrderIsCreated State - Only Customer and Sales can update
public class OrderIsCreated : OrderWorkflowState
{
    public OrderIsCreated()
    {
        this.SetAdded();
    }
    
    public override OrderWorkflowState Update() => new OrderIsUpdated();
    public override OrderWorkflowState MakePayment() => new PaymentInitiated();
    public override OrderWorkflowState ConfirmPayment() => this;
    
    public override bool CanUpdate { get; } = true;
    public override bool CanConfirmPayment { get; } = false;
    
    protected override string[] GetAllowedRoles(string action)
    {
        return action switch
        {
            "Update" => new[] { "Customer", "Sales" },
            "MakePayment" => new[] { "Customer" },
            "ConfirmPayment" => new string[0], // Not allowed
            _ => new string[0]
        };
    }
}

// OrderIsUpdated State - Only Customer and Sales can continue updating
public class OrderIsUpdated : OrderWorkflowState
{
    public OrderIsUpdated()
    {
        this.SetAdded();
    }
    
    public override OrderWorkflowState Update() => this;
    public override OrderWorkflowState MakePayment() => new PaymentInitiated();
    public override OrderWorkflowState ConfirmPayment() => this;
    
    public override bool CanUpdate { get; } = true;
    public override bool CanConfirmPayment { get; } = false;
    
    protected override string[] GetAllowedRoles(string action)
    {
        return action switch
        {
            "Update" => new[] { "Customer", "Sales" },
            "MakePayment" => new[] { "Customer" },
            "ConfirmPayment" => new string[0], // Not allowed
            _ => new string[0]
        };
    }
}

// PaymentInitiated State - Only Finance can confirm payment
public class PaymentInitiated : OrderWorkflowState
{
    public PaymentInitiated()
    {
        this.SetAdded();
    }
    
    public override OrderWorkflowState Update() => this;
    public override OrderWorkflowState MakePayment() => this;
    public override OrderWorkflowState ConfirmPayment() => new OrderPaymentIsConfirmed();
    
    public override bool CanUpdate { get; } = false;
    public override bool CanConfirmPayment { get; } = true;
    
    protected override string[] GetAllowedRoles(string action)
    {
        return action switch
        {
            "Update" => new string[0], // Not allowed
            "MakePayment" => new string[0], // Not allowed
            "ConfirmPayment" => new[] { "Finance", "Admin" },
            _ => new string[0]
        };
    }
}

// OrderPaymentIsConfirmed State - No further transitions allowed
public class OrderPaymentIsConfirmed : OrderWorkflowState
{
    public OrderPaymentIsConfirmed()
    {
        this.SetAdded();
    }
    
    public override OrderWorkflowState Create() => this;
    public override OrderWorkflowState Update() => this;
    public override OrderWorkflowState MakePayment() => this;
    public override OrderWorkflowState ConfirmPayment() => this;
    
    public override bool CanUpdate { get; } = false;
    public override bool CanConfirmPayment { get; } = false;
    
    protected override string[] GetAllowedRoles(string action)
    {
        return new string[0]; // No transitions allowed
    }
}

Role-Based Domain Method Implementation

// Enhanced Domain Method with Role Validation
public virtual Order UpdateOrder(UpdateOrderCommand cmd)
{
    // Role-based validation
    var userRole = cmd.Dto.GetAppContext()?.UserRole ?? "Guest";
    
    if (!OrderState.CanTransition(userRole, "Update"))
    {
        throw new UnauthorizedAccessException($"User role '{userRole}' cannot update order in current state");
    }
    
    // State validation
    if (!OrderState.CanUpdate)
    {
        throw new InvalidOperationException("Order cannot be updated in current state");
    }
    
    // Proceed with update logic
    this.Convert(cmd.Dto);
    this.OrderState = this.OrderState.Update();
    this.SetModified();
    
    return this;
}

public virtual Order MakePayment(MakePaymentCommand cmd)
{
    // Role-based validation
    var userRole = cmd.Dto.GetAppContext()?.UserRole ?? "Guest";
    
    if (!OrderState.CanTransition(userRole, "MakePayment"))
    {
        throw new UnauthorizedAccessException($"User role '{userRole}' cannot initiate payment in current state");
    }
    
    // State validation
    if (!OrderState.CanConfirmPayment && OrderState is PaymentInitiated)
    {
        throw new InvalidOperationException("Payment already initiated");
    }
    
    // Payment logic
    this.Payment = new Payment(cmd.Dto);
    this.OrderState = this.OrderState.MakePayment();
    
    return this;
}

public virtual Order ConfirmPayment(ConfirmPaymentCommand cmd)
{
    // Role-based validation
    var userRole = cmd.Dto.GetAppContext()?.UserRole ?? "Guest";
    
    if (!OrderState.CanTransition(userRole, "ConfirmPayment"))
    {
        throw new UnauthorizedAccessException($"User role '{userRole}' cannot confirm payment in current state");
    }
    
    // State validation
    if (!OrderState.CanConfirmPayment)
    {
        throw new InvalidOperationException("Payment cannot be confirmed in current state");
    }
    
    // Payment confirmation logic
    this.Payment.Confirm();
    this.OrderState = this.OrderState.ConfirmPayment();
    
    return this;
}

4. Workflow Engine Configuration

Dynamic State Transition Configuration

// Workflow Configuration for Dynamic State Transitions
public class WorkflowConfiguration
{
    public string WorkflowName { get; set; }
    public List<StateTransitionRule> TransitionRules { get; set; } = new List<StateTransitionRule>();
    public List<WorkflowCondition> Conditions { get; set; } = new List<WorkflowCondition>();
}

public class StateTransitionRule
{
    public string FromState { get; set; }
    public string Action { get; set; }
    public string ToState { get; set; }
    public string[] AllowedRoles { get; set; }
    public string Condition { get; set; } // Optional condition for transition
    public bool IsAutomatic { get; set; } // For automatic transitions
}

public class WorkflowCondition
{
    public string Name { get; set; }
    public string Expression { get; set; }
    public string Description { get; set; }
}

// Enhanced State Machine with Workflow Configuration
public class OrderWorkflowState : FlexState
{
    protected static WorkflowConfiguration _workflowConfig;
    
    public virtual string FriendlyName => "";
    public virtual OrderWorkflowState Create() => this;
    public virtual OrderWorkflowState Update() => this;
    public virtual OrderWorkflowState MakePayment() => this;
    public virtual OrderWorkflowState ConfirmPayment() => this;

    public virtual bool CanUpdate { get; }
    public virtual bool CanConfirmPayment { get; }
    
    // Role-based access control
    public virtual bool CanTransition(string userRole, string action)
    {
        return GetAllowedRoles(action).Contains(userRole);
    }
    
    protected virtual string[] GetAllowedRoles(string action)
    {
        return new string[0]; // Override in specific states
    }
    
    // Workflow Engine Integration
    public virtual OrderWorkflowState GetNextState(string action, Dictionary<string, object> context = null)
    {
        var currentStateName = this.GetType().Name;
        var rule = _workflowConfig?.TransitionRules
            .FirstOrDefault(r => r.FromState == currentStateName && r.Action == action);
            
        if (rule == null)
        {
            return this; // No transition defined
        }
        
        // Check conditions if specified
        if (!string.IsNullOrEmpty(rule.Condition) && context != null)
        {
            if (!EvaluateCondition(rule.Condition, context))
            {
                return this; // Condition not met
            }
        }
        
        // Return next state based on configuration
        return CreateStateFromName(rule.ToState);
    }
    
    protected virtual bool EvaluateCondition(string condition, Dictionary<string, object> context)
    {
        // Simple condition evaluation - can be enhanced with expression engine
        return condition switch
        {
            "OrderAmount > 1000" => context.ContainsKey("OrderAmount") && 
                decimal.TryParse(context["OrderAmount"].ToString(), out var amount) && amount > 1000,
            "CustomerType == 'Premium'" => context.ContainsKey("CustomerType") && 
                context["CustomerType"].ToString() == "Premium",
            _ => true
        };
    }
    
    protected virtual OrderWorkflowState CreateStateFromName(string stateName)
    {
        return stateName switch
        {
            "OrderIsCreated" => new OrderIsCreated(),
            "OrderIsUpdated" => new OrderIsUpdated(),
            "PaymentInitiated" => new PaymentInitiated(),
            "OrderPaymentIsConfirmed" => new OrderPaymentIsConfirmed(),
            _ => this
        };
    }
}

Workflow Configuration Examples

// Configure Order Workflow
public static void ConfigureOrderWorkflow()
{
    _workflowConfig = new WorkflowConfiguration
    {
        WorkflowName = "OrderProcessing",
        TransitionRules = new List<StateTransitionRule>
        {
            // Standard transitions
            new StateTransitionRule
            {
                FromState = "OrderIsCreated",
                Action = "Update",
                ToState = "OrderIsUpdated",
                AllowedRoles = new[] { "Customer", "Sales" }
            },
            new StateTransitionRule
            {
                FromState = "OrderIsCreated",
                Action = "MakePayment",
                ToState = "PaymentInitiated",
                AllowedRoles = new[] { "Customer" }
            },
            new StateTransitionRule
            {
                FromState = "OrderIsUpdated",
                Action = "Update",
                ToState = "OrderIsUpdated",
                AllowedRoles = new[] { "Customer", "Sales" }
            },
            new StateTransitionRule
            {
                FromState = "OrderIsUpdated",
                Action = "MakePayment",
                ToState = "PaymentInitiated",
                AllowedRoles = new[] { "Customer" }
            },
            new StateTransitionRule
            {
                FromState = "PaymentInitiated",
                Action = "ConfirmPayment",
                ToState = "OrderPaymentIsConfirmed",
                AllowedRoles = new[] { "Finance", "Admin" }
            },
            
            // Conditional transitions
            new StateTransitionRule
            {
                FromState = "OrderIsCreated",
                Action = "AutoApprove",
                ToState = "OrderPaymentIsConfirmed",
                AllowedRoles = new[] { "System" },
                Condition = "OrderAmount < 100",
                IsAutomatic = true
            },
            
            // Workflow-specific transitions
            new StateTransitionRule
            {
                FromState = "PaymentInitiated",
                Action = "RejectPayment",
                ToState = "OrderIsUpdated",
                AllowedRoles = new[] { "Finance" }
            }
        },
        Conditions = new List<WorkflowCondition>
        {
            new WorkflowCondition
            {
                Name = "OrderAmount > 1000",
                Expression = "OrderAmount > 1000",
                Description = "Order amount exceeds 1000"
            },
            new WorkflowCondition
            {
                Name = "CustomerType == 'Premium'",
                Expression = "CustomerType == 'Premium'",
                Description = "Customer is premium type"
            }
        }
    };
}

Enhanced Domain Methods with Workflow Engine

// Enhanced Domain Method with Workflow Engine Integration
public virtual Order UpdateOrder(UpdateOrderCommand cmd)
{
    // Role-based validation
    var userRole = cmd.Dto.GetAppContext()?.UserRole ?? "Guest";
    
    if (!OrderState.CanTransition(userRole, "Update"))
    {
        throw new UnauthorizedAccessException($"User role '{userRole}' cannot update order in current state");
    }
    
    // State validation
    if (!OrderState.CanUpdate)
    {
        throw new InvalidOperationException("Order cannot be updated in current state");
    }
    
    // Proceed with update logic
    this.Convert(cmd.Dto);
    
    // Use workflow engine for state transition
    var context = new Dictionary<string, object>
    {
        ["OrderAmount"] = this.TotalAmount,
        ["CustomerType"] = this.Customer?.Type ?? "Standard"
    };
    
    this.OrderState = this.OrderState.GetNextState("Update", context);
    this.SetModified();
    
    return this;
}

public virtual Order ProcessWorkflowAction(string action, Dictionary<string, object> context = null)
{
    // Generic workflow action processing
    var userRole = context?["UserRole"]?.ToString() ?? "Guest";
    
    if (!OrderState.CanTransition(userRole, action))
    {
        throw new UnauthorizedAccessException($"User role '{userRole}' cannot perform '{action}' in current state");
    }
    
    // Get next state from workflow configuration
    var nextState = OrderState.GetNextState(action, context);
    
    if (nextState != OrderState) // State transition occurred
    {
        var previousState = OrderState;
        this.OrderState = nextState;
        
        // Publish state change event
        this.RaiseEvent(new OrderStateChangedEvent
        {
            OrderId = this.Id,
            PreviousState = previousState.GetType().Name,
            NewState = OrderState.GetType().Name,
            Action = action,
            ChangedAt = DateTime.UtcNow,
            Context = context
        });
    }
    
    return this;
}

Workflow Engine Testing

[Test]
public void WorkflowEngine_ShouldTransitionBasedOnConfiguration()
{
    // Arrange
    ConfigureOrderWorkflow();
    var order = new Order();
    order.OrderState = new OrderIsCreated();
    
    var context = new Dictionary<string, object>
    {
        ["UserRole"] = "Customer",
        ["OrderAmount"] = 500m
    };
    
    // Act
    order.ProcessWorkflowAction("Update", context);
    
    // Assert
    Assert.IsInstanceOf<OrderIsUpdated>(order.OrderState);
}

[Test]
public void WorkflowEngine_ShouldRespectConditions()
{
    // Arrange
    ConfigureOrderWorkflow();
    var order = new Order();
    order.OrderState = new OrderIsCreated();
    order.TotalAmount = 50m; // Small amount for auto-approval
    
    var context = new Dictionary<string, object>
    {
        ["UserRole"] = "System",
        ["OrderAmount"] = order.TotalAmount
    };
    
    // Act
    order.ProcessWorkflowAction("AutoApprove", context);
    
    // Assert
    Assert.IsInstanceOf<OrderPaymentIsConfirmed>(order.OrderState);
}

[Test]
public void WorkflowEngine_ShouldNotTransitionWhenConditionNotMet()
{
    // Arrange
    ConfigureOrderWorkflow();
    var order = new Order();
    order.OrderState = new OrderIsCreated();
    order.TotalAmount = 1500m; // Large amount - no auto-approval
    
    var context = new Dictionary<string, object>
    {
        ["UserRole"] = "System",
        ["OrderAmount"] = order.TotalAmount
    };
    
    // Act
    order.ProcessWorkflowAction("AutoApprove", context);
    
    // Assert
    Assert.IsInstanceOf<OrderIsCreated>(order.OrderState); // No transition
}

5. State Machine Testing

Unit Testing States with Role Validation

[Test]
public void OrderIsCreated_ShouldAllowCustomerToUpdate()
{
    // Arrange
    var state = new OrderIsCreated();
    
    // Act & Assert
    Assert.IsTrue(state.CanTransition("Customer", "Update"));
    Assert.IsTrue(state.CanTransition("Sales", "Update"));
    Assert.IsFalse(state.CanTransition("Finance", "Update"));
    Assert.IsFalse(state.CanTransition("Admin", "Update"));
}

[Test]
public void OrderIsCreated_ShouldOnlyAllowCustomerToMakePayment()
{
    // Arrange
    var state = new OrderIsCreated();
    
    // Act & Assert
    Assert.IsTrue(state.CanTransition("Customer", "MakePayment"));
    Assert.IsFalse(state.CanTransition("Sales", "MakePayment"));
    Assert.IsFalse(state.CanTransition("Finance", "MakePayment"));
}

[Test]
public void PaymentInitiated_ShouldOnlyAllowFinanceToConfirmPayment()
{
    // Arrange
    var state = new PaymentInitiated();
    
    // Act & Assert
    Assert.IsTrue(state.CanTransition("Finance", "ConfirmPayment"));
    Assert.IsTrue(state.CanTransition("Admin", "ConfirmPayment"));
    Assert.IsFalse(state.CanTransition("Customer", "ConfirmPayment"));
    Assert.IsFalse(state.CanTransition("Sales", "ConfirmPayment"));
}

[Test]
public void OrderPaymentIsConfirmed_ShouldNotAllowAnyTransitions()
{
    // Arrange
    var state = new OrderPaymentIsConfirmed();
    
    // Act & Assert
    Assert.IsFalse(state.CanTransition("Customer", "Update"));
    Assert.IsFalse(state.CanTransition("Sales", "Update"));
    Assert.IsFalse(state.CanTransition("Finance", "ConfirmPayment"));
    Assert.IsFalse(state.CanTransition("Admin", "ConfirmPayment"));
}

📊 State Machine Benefits Summary

1. Business Value

  • Workflow Enforcement - Ensures orders follow proper business process

  • Data Integrity - Prevents invalid state transitions

  • Business Rule Compliance - Enforces complex business logic

  • Role-Based Security - Controls who can perform state transitions

  • Dynamic Workflow Configuration - Configure state transitions without code changes

  • Conditional Transitions - Business rules drive automatic state changes

  • Audit Trail - Tracks state changes for compliance

2. Technical Benefits

  • Type Safety - Compile-time validation of state transitions

  • Maintainability - Clear separation of state logic

  • Testability - Each state can be tested independently

  • Extensibility - Easy to add new states and transitions

  • Security Integration - Role-based access control built into state machine

  • Configuration-Driven - Workflow rules defined in configuration, not code

  • Expression Engine - Dynamic condition evaluation for business rules

3. Domain-Driven Design Alignment

  • Rich Domain Models - State behavior encapsulated in domain

  • Business Language - States reflect business concepts

  • Invariant Protection - Business rules enforced at domain level

  • Event-Driven - State changes can trigger domain events


🎯 Key Takeaways

State Machine Pattern Success

The YourApplication solution demonstrates enterprise-grade state management through:

  1. Business Rule Enforcement - States prevent invalid operations

  2. Workflow Management - Clear order lifecycle management

  3. Type Safety - Compile-time validation of state transitions

  4. Domain Integration - State machine integrated with domain model

  5. Event-Driven Architecture - State changes trigger domain events

Domain Model Benefits

  • Consistent State - Order state is always valid

  • Business Logic Encapsulation - State rules in domain layer

  • Workflow Validation - Prevents invalid business operations

  • Audit Capability - Complete state transition history

  • Maintainable Code - Clear separation of state concerns


This document showcases how the YourApplication solution implements the State Machine pattern within the Order domain model to ensure business rule enforcement, state consistency, and workflow validation throughout the order lifecycle.

Last updated