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}