actor_sample/lifecycle/
order_system.rs

1//! # Order System
2//!
3//! Provides the runtime orchestrator for the actor‑based order management system.
4//! It wires together the `User`, `Product`, and `Order` actors and exposes
5//! high‑level clients for interacting with them. Includes lifecycle management
6//! and graceful shutdown.
7use crate::clients::{OrderClient, ProductClient, UserClient};
8use tracing::{error, info};
9
10/// The main runtime orchestrator for the actor-based order management system.
11///
12/// `OrderSystem` is responsible for:
13/// - **Lifecycle Management**: Starting and stopping all actors in the system
14/// - **Dependency Wiring**: Connecting actors that depend on each other (e.g., OrderClient needs UserClient)
15/// - **Resource Coordination**: Managing shared resources like ID generators
16///
17/// # Architecture
18///
19/// The system consists of three main actors:
20/// - **User Actor**: Manages user entities (CRUD operations)
21/// - **Product Actor**: Manages product entities with stock tracking
22/// - **Order Actor**: Manages orders and coordinates with User and Product actors
23///
24/// # Example
25///
26/// ```ignore
27/// let system = OrderSystem::new();
28///
29/// // Use the clients to interact with actors
30/// let user_id = system.user_client.create_user(user_data).await?;
31/// let product_id = system.product_client.create_product(product_data).await?;
32/// let order_id = system.order_client.create_order(order_data).await?;
33///
34/// // Gracefully shut down when done
35/// system.shutdown().await?;
36/// ```
37pub struct OrderSystem {
38    /// Client for interacting with the Order actor
39    pub order_client: OrderClient,
40
41    /// Client for interacting with the User actor
42    pub user_client: UserClient,
43
44    /// Client for interacting with the Product actor
45    pub product_client: ProductClient,
46
47    /// Task handles for all running actors (used for graceful shutdown)
48    handles: Vec<tokio::task::JoinHandle<()>>,
49}
50
51impl Default for OrderSystem {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57impl OrderSystem {
58    /// Creates and initializes a new `OrderSystem` with all actors running.
59    ///
60    /// This method:
61    /// 1. Creates ID generators for each entity type
62    /// 2. Spawns ResourceActors for User, Product, and Order
63    /// 3. Wires up dependencies (OrderClient depends on UserClient and ProductClient)
64    /// 4. Spawns each actor in its own Tokio task
65    ///
66    /// # Returns
67    ///
68    /// A fully initialized `OrderSystem` with all actors running and ready to accept requests.
69    pub fn new() -> Self {
70        // 1. Create actors (no dependencies) and wrap generic clients
71        let (user_actor, user_generic_client) = crate::user_actor::new();
72        let user_client = UserClient::new(user_generic_client);
73        let (product_actor, product_generic_client) = crate::product_actor::new();
74        let product_client = ProductClient::new(product_generic_client);
75        let (order_actor, order_generic_client) = crate::order_actor::new();
76        let order_client = OrderClient::new(order_generic_client);
77
78        // 2. Start actors with injected context
79        // User and Product have no dependencies (Context = ())
80        let user_handle = tokio::spawn(user_actor.run(()));
81        let product_handle = tokio::spawn(product_actor.run(()));
82
83        // Order actor needs User and Product clients (Context = (UserClient, ProductClient))
84        let order_handle =
85            tokio::spawn(order_actor.run((user_client.clone(), product_client.clone())));
86
87        Self {
88            order_client,
89            user_client,
90            product_client,
91            handles: vec![user_handle, product_handle, order_handle],
92        }
93    }
94
95    /// Gracefully shuts down the entire system.
96    ///
97    /// This method:
98    /// 1. Drops all clients, which closes their communication channels
99    /// 2. Waits for all actor tasks to complete
100    /// 3. Returns an error if any actor task panicked
101    ///
102    /// # Shutdown Process
103    ///
104    /// When clients are dropped, the underlying channels are closed. Each `ResourceActor`
105    /// detects the closed channel and exits its event loop gracefully.
106    ///
107    /// # Returns
108    ///
109    /// - `Ok(())` if all actors shut down cleanly
110    /// - `Err(String)` if any actor task failed or panicked
111    ///
112    /// # Example
113    ///
114    /// ```ignore
115    /// let system = OrderSystem::new();
116    /// // ... use the system ...
117    /// system.shutdown().await?;
118    /// ```
119    pub async fn shutdown(self) -> Result<(), String> {
120        info!("Shutting down system...");
121
122        // =====================================================================
123        // Step 1: Close all channels by dropping clients
124        // =====================================================================
125
126        // When we drop the clients, their internal channel senders are dropped.
127        // This causes the actors' receivers to return None, signaling shutdown.
128        drop(self.order_client);
129        drop(self.user_client);
130        drop(self.product_client);
131
132        // =====================================================================
133        // Step 2: Wait for all actor tasks to complete
134        // =====================================================================
135
136        for handle in self.handles {
137            // Wait for the actor task to finish
138            // If the task panicked, this will return an Err
139            if let Err(e) = handle.await {
140                error!("Actor task failed: {:?}", e);
141                return Err(format!("Actor task failed: {:?}", e));
142            }
143        }
144
145        info!("System shutdown complete.");
146        Ok(())
147    }
148}