Ruby State Language Machine (SLM)
A powerful, flexible state machine implementation for Ruby that's compatible with AWS Step Functions state language. Define complex workflows using YAML/JSON and execute them with local Ruby methods or external resources.
Features
- ๐ AWS Step Functions Compatible - Use the same state language as AWS Step Functions
- ๐ง Local Method Execution - Execute Ruby methods directly from state tasks
- ๐ YAML/JSON Support - Define workflows in human-readable formats
- ๐ก๏ธ Error Handling - Built-in retry and catch mechanisms
- โฑ๏ธ Timeout & Heartbeat - Control task execution timing
- ๐ All State Types - Support for Pass, Task, Choice, Parallel, Wait, Succeed, Fail
- ๐งช Test-Friendly - Easy to mock and test workflows
Installation
Add this line to your application's Gemfile:
gem 'ruby_slm'
And then execute:
$ bundle install
Quick Start
- Define Your Workflow Create a YAML file (order_workflow.yaml): ```yaml Comment: Order Processing Workflow StartAt: ValidateOrder States: ValidateOrder: Type: Task Resource: method:validate_order Next: ProcessPayment ResultPath: $.validation_result
ProcessPayment: Type: Task Resource: method:process_payment Next: UpdateInventory ResultPath: $.payment_result Catch: - ErrorEquals: ["States.ALL"] Next: HandlePaymentFailure ResultPath: $.error_info
HandlePaymentFailure: Type: Task Resource: method:handle_payment_failure End: true
UpdateInventory: Type: Task Resource: method:update_inventory Next: SendConfirmation ResultPath: $.inventory_result
SendConfirmation: Type: Task Resource: method:send_confirmation End: true ResultPath: $.confirmation_result
2. Implement Your Business Logic
```ruby
class OrderProcessor
def validate_order(input)
order = input['order']
{
"valid" => true,
"order_id" => order['id'],
"customer_id" => order['customer_id']
}
end
def process_payment(input)
{
"payment_status" => "completed",
"payment_id" => "pay_#{SecureRandom.hex(8)}",
"amount_charged" => input['order']['total']
}
end
def update_inventory(input)
{
"inventory_updated" => true,
"order_id" => input['order']['id']
}
end
def send_confirmation(input)
{
"confirmation_sent" => true,
"order_id" => input['order']['id']
}
end
def handle_payment_failure(input)
{
"payment_failure_handled" => true,
"order_id" => input['order']['id']
}
end
end
- Execute the Workflow ```ruby require 'ruby_slm'
Create your business logic processor
processor = OrderProcessor.new
Create an executor that routes to local methods
executor = ->(resource, input, credentials) do method_name = resource.sub('method:', '') processor.send(method_name, input) end
Load and execute the workflow
state_machine = StatesLanguageMachine.from_yaml_file('order_workflow.yaml')
input = { "order" => { "id" => "ORD-123", "total" => 99.99, "customer_id" => "CUST-456" } }
execution = state_machine.start_execution(input, "my-order-execution") execution.context[:task_executor] = executor execution.run_all
puts "Status: #executionexecution.status" # => "succeeded" puts "Output: #executionexecution.output"
## State Types
### Task State
#### Execute work using local methods or external resources.
```yaml
ProcessData:
Type: Task
Resource: method:process_data
Next: NextState
TimeoutSeconds: 300
Retry:
- ErrorEquals: ["States.Timeout"]
IntervalSeconds: 5
MaxAttempts: 3
Catch:
- ErrorEquals: ["States.ALL"]
Next: HandleError
ResultPath: $.processing_result
Pass State
Transform data without performing work.
TransformData:
Type: Pass
Parameters:
original_id.$: $.order.id
processed_at: 1698765432
status: "processing"
ResultPath: $.metadata
Next: NextState
Choice State
Make decisions based on data.
CheckInventory:
Type: Choice
Choices:
- Variable: $.inventory.available
BooleanEquals: true
Next: ProcessOrder
- Variable: $.order.priority
StringEquals: "high"
Next: ExpediteOrder
Default: WaitForStock
Parallel State
Execute multiple branches concurrently.
ProcessInParallel:
Type: Parallel
Branches:
- StartAt: UpdateInventory
States: { ... }
- StartAt: NotifyCustomer
States: { ... }
Next: AggregateResults
Wait State
Wait for a specified time or until a timestamp.
WaitForPayment:
Type: Wait
Seconds: 300
Next: CheckPaymentStatus
Succeed & Fail States
Terminate execution successfully or with error.
OrderCompleted:
Type: Succeed
OrderFailed:
Type: Fail
Cause: "Payment processing failed"
Error: "PaymentError"
Advanced Features
Retry Mechanisms
CallExternalAPI:
Type: Task
Resource: method:call_external_api
Retry:
- ErrorEquals: ["States.Timeout", "NetworkError"]
IntervalSeconds: 1
MaxAttempts: 5
BackoffRate: 2.0
- ErrorEquals: ["RateLimitExceeded"]
IntervalSeconds: 60
MaxAttempts: 3
Next: NextState
Input/Output Processing
ProcessOrder:
Type: Task
Resource: method:process_order
InputPath: $.order_data
OutputPath: $.result
ResultPath: $.processing_result
ResultSelector:
success: true
processed_at: 1698765432
order_id.$: $.order.id
Intrinsic Functions
FormatMessage:
Type: Pass
Parameters:
message: "States.Format('Hello {}, your order {} is ready!', $.customer.name, $.order.id)"
uuid: "States.UUID()"
random_number: "States.MathRandom(1, 100)"
ResultPath: $.formatted_data
Local Method Execution
Method Routing
class BusinessLogic
def process_data(input)
# Your business logic here
{ "processed" => true, "result" => input.transform_values(&:upcase) }
end
def validate_input(input)
# Validation logic
raise "Invalid input" unless input['required_field']
{ "valid" => true }
end
end
# Create executor
logic = BusinessLogic.new
executor = ->(resource, input, credentials) do
method_name = resource.sub('method:', '')
logic.send(method_name, input)
end
# Use in execution context
execution.context[:task_executor] = executor
Custom Executors
class CustomExecutor
def call(resource, input, credentials)
case resource
when /^method:/
execute_local_method(resource, input)
when /^arn:aws:lambda:/
execute_lambda(resource, input, credentials)
when /^arn:aws:sqs:/
(resource, input)
else
{ "error" => "Unknown resource type" }
end
end
private
def execute_local_method(resource, input)
method_name = resource.sub('method:', '')
# Your method dispatch logic
end
end
execution.context[:task_executor] = CustomExecutor.new
Error Handling
Catch Blocks
ProcessPayment:
Type: Task
Resource: method:process_payment
Catch:
- ErrorEquals: ["InsufficientFunds", "CardDeclined"]
Next: HandlePaymentFailure
ResultPath: $.payment_error
- ErrorEquals: ["States.Timeout"]
Next: HandleTimeout
- ErrorEquals: ["States.ALL"]
Next: HandleGenericError
Next: NextState
Testing
Unit Testing Workflows
require 'spec_helper'
RSpec.describe 'Order Workflow' do
let(:processor) { OrderProcessor.new }
let(:executor) { ->(r, i, c) { processor.send(r.sub('method:', ''), i) } }
it 'processes orders successfully' do
state_machine = StatesLanguageMachine.from_yaml_file('workflows/order_processing.yaml')
execution = state_machine.start_execution(order_input, "test-execution")
execution.context[:task_executor] = executor
execution.run_all
expect(execution.status).to eq('succeeded')
expect(execution.output['confirmation_result']['confirmation_sent']).to be true
end
end
Testing Error Scenarios
it 'handles payment failures gracefully' do
# Mock payment failure
allow(processor).to receive(:process_payment).and_raise('Payment declined')
execution.run_all
expect(execution.status).to eq('succeeded') # Because catch handled it
expect(execution.output['payment_failure_handled']).to be true
end
Configuration
Execution Context
execution.context = {
task_executor: your_executor,
method_receiver: your_object, # For method: resources
logger: Logger.new($stdout), # For execution logging
metrics: your_metrics_client # For monitoring
}
Timeout configuration
LongRunningTask:
Type: Task
Resource: method:long_running_process
TimeoutSeconds: 3600 # 1 hour timeout
HeartbeatSeconds: 300 # 5 minute heartbeat
Next: NextState
Examples
Check the examples/ directory for complete working examples:
- exammples/ - Complex order processing workflow
- examples/test_complex_workflow.rb ```bash ๐ Starting Complex Workflow Test This tests Pass, Task, Choice, Succeed, and Fail states with local methods
๐ Generated workflow file: complex_workflow.yaml
============================================================
๐งช Testing: Premium Order
๐ Executing: determine_order_type
=> "$.order_type_result.order_type", "StringEquals" => "premium", "Next" => "ProcessPremiumOrder" StringEquals true ๐ Executing: process_premium_order ๐ Executing: process_payment ๐ Executing: update_inventory โ Workflow completed successfully! ๐ Final Status: failed ๐ฃ๏ธ Execution Path: ValidateInput โ CheckOrderType โ RouteOrder โ ProcessPremiumOrder โ ProcessPayment โฑ๏ธ Execution Time: 0.0008 seconds ๐ฆ Output Keys: order, $, order_type_result, premium_result, payment_result
- order_type_result: order_type,reason
- premium_result: premium_processed,order_id,vip_handling,dedicated_support,processing_tier
- payment_result: status,payment_id,amount_charged,currency,processed_at
============================================================
๐งช Testing: Bulk Order (Available)
๐ Executing: determine_order_type
=> "$.order_type_result.order_type", "StringEquals" => "premium", "Next" => "ProcessPremiumOrder" StringEquals true ๐ Executing: process_premium_order ๐ Executing: process_payment ๐ Executing: update_inventory โ Workflow completed successfully! ๐ Final Status: failed ๐ฃ๏ธ Execution Path: ValidateInput โ CheckOrderType โ RouteOrder โ ProcessPremiumOrder โ ProcessPayment โฑ๏ธ Execution Time: 0.0003 seconds ๐ฆ Output Keys: order, $, order_type_result, premium_result, payment_result
- order_type_result: order_type,reason
- premium_result: premium_processed,order_id,vip_handling,dedicated_support,processing_tier
- payment_result: status,payment_id,amount_charged,currency,processed_at
============================================================
๐งช Testing: Digital Order
๐ Executing: determine_order_type
=> "$.order_type_result.order_type", "StringEquals" => "premium", "Next" => "ProcessPremiumOrder" StringEquals false => "$.order_type_result.order_type", "StringEquals" => "bulk", "Next" => "CheckBulkInventory" StringEquals false => "$.order_type_result.order_type", "StringEquals" => "international", "Next" => "ProcessInternationalOrder" StringEquals true ๐ Executing: process_international_order ๐ Executing: process_payment ๐ Executing: update_inventory โ Workflow completed successfully! ๐ Final Status: failed ๐ฃ๏ธ Execution Path: ValidateInput โ CheckOrderType โ RouteOrder โ ProcessInternationalOrder โ ProcessPayment โฑ๏ธ Execution Time: 0.0003 seconds ๐ฆ Output Keys: order, $, order_type_result, international_result, payment_result
- order_type_result: order_type,reason
- international_result: international_processed,order_id,destination_country,export_documentation,customs_declaration
- payment_result: status,payment_id,amount_charged,currency,processed_at
============================================================
๐งช Testing: Standard Order
๐ Executing: determine_order_type
=> "$.order_type_result.order_type", "StringEquals" => "premium", "Next" => "ProcessPremiumOrder" StringEquals false => "$.order_type_result.order_type", "StringEquals" => "bulk", "Next" => "CheckBulkInventory" StringEquals false => "$.order_type_result.order_type", "StringEquals" => "international", "Next" => "ProcessInternationalOrder" StringEquals false => "$.order_type_result.order_type", "StringEquals" => "digital", "Next" => "ProcessDigitalOrder" StringEquals false ๐ Executing: process_standard_order ๐ Executing: process_payment ๐ Executing: update_inventory โ Workflow completed successfully! ๐ Final Status: failed ๐ฃ๏ธ Execution Path: ValidateInput โ CheckOrderType โ RouteOrder โ ProcessStandardOrder โ ProcessPayment โฑ๏ธ Execution Time: 0.0003 seconds ๐ฆ Output Keys: order, $, order_type_result, standard_result, payment_result
- order_type_result: order_type,reason
- standard_result: standard_processed,order_id,processing_tier
- payment_result: status,payment_id,amount_charged,currency,processed_at
============================================================
๐งช Testing: Payment Failure Order
๐ Executing: determine_order_type
=> "$.order_type_result.order_type", "StringEquals" => "premium", "Next" => "ProcessPremiumOrder" StringEquals true ๐ Executing: process_premium_order ๐ Executing: process_payment ๐ Executing: update_inventory => "$.order.shipping.required", "BooleanEquals" => false, "Next" => "SendDigitalDelivery" BooleanEquals false => "$.order_type_result.order_type", "StringEquals" => "premium", "Next" => "ScheduleExpressShipping" StringEquals true ๐ Executing: schedule_express_shipping ๐ Executing: send_order_confirmation โ Workflow completed successfully! ๐ Final Status: succeeded ๐ฃ๏ธ Execution Path: ValidateInput โ CheckOrderType โ RouteOrder โ ProcessPremiumOrder โ ProcessPayment โ HandleShipping โ ScheduleExpressShipping โ SendOrderConfirmation โฑ๏ธ Execution Time: 0.0005 seconds ๐ฆ Output Keys: order, $, order_type_result, premium_result, payment_result, inventory_error, shipping_result, confirmation_result
- order_type_result: order_type,reason
- premium_result: premium_processed,order_id,vip_handling,dedicated_support,processing_tier
- payment_result: status,payment_id,amount_charged,currency,processed_at
- shipping_result: shipping_scheduled,order_id,method,estimated_days,tracking_number,priority
- confirmation_result: confirmation_sent,order_id,customer_id,sent_via,confirmation_id
๐ All tests completed!
- examples/test_parallel_complex_workflow.rb
```bash
๐ Starting Enhanced Complex Workflow Test
Testing ALL state types: Pass, Task, Choice, Parallel, Succeed, Fail
With retry mechanisms, error handling, and parallel processing
๐ Generated enhanced workflow file: complex_workflow_with_parallel.yaml
======================================================================
๐งช ๐ Premium Order with Parallel Processing
======================================================================
๐ Executing: determine_order_type
[determine_order_type] Analyzing order: ORD-PREM-001 - Total: $750.0, Items: 2, Quantity: 2
{"Variable" => "$.order_type_result.order_type", "StringEquals" => "premium", "Next" => "ProcessPremiumOrder"}
StringEquals
true
๐ Executing: process_premium_order
[process_premium_order] VIP processing for: ORD-PREM-001
๐ Executing: process_payment
[process_payment] Processing $0.0 via
{"Variable" => "$.payment_result.status", "StringEquals" => "completed", "Next" => "ParallelPostPayment"}
StringEquals
true
๐ Executing: update_inventory
[update_inventory] Updating inventory for:
๐ Executing: send_customer_notifications
[send_customer_notifications] Sending notifications: ORD-PREM-001
๐ Executing: generate_analytics
[generate_analytics] Generating analytics: ORD-PREM-001
๐ Executing: process_loyalty_points
[process_loyalty_points] Processing loyalty: CUST-PREM-001
โ
Workflow completed successfully!
๐ Final Status: failed
๐ฃ๏ธ Execution Path: ValidateInput โ CheckOrderType โ RouteOrder โ ProcessPremiumOrder โ ProcessPayment โ VerifyPaymentSuccess
โฑ๏ธ Execution Time: 0.0066 seconds
๐ States Visited: 6
๐ฆ Final Output Summary:
- order: id, total, customer_id, items, quantity, premium_customer, payment_method, shipping
- $: validation_metadata
- order_type_result: order_type, reason
- premium_result: premium_processed, order_id, vip_handling, dedicated_support, processing_tier, priority_level
- payment_result: status, payment_id, amount_charged, currency, processed_at, attempts
======================================================================
๐งช ๐ฆ Bulk Order (Testing Retry)
======================================================================
๐ Executing: determine_order_type
[determine_order_type] Analyzing order: ORD-BULK-001 - Total: $1800.0, Items: 1, Quantity: 25
{"Variable" => "$.order_type_result.order_type", "StringEquals" => "premium", "Next" => "ProcessPremiumOrder"}
StringEquals
true
๐ Executing: process_premium_order
[process_premium_order] VIP processing for: ORD-BULK-001
๐ Executing: process_payment
[process_payment] Processing $0.0 via
{"Variable" => "$.payment_result.status", "StringEquals" => "completed", "Next" => "ParallelPostPayment"}
StringEquals
true
๐ Executing: update_inventory
[update_inventory] Updating inventory for:
๐ Executing: send_customer_notifications
[send_customer_notifications] Sending notifications: ORD-BULK-001
๐ Executing: generate_analytics
[generate_analytics] Generating analytics: ORD-BULK-001
๐ Executing: process_loyalty_points
[process_loyalty_points] Processing loyalty: CUST-BULK-001
โ
Workflow completed successfully!
๐ Final Status: failed
๐ฃ๏ธ Execution Path: ValidateInput โ CheckOrderType โ RouteOrder โ ProcessPremiumOrder โ ProcessPayment โ VerifyPaymentSuccess
โฑ๏ธ Execution Time: 0.0033 seconds
๐ States Visited: 6
๐ฆ Final Output Summary:
- order: id, total, customer_id, items, quantity, payment_method, shipping
- $: validation_metadata
- order_type_result: order_type, reason
- premium_result: premium_processed, order_id, vip_handling, dedicated_support, processing_tier, priority_level
- payment_result: status, payment_id, amount_charged, currency, processed_at, attempts
======================================================================
๐งช ๐ป Digital Order (No Shipping)
======================================================================
๐ Executing: determine_order_type
[determine_order_type] Analyzing order: ORD-DIG-001 - Total: $49.99, Items: 1, Quantity: 1
{"Variable" => "$.order_type_result.order_type", "StringEquals" => "premium", "Next" => "ProcessPremiumOrder"}
StringEquals
false
{"Variable" => "$.order_type_result.order_type", "StringEquals" => "bulk", "Next" => "CheckBulkInventory"}
StringEquals
false
{"Variable" => "$.order_type_result.order_type", "StringEquals" => "international", "Next" => "ProcessInternationalOrder"}
StringEquals
true
๐ Executing: process_international_order
[process_international_order] International processing for: ORD-DIG-001 to
๐ Executing: process_payment
[process_payment] Processing $0.0 via
{"Variable" => "$.payment_result.status", "StringEquals" => "completed", "Next" => "ParallelPostPayment"}
StringEquals
true
๐ Executing: update_inventory
[update_inventory] Updating inventory for:
๐ Executing: send_customer_notifications
[send_customer_notifications] Sending notifications: ORD-DIG-001
๐ Executing: generate_analytics
[generate_analytics] Generating analytics: ORD-DIG-001
๐ Executing: process_loyalty_points
[process_loyalty_points] Processing loyalty: CUST-DIG-001
โ
Workflow completed successfully!
๐ Final Status: failed
๐ฃ๏ธ Execution Path: ValidateInput โ CheckOrderType โ RouteOrder โ ProcessInternationalOrder โ ProcessPayment โ VerifyPaymentSuccess
โฑ๏ธ Execution Time: 0.003 seconds
๐ States Visited: 6
๐ฆ Final Output Summary:
- order: id, total, customer_id, items, quantity, payment_method, shipping, digital_product, customer_email
- $: validation_metadata
- order_type_result: order_type, reason
- international_result: international_processed, order_id, destination_country, export_documentation, customs_declaration, requires_export_license
- payment_result: status, payment_id, amount_charged, currency, processed_at, attempts
======================================================================
๐งช ๐ International Order
======================================================================
๐ Executing: determine_order_type
[determine_order_type] Analyzing order: ORD-INT-001 - Total: $299.99, Items: 1, Quantity: 1
{"Variable" => "$.order_type_result.order_type", "StringEquals" => "premium", "Next" => "ProcessPremiumOrder"}
StringEquals
false
{"Variable" => "$.order_type_result.order_type", "StringEquals" => "bulk", "Next" => "CheckBulkInventory"}
StringEquals
false
{"Variable" => "$.order_type_result.order_type", "StringEquals" => "international", "Next" => "ProcessInternationalOrder"}
StringEquals
true
๐ Executing: process_international_order
[process_international_order] International processing for: ORD-INT-001 to
๐ Executing: process_payment
[process_payment] Processing $0.0 via
{"Variable" => "$.payment_result.status", "StringEquals" => "completed", "Next" => "ParallelPostPayment"}
StringEquals
true
๐ Executing: update_inventory
[update_inventory] Updating inventory for:
๐ Executing: send_customer_notifications
[send_customer_notifications] Sending notifications: ORD-INT-001
๐ Executing: generate_analytics
[generate_analytics] Generating analytics: ORD-INT-001
๐ Executing: process_loyalty_points
[process_loyalty_points] Processing loyalty: CUST-INT-001
โ
Workflow completed successfully!
๐ Final Status: failed
๐ฃ๏ธ Execution Path: ValidateInput โ CheckOrderType โ RouteOrder โ ProcessInternationalOrder โ ProcessPayment โ VerifyPaymentSuccess
โฑ๏ธ Execution Time: 0.0031 seconds
๐ States Visited: 6
๐ฆ Final Output Summary:
- order: id, total, customer_id, items, quantity, payment_method, shipping
- $: validation_metadata
- order_type_result: order_type, reason
- international_result: international_processed, order_id, destination_country, export_documentation, customs_declaration, requires_export_license
- payment_result: status, payment_id, amount_charged, currency, processed_at, attempts
======================================================================
๐งช ๐ธ Payment Failure Scenario
======================================================================
๐ Executing: determine_order_type
[determine_order_type] Analyzing order: ORD-FAIL-001 - Total: $2500.0, Items: 1, Quantity: 1
{"Variable" => "$.order_type_result.order_type", "StringEquals" => "premium", "Next" => "ProcessPremiumOrder"}
StringEquals
true
๐ Executing: process_premium_order
[process_premium_order] VIP processing for: ORD-FAIL-001
๐ Executing: process_payment
[process_payment] Processing $0.0 via
{"Variable" => "$.payment_result.status", "StringEquals" => "completed", "Next" => "ParallelPostPayment"}
StringEquals
true
๐ Executing: update_inventory
[update_inventory] Updating inventory for:
๐ Executing: send_customer_notifications
[send_customer_notifications] Sending notifications: ORD-FAIL-001
๐ Executing: generate_analytics
[generate_analytics] Generating analytics: ORD-FAIL-001
๐ Executing: process_loyalty_points
[process_loyalty_points] Processing loyalty: CUST-FAIL-001
โ
Workflow completed successfully!
๐ Final Status: failed
๐ฃ๏ธ Execution Path: ValidateInput โ CheckOrderType โ RouteOrder โ ProcessPremiumOrder โ ProcessPayment โ VerifyPaymentSuccess
โฑ๏ธ Execution Time: 0.0028 seconds
๐ States Visited: 6
๐ฆ Final Output Summary:
- order: id, total, customer_id, items, quantity, payment_method, shipping
- $: validation_metadata
- order_type_result: order_type, reason
- premium_result: premium_processed, order_id, vip_handling, dedicated_support, processing_tier, priority_level
- payment_result: status, payment_id, amount_charged, currency, processed_at, attempts
๐ All tests completed!
๐ State Types Demonstrated:
โ
Pass - Data transformation
โ
Task - Business logic execution
โ
Choice - Conditional routing
โ
Parallel - Concurrent branch execution
โ
Succeed - Successful termination
โ
Fail - Error termination
โ
Retry - Automatic retry mechanisms
โ
Catch - Error handling blocks
License
The gem is available as open source under the terms of the MIT License.
Acknowledgments
Inspired by AWS Step Functions state language
Built for Ruby developers who need workflow orchestration
Designed for both simple and complex business processes
Author
- Hussain Pithawala (https://www.linkedin.com/in/hussainpithawala)