actor_framework/
lib.rs

1//! # Actor Framework
2//!
3//! This crate provides the foundational building blocks for creating type-safe, concurrent
4//! actor systems in Rust. It implements a **Resource-Oriented Architecture (ROA)** pattern
5//! on top of the **Actor Model**, providing a clean abstraction for managing stateful entities.
6//!
7//! ## Why ROA + Actor Model?
8//!
9//! This framework combines **Resource-Oriented Architecture (ROA)** with the **Actor Model**
10//! to create a powerful pattern for building scalable systems.
11//!
12//! ### Resource-Oriented Architecture (ROA)
13//!
14//! - Standard CRUD operations (Create, Read, Update, Delete) on well-defined resources
15//! - Predictable lifecycle management
16//! - Clean, uniform API surface across all resource types
17//!
18//! ### Actor Model
19//!
20//! - Isolated state (no shared memory, no locks)
21//! - Message-passing concurrency
22//! - Sequential processing within each actor eliminates race conditions
23//!
24//! ### The Synergy
25//!
26//! - **Separation**: Each resource type (User, Product, Order) gets its own actor with completely isolated state
27//! - **Coordination**: When resources need to interact (e.g., Order reserving Product stock), they communicate via **Action messages** instead of direct coupling
28//! - **Scalability**: Independent resources can scale independently without coordination overhead
29//! - **Maintainability**: Changes to one resource type don't ripple through the system
30//!
31//! This pattern excels in systems with many loosely-coupled resources that occasionally need
32//! to coordinate. The ROA provides structure, while the Actor Model provides safe concurrency.
33//!
34//! **Further Reading**:
35//! - [Actor Model (Wikipedia)](https://en.wikipedia.org/wiki/Actor_model) - Foundational concurrency pattern by Carl Hewitt
36//! - [Resource-Oriented Architecture](https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm) - Roy Fielding's dissertation on REST/ROA principles
37//! - [Actors in Rust](https://ryhl.io/blog/actors-with-tokio/) - Practical guide to implementing actors with Tokio
38//!
39//! ## Architecture Overview
40//!
41//! The framework separates concerns into three layers:
42//!
43//! 1. **Entity Layer** ([`ActorEntity`]) - Your business logic and domain models
44//! 2. **Runtime Layer** ([`ResourceActor`]) - Message processing and concurrency
45//! 3. **Interface Layer** ([`ResourceClient`]) - Type-safe communication
46//!
47//! This separation means you write your business logic **once** in the entity trait,
48//! and the framework handles all the async message passing, error handling, and state management.
49//!
50//! ## Core Abstractions
51//!
52//! ### [`ActorEntity`] - The Business Logic
53//!
54//! Define what your actor manages and how it behaves:
55//!
56//! ### [`ActorEntity`] - The Business Logic
57//!
58//! Define what your actor manages and how it behaves:
59//!
60//! ```rust
61//! use actor_framework::{ActorEntity, ResourceActor, ResourceClient};
62//! use async_trait::async_trait;
63//!
64//! // 1. Define the Entity
65//! #[derive(Clone, Debug)]
66//! struct User {
67//!     id: u32,
68//!     name: String,
69//! }
70//!
71//! #[derive(Debug)] struct UserCreate { name: String }
72//! #[derive(Debug)] struct UserUpdate { name: Option<String> }
73//! #[derive(Debug)] enum UserAction {}
74//! #[derive(Debug)] struct UserError(String);
75//!
76//! impl std::fmt::Display for UserError {
77//!     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) }
78//! }
79//! impl std::error::Error for UserError {}
80//!
81//! #[async_trait]
82//! impl ActorEntity for User {
83//!     type Id = u32;
84//!     type Create = UserCreate;
85//!     type Update = UserUpdate;
86//!     type Action = UserAction;
87//!     type ActionResult = ();
88//!     type Context = ();
89//!     type Error = UserError;
90//!
91//!     fn from_create_params(id: u32, params: UserCreate) -> Result<Self, Self::Error> {
92//!         Ok(Self { id, name: params.name })
93//!     }
94//!
95//!     async fn on_update(&mut self, update: UserUpdate, _ctx: &Self::Context) -> Result<(), Self::Error> {
96//!         if let Some(name) = update.name { self.name = name; }
97//!         Ok(())
98//!     }
99//!
100//!     async fn handle_action(&mut self, _: UserAction, _: &Self::Context) -> Result<(), Self::Error> {
101//!         Ok(())
102//!     }
103//! }
104//!
105//! // 2. Use the Actor
106//! #[tokio::main]
107//! async fn main() {
108//!     // Create actor and client
109//!     let (actor, client) = ResourceActor::<User>::new(10);
110//!
111//!     // Spawn the actor
112//!     tokio::spawn(actor.run(()));
113//!
114//!     // Use the client
115//!     let id = client.create(UserCreate { name: "Alice".into() }).await.unwrap();
116//!     let user = client.get(id).await.unwrap().unwrap();
117//!     assert_eq!(user.name, "Alice");
118//! }
119//! ```
120//!
121//! ## Context Injection Pattern
122//!
123//! Dependencies are injected at **runtime** via the `run()` method, not at construction time.
124//! This "late binding" pattern solves circular dependencies:
125//!
126//! ```rust
127//! use actor_framework::{ActorEntity, ResourceActor, ResourceClient};
128//! use async_trait::async_trait;
129//!
130//! // --- Define Minimal Entities ---
131//! #[derive(Clone, Debug)] struct User { id: u32 }
132//! #[derive(Debug)] struct UserCreate;
133//! #[derive(Debug)] struct UserUpdate;
134//! #[derive(Debug)] enum UserAction {}
135//! #[derive(Debug)] struct UserError;
136//! impl std::fmt::Display for UserError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Err") } }
137//! impl std::error::Error for UserError {}
138//! impl From<String> for UserError { fn from(_: String) -> Self { UserError } }
139//!
140//! #[async_trait]
141//! impl ActorEntity for User {
142//!     type Id = u32; type Create = UserCreate; type Update = UserUpdate; type Action = UserAction;
143//!     type ActionResult = (); type Context = (); type Error = UserError;
144//!     fn from_create_params(id: u32, _: UserCreate) -> Result<Self, Self::Error> { Ok(Self { id }) }
145//!     async fn on_update(&mut self, _: UserUpdate, _: &()) -> Result<(), Self::Error> { Ok(()) }
146//!     async fn handle_action(&mut self, _: UserAction, _: &()) -> Result<(), Self::Error> { Ok(()) }
147//! }
148//!
149//! #[derive(Clone, Debug)] struct Product { id: u32 }
150//! // ... (impl ActorEntity for Product similar to User) ...
151//! # #[derive(Debug)] struct ProductCreate;
152//! # #[derive(Debug)] struct ProductUpdate;
153//! # #[derive(Debug)] enum ProductAction {}
154//! # #[derive(Debug)] struct ProductError;
155//! # impl std::fmt::Display for ProductError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Err") } }
156//! # impl std::error::Error for ProductError {}
157//! # impl From<String> for ProductError { fn from(_: String) -> Self { ProductError } }
158//! # #[async_trait]
159//! # impl ActorEntity for Product {
160//! #     type Id = u32; type Create = ProductCreate; type Update = ProductUpdate; type Action = ProductAction;
161//! #     type ActionResult = (); type Context = (); type Error = ProductError;
162//! #     fn from_create_params(id: u32, _: ProductCreate) -> Result<Self, Self::Error> { Ok(Self { id }) }
163//! #     async fn on_update(&mut self, _: ProductUpdate, _: &()) -> Result<(), Self::Error> { Ok(()) }
164//! #     async fn handle_action(&mut self, _: ProductAction, _: &()) -> Result<(), Self::Error> { Ok(()) }
165//! # }
166//!
167//! #[derive(Clone, Debug)] struct Order { id: u32 }
168//! // Order depends on UserClient and ProductClient
169//! type OrderContext = (ResourceClient<User>, ResourceClient<Product>);
170//!
171//! #[derive(Debug)] struct OrderCreate;
172//! #[derive(Debug)] struct OrderUpdate;
173//! #[derive(Debug)] enum OrderAction {}
174//! #[derive(Debug)] struct OrderError;
175//! impl std::fmt::Display for OrderError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Err") } }
176//! impl std::error::Error for OrderError {}
177//! impl From<String> for OrderError { fn from(_: String) -> Self { OrderError } }
178//!
179//! #[async_trait]
180//! impl ActorEntity for Order {
181//!     type Id = u32; type Create = OrderCreate; type Update = OrderUpdate; type Action = OrderAction;
182//!     type ActionResult = (); type Context = OrderContext; type Error = OrderError;
183//!
184//!     fn from_create_params(id: u32, _: OrderCreate) -> Result<Self, Self::Error> { Ok(Self { id }) }
185//!     async fn on_update(&mut self, _: OrderUpdate, _: &OrderContext) -> Result<(), Self::Error> { Ok(()) }
186//!     async fn handle_action(&mut self, _: OrderAction, _: &OrderContext) -> Result<(), Self::Error> { Ok(()) }
187//!     // In a real app, on_create would use the context to validate user/product
188//! }
189//!
190//! #[tokio::main]
191//! async fn main() {
192//!     // 1. Create all actors (no dependencies yet)
193//!     let (user_actor, user_client) = ResourceActor::<User>::new(10);
194//!     let (product_actor, product_client) = ResourceActor::<Product>::new(10);
195//!     let (order_actor, order_client) = ResourceActor::<Order>::new(10);
196//!
197//!     // 2. Wire dependencies when starting actors
198//!     tokio::spawn(user_actor.run(()));
199//!     tokio::spawn(product_actor.run(()));
200//!     // Order actor gets the clients it needs
201//!     tokio::spawn(order_actor.run((user_client, product_client)));
202//!
203//!     // 3. Use the actor (keeps main alive)
204//!     let _ = order_client.create(OrderCreate).await;
205//! }
206//! ```
207//!
208//! The `Order` actor receives `(UserClient, ProductClient)` as its context, allowing it to
209//! validate users and reserve product stock during order creation.
210//!
211//! ## Type Safety
212//!
213//! The framework leverages Rust's type system to eliminate entire classes of runtime errors:
214//!
215//! - **Compile-time guarantees**: Can't send wrong message types to actors
216//! - **Type-safe errors**: Each entity defines its own error type
217//! - **No stringly-typed APIs**: IDs, actions, and results are all strongly typed
218//!
219//! ## Concurrency Model
220//!
221//! - Each actor runs in its own Tokio task
222//! - Messages are processed **sequentially** within an actor (no locks needed!)
223//! - Multiple actors run in **parallel** (true concurrency)
224//! - No shared mutable state (message passing only)
225//!
226//! ## Testing
227//!
228//! The framework provides a **MockClient** type that implements the same `ResourceClient<T>` API as the real client but operates entirely in‑memory. It lets you write fast, deterministic unit tests for client logic (e.g. `OrderClient`) without spawning any actors. See the [`mock`] module for the full API and usage patterns.
229
230pub mod actor;
231pub mod client;
232pub mod client_trait;
233pub mod entity;
234pub mod error;
235pub mod message;
236pub mod mock;
237pub mod tracing;
238
239// Re-export core types for convenience
240pub use actor::ResourceActor;
241pub use client::ResourceClient;
242pub use client_trait::ActorClient;
243pub use entity::ActorEntity;
244pub use error::FrameworkError;
245pub use message::{ResourceRequest, Response};