# Event-Driven Flow Architecture ## Overview A simple, single-threaded architecture where API calls trigger HTTP requests and spawn new Rhai scripts based on responses. No global state, no polling, no blocking - just clean event-driven flows. ## Core Concept ```mermaid graph LR RS1[Rhai Script] --> API[create_payment_intent] API --> HTTP[HTTP Request] HTTP --> SPAWN[Spawn Thread] SPAWN --> WAIT[Wait for Response] WAIT --> SUCCESS[200 OK] WAIT --> ERROR[Error] SUCCESS --> RS2[new_payment_intent.rhai] ERROR --> RS3[payment_failed.rhai] ``` ## Architecture Design ### 1. Simple Flow Manager ```rust use std::thread; use std::collections::HashMap; use reqwest::Client; use rhai::{Engine, Scope}; pub struct FlowManager { pub client: Client, pub engine: Engine, pub flow_scripts: HashMap, // event_name -> script_path } impl FlowManager { pub fn new() -> Self { let mut flow_scripts = HashMap::new(); // Define flow mappings flow_scripts.insert("payment_intent_created".to_string(), "flows/payment_intent_created.rhai".to_string()); flow_scripts.insert("payment_intent_failed".to_string(), "flows/payment_intent_failed.rhai".to_string()); flow_scripts.insert("product_created".to_string(), "flows/product_created.rhai".to_string()); flow_scripts.insert("subscription_created".to_string(), "flows/subscription_created.rhai".to_string()); Self { client: Client::new(), engine: Engine::new(), flow_scripts, } } // Fire HTTP request and spawn response handler pub fn fire_and_continue(&self, endpoint: String, method: String, data: HashMap, success_event: String, error_event: String, context: HashMap ) { let client = self.client.clone(); let flow_scripts = self.flow_scripts.clone(); // Spawn thread for HTTP request thread::spawn(move || { let result = Self::make_http_request(&client, &endpoint, &method, &data); match result { Ok(response_data) => { // Success: dispatch success flow Self::dispatch_flow(&flow_scripts, &success_event, response_data, context); } Err(error) => { // Error: dispatch error flow let mut error_data = HashMap::new(); error_data.insert("error".to_string(), error); Self::dispatch_flow(&flow_scripts, &error_event, error_data, context); } } }); // Return immediately - no blocking! } // Execute HTTP request fn make_http_request( client: &Client, endpoint: &str, method: &str, data: &HashMap ) -> Result, String> { // This runs in spawned thread - can block safely let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async { let url = format!("https://api.stripe.com/v1/{}", endpoint); let response = client .post(&url) .form(data) .send() .await .map_err(|e| format!("HTTP error: {}", e))?; let response_text = response.text().await .map_err(|e| format!("Response read error: {}", e))?; let json: serde_json::Value = serde_json::from_str(&response_text) .map_err(|e| format!("JSON parse error: {}", e))?; // Convert JSON to HashMap for Rhai let mut result = HashMap::new(); if let Some(id) = json.get("id").and_then(|v| v.as_str()) { result.insert("id".to_string(), id.to_string()); } if let Some(status) = json.get("status").and_then(|v| v.as_str()) { result.insert("status".to_string(), status.to_string()); } Ok(result) }) } // Dispatch new Rhai script based on event fn dispatch_flow( flow_scripts: &HashMap, event_name: &str, response_data: HashMap, context: HashMap ) { if let Some(script_path) = flow_scripts.get(event_name) { println!("🎯 Dispatching flow: {} -> {}", event_name, script_path); // Create new engine instance for this flow let mut engine = Engine::new(); register_payment_rhai_module(&mut engine); // Create scope with response data and context let mut scope = Scope::new(); // Add response data for (key, value) in response_data { scope.push(key, value); } // Add context data for (key, value) in context { scope.push(format!("context_{}", key), value); } // Execute flow script if let Ok(script_content) = std::fs::read_to_string(script_path) { match engine.eval_with_scope::<()>(&mut scope, &script_content) { Ok(_) => println!("✅ Flow {} completed successfully", event_name), Err(e) => println!("❌ Flow {} failed: {}", event_name, e), } } else { println!("❌ Flow script not found: {}", script_path); } } else { println!("⚠️ No flow defined for event: {}", event_name); } } } ``` ### 2. Simple Rhai Functions ```rust #[export_module] mod rhai_flow_module { use super::*; // Global flow manager instance static FLOW_MANAGER: std::sync::OnceLock = std::sync::OnceLock::new(); #[rhai_fn(name = "init_flows")] pub fn init_flows() { FLOW_MANAGER.set(FlowManager::new()).ok(); println!("✅ Flow manager initialized"); } #[rhai_fn(name = "create_payment_intent")] pub fn create_payment_intent( amount: i64, currency: String, customer: String ) { let manager = FLOW_MANAGER.get().expect("Flow manager not initialized"); let mut data = HashMap::new(); data.insert("amount".to_string(), amount.to_string()); data.insert("currency".to_string(), currency); data.insert("customer".to_string(), customer.clone()); let mut context = HashMap::new(); context.insert("customer_id".to_string(), customer); context.insert("original_amount".to_string(), amount.to_string()); manager.fire_and_continue( "payment_intents".to_string(), "POST".to_string(), data, "payment_intent_created".to_string(), "payment_intent_failed".to_string(), context ); println!("🚀 Payment intent creation started"); // Returns immediately! } #[rhai_fn(name = "create_product")] pub fn create_product(name: String, description: String) { let manager = FLOW_MANAGER.get().expect("Flow manager not initialized"); let mut data = HashMap::new(); data.insert("name".to_string(), name.clone()); data.insert("description".to_string(), description); let mut context = HashMap::new(); context.insert("product_name".to_string(), name); manager.fire_and_continue( "products".to_string(), "POST".to_string(), data, "product_created".to_string(), "product_failed".to_string(), context ); println!("🚀 Product creation started"); } #[rhai_fn(name = "create_subscription")] pub fn create_subscription(customer: String, price_id: String) { let manager = FLOW_MANAGER.get().expect("Flow manager not initialized"); let mut data = HashMap::new(); data.insert("customer".to_string(), customer.clone()); data.insert("items[0][price]".to_string(), price_id.clone()); let mut context = HashMap::new(); context.insert("customer_id".to_string(), customer); context.insert("price_id".to_string(), price_id); manager.fire_and_continue( "subscriptions".to_string(), "POST".to_string(), data, "subscription_created".to_string(), "subscription_failed".to_string(), context ); println!("🚀 Subscription creation started"); } } ``` ## Usage Examples ### 1. Main Script (Initiator) ```rhai // main.rhai init_flows(); print("Starting payment flow..."); // This returns immediately, spawns HTTP request create_payment_intent(2000, "usd", "cus_customer123"); print("Payment intent request sent, continuing..."); // Script ends here, but flow continues in background ``` ### 2. Success Flow Script ```rhai // flows/payment_intent_created.rhai print("🎉 Payment intent created successfully!"); print(`Payment Intent ID: ${id}`); print(`Status: ${status}`); print(`Customer: ${context_customer_id}`); print(`Amount: ${context_original_amount}`); // Continue the flow - create subscription if status == "requires_payment_method" { print("Creating subscription for customer..."); create_subscription(context_customer_id, "price_monthly_plan"); } ``` ### 3. Error Flow Script ```rhai // flows/payment_intent_failed.rhai print("❌ Payment intent creation failed"); print(`Error: ${error}`); print(`Customer: ${context_customer_id}`); // Handle error - maybe retry or notify print("Sending notification to customer..."); // Could trigger email notification flow here ``` ### 4. Subscription Success Flow ```rhai // flows/subscription_created.rhai print("🎉 Subscription created!"); print(`Subscription ID: ${id}`); print(`Customer: ${context_customer_id}`); print(`Price: ${context_price_id}`); // Final step - send welcome email print("Sending welcome email..."); // Could trigger email flow here ``` ## Flow Configuration ### 1. Flow Mapping ```rust // Define in FlowManager::new() flow_scripts.insert("payment_intent_created".to_string(), "flows/payment_intent_created.rhai".to_string()); flow_scripts.insert("payment_intent_failed".to_string(), "flows/payment_intent_failed.rhai".to_string()); flow_scripts.insert("product_created".to_string(), "flows/product_created.rhai".to_string()); flow_scripts.insert("subscription_created".to_string(), "flows/subscription_created.rhai".to_string()); ``` ### 2. Directory Structure ``` project/ ├── main.rhai # Main script ├── flows/ │ ├── payment_intent_created.rhai # Success flow │ ├── payment_intent_failed.rhai # Error flow │ ├── product_created.rhai # Product success │ ├── subscription_created.rhai # Subscription success │ └── email_notification.rhai # Email flow └── src/ └── flow_manager.rs # Flow manager code ``` ## Execution Flow ```mermaid sequenceDiagram participant MS as Main Script participant FM as FlowManager participant TH as Spawned Thread participant API as Stripe API participant FS as Flow Script MS->>FM: create_payment_intent() FM->>TH: spawn thread FM->>MS: return immediately Note over MS: Script ends TH->>API: HTTP POST /payment_intents API->>TH: 200 OK + payment_intent data TH->>FS: dispatch payment_intent_created.rhai Note over FS: New Rhai execution FS->>FM: create_subscription() FM->>TH: spawn new thread TH->>API: HTTP POST /subscriptions API->>TH: 200 OK + subscription data TH->>FS: dispatch subscription_created.rhai ``` ## Benefits ### 1. **Simplicity** - No global state management - No complex polling or callbacks - Each flow is a simple Rhai script ### 2. **Single-Threaded Rhai** - Main Rhai engine never blocks - Each flow script runs in its own engine instance - No concurrency issues in Rhai code ### 3. **Event-Driven** - Clear separation of concerns - Easy to add new flows - Composable flow chains ### 4. **No Blocking** - HTTP requests happen in background threads - Main script continues immediately - Flows trigger based on responses ## Advanced Features ### 1. Flow Chaining ```rhai // flows/payment_intent_created.rhai if status == "requires_payment_method" { // Chain to next flow create_subscription(context_customer_id, "price_monthly"); } ``` ### 2. Conditional Flows ```rhai // flows/subscription_created.rhai if context_customer_type == "enterprise" { // Enterprise-specific flow create_enterprise_setup(context_customer_id); } else { // Standard flow send_welcome_email(context_customer_id); } ``` ### 3. Error Recovery ```rhai // flows/payment_intent_failed.rhai if error.contains("insufficient_funds") { // Retry with smaller amount let retry_amount = context_original_amount / 2; create_payment_intent(retry_amount, "usd", context_customer_id); } else { // Send error notification send_error_notification(context_customer_id, error); } ``` This architecture is much simpler, has no global state, and provides clean event-driven flows that are easy to understand and maintain.