actor_sample/clients/mod.rs
1//! # Domain-Specific Client Wrappers
2//!
3//! This module provides **type-safe, domain-specific wrappers** around the generic
4//! [`ResourceClient`](actor_framework::ResourceClient). These wrappers add domain
5//! knowledge and ergonomic APIs on top of the raw framework client.
6//!
7//! ## The Client Wrapper Pattern
8//!
9//! Instead of exposing the generic `ResourceClient<T>` directly, we wrap it in
10//! domain-specific clients that provide:
11//!
12//! 1. **Domain-specific methods** - `create_user()` instead of generic `create()`
13//! 2. **Type-safe errors** - `UserError` instead of generic `FrameworkError`
14//! 3. **Business logic** - Validation, transformation, orchestration
15//! 4. **Better API ergonomics** - Hide framework details from consumers
16//!
17//! ## Example: UserClient
18//!
19//! ```rust
20//! use actor_framework::ResourceClient;
21//! use actor_sample::model::{User, UserCreate, UserId};
22//! use actor_sample::user_actor::UserError;
23//!
24//! #[derive(Clone)]
25//! pub struct UserClient {
26//! inner: ResourceClient<User>,
27//! }
28//!
29//! impl UserClient {
30//! pub fn new(inner: ResourceClient<User>) -> Self {
31//! Self { inner }
32//! }
33//!
34//! // Domain-specific method with type-safe errors
35//! pub async fn create_user(&self, params: UserCreate) -> Result<UserId, UserError> {
36//! self.inner.create(params).await
37//! .map_err(|e| UserError::ActorCommunicationError(e.to_string()))
38//! }
39//! }
40//! ```
41//!
42//! ## The ActorClient Trait
43//!
44//! The [`actor_client::ActorClient`] trait provides a common interface for all clients,
45//! automatically implementing `get()` and `delete()` methods:
46//!
47//! ```rust,ignore
48//! #[async_trait]
49//! impl ActorClient<User> for UserClient {
50//! type Error = UserError;
51//!
52//! fn inner(&self) -> &ResourceClient<User> {
53//! &self.inner
54//! }
55//!
56//! fn map_error(e: FrameworkError) -> Self::Error {
57//! UserError::ActorCommunicationError(e.to_string())
58//! }
59//! }
60//! ```
61//!
62//! Now `UserClient` automatically gets:
63//! - `async fn get(&self, id: String) -> Result<Option<User>, UserError>`
64//! - `async fn delete(&self, id: String) -> Result<(), UserError>`
65//!
66//! ## Type-Safe Error Mapping
67//!
68//! Each client maps framework errors to domain-specific error types:
69//!
70//! ```rust,ignore
71//! // Framework error (generic)
72//! FrameworkError::Timeout
73//!
74//! // Mapped to domain error (specific)
75//! UserError::ActorCommunicationError("timeout".to_string())
76//! ```
77//!
78//! This allows consumers to pattern match on domain-specific errors:
79//!
80//! ```rust,ignore
81//! match user_client.get(id).await {
82//! Ok(Some(user)) => println!("Found: {}", user.name),
83//! Ok(None) => println!("User not found"),
84//! Err(UserError::ActorCommunicationError(msg)) => {
85//! println!("Communication failed: {}", msg)
86//! }
87//! Err(e) => println!("Other error: {}", e),
88//! }
89//! ```
90//!
91//! ## Orchestration Example: OrderClient
92//!
93//! Clients can orchestrate multiple actors to implement complex workflows:
94//!
95//! ```rust,ignore
96//! impl OrderClient {
97//! pub async fn create_order(&self, params: OrderCreate) -> Result<String, OrderError> {
98//! // 1. Validate user exists
99//! let user = self.user_client.get(params.user_id.clone()).await?
100//! .ok_or_else(|| OrderError::InvalidUser(params.user_id.clone()))?;
101//!
102//! // 2. Reserve product stock
103//! self.product_client.reserve_stock(
104//! params.product_id.clone(),
105//! params.quantity
106//! ).await?;
107//!
108//! // 3. Create the order
109//! match self.inner.create(params.clone()).await {
110//! Ok(id) => Ok(id),
111//! Err(e) => {
112//! // COMPENSATING TRANSACTION: Rollback stock reservation
113//! // If we fail to create the order, we must release the stock
114//! // so it doesn't get "leaked" (permanently reserved).
115//! let _ = self.product_client.release_stock(
116//! params.product_id,
117//! params.quantity
118//! ).await;
119//!
120//! Err(OrderError::ActorCommunicationError(e.to_string()))
121//! }
122//! }
123//! }
124//! }
125//! ```
126//!
127//! This keeps orchestration logic in the **client layer**, while actors remain
128//! focused on managing their own state.
129//!
130//! ## Benefits
131//!
132//! 1. **Type Safety** - Compile-time guarantees for domain operations
133//! 2. **Encapsulation** - Hide framework details from consumers
134//! 3. **Testability** - Easy to mock with [`MockClient`](actor_framework::mock::MockClient)
135//! 4. **Maintainability** - Domain logic lives in one place
136//! 5. **Discoverability** - IDE autocomplete shows domain methods
137
138pub mod order_client;
139pub mod product_client;
140pub mod user_client;
141
142pub use order_client::*;
143pub use product_client::*;
144pub use user_client::*;