actor_framework/
entity.rs

1//! # ActorEntity Trait
2//!
3//! The `ActorEntity` trait defines the contract that every resource (User, Product, Order, …) must implement to be managed by the generic `ResourceActor`. It specifies associated types for IDs, DTOs, actions, context, and errors, and provides lifecycle hooks (`on_create`, `on_update`, `on_delete`, `handle_action`). Implementing this trait enables the framework to offer a uniform CRUD + Action API for any domain model.
4//!
5//! Trait that any resource entity must implement to be managed by ResourceActor.
6//!
7//! # Architecture Note
8//! Why do we need this trait?
9//! By defining a contract (`ActorEntity`) that all our resource types (User, Product, Order)
10//! must satisfy, we can write the `ResourceActor` logic *once* and reuse it everywhere.
11//! This is "Polymorphism" in action.
12//!
13//! We use "Associated Types" (type Id, type Create, etc.) to enforce type safety.
14//! A `User` entity requires a `UserCreate` payload, and you can't accidentally send it
15//! a `ProductCreate` payload. The compiler prevents this class of bugs entirely.
16//!
17//! # Provided Methods (Hooks)
18//! This trait includes **Provided Methods** (methods with default implementations) for lifecycle hooks:
19//! - [`ActorEntity::on_create`]
20//! - [`ActorEntity::on_delete`]
21//!
22//! You do **not** need to implement these methods unless you want to customize behavior.
23//! The default implementation does nothing (`Ok(())`).
24
25use async_trait::async_trait;
26use std::fmt::{Debug, Display};
27use std::hash::Hash;
28
29/// Trait that any resource entity must implement to be managed by ResourceActor.
30///
31/// # Architecture Note
32/// By defining a contract (`ActorEntity`) that all our resource types (User, Product, Order)
33/// must satisfy, we can write the `ResourceActor` logic *once* and reuse it everywhere.
34///
35/// # Async & Context
36/// This trait is `#[async_trait]` to allow asynchronous operations in hooks (e.g., calling other actors).
37/// It also defines a `Context` type, which is injected into every hook. This allows "Late Binding"
38/// of dependencies (passing clients to `run()` instead of `new()`).
39#[async_trait]
40pub trait ActorEntity: Clone + Send + Sync + 'static {
41    /// The unique identifier for this entity (e.g., String, Uuid, u64).
42    /// Must be convertible from u32 for automatic ID generation.
43    type Id: Eq + Hash + Clone + Send + Sync + Display + Debug + From<u32>;
44
45    /// The data required to create a new instance (DTO - Data Transfer Object).
46    type Create: Send + Sync + Debug;
47
48    /// The data required to update an existing instance.
49    type Update: Send + Sync + Debug;
50
51    /// Enum representing resource-specific operations (e.g., `ReserveStock`).
52    type Action: Send + Sync + Debug;
53
54    /// The result type returned by custom actions.
55    type ActionResult: Send + Sync + Debug;
56
57    /// The runtime context (dependencies) injected into the actor.
58    /// Use `()` if no dependencies are needed.
59    type Context: Send + Sync;
60
61    /// The error type for this entity.
62    /// Must implement std::error::Error for proper error propagation.
63    ///
64    /// # Design Note: Error Granularity
65    ///
66    /// The framework enforces a **Per-Actor Error Type** (one enum for the whole actor) rather than
67    /// **Per-Message Error Types** (a specific error for each action).
68    ///
69    /// **Why?**
70    /// - **Simplicity**: Reduces boilerplate. You don't need to define 10 different error enums for 10 actions.
71    /// - **Ergonomics**: Clients deal with a single `UserError` type, making pattern matching easier.
72    ///
73    /// **Trade-off**:
74    /// This means `UserError` must be the union of all possible errors. If `ActionA` can only fail with `ErrorX`,
75    /// but `ActionB` can fail with `ErrorY`, the return type for both is `Result<..., UserError>`, which technically
76    /// allows `ErrorY` to be returned from `ActionA`. In practice, this theoretical loss of precision is worth
77    /// the massive reduction in code complexity.
78    type Error: std::error::Error + Send + Sync + 'static;
79
80    /// Construct the full Entity from the ID and Payload.
81    /// This is called synchronously before `on_create`.
82    fn from_create_params(id: Self::Id, params: Self::Create) -> Result<Self, Self::Error>;
83
84    // --- Lifecycle Hooks (Async) ---
85
86    /// Called immediately after the entity is created and initialized.
87    /// Use this hook to perform validation or side effects (e.g., checking other actors).
88    async fn on_create(&mut self, _ctx: &Self::Context) -> Result<(), Self::Error> {
89        Ok(())
90    }
91
92    /// Called when an update request is received.
93    async fn on_update(
94        &mut self,
95        update: Self::Update,
96        _ctx: &Self::Context,
97    ) -> Result<(), Self::Error>;
98
99    /// Called immediately before the entity is removed from the system.
100    async fn on_delete(&self, _ctx: &Self::Context) -> Result<(), Self::Error> {
101        Ok(())
102    }
103
104    // --- Action Handler (Async) ---
105
106    /// Handle a custom resource-specific action.
107    async fn handle_action(
108        &mut self,
109        action: Self::Action,
110        _ctx: &Self::Context,
111    ) -> Result<Self::ActionResult, Self::Error>;
112}