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:
Business Rule Enforcement - States prevent invalid operations
Workflow Management - Clear order lifecycle management
Type Safety - Compile-time validation of state transitions
Domain Integration - State machine integrated with domain model
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