Skip to main content

backbone_core/
command.rs

1//! CQRS Command Pattern
2//!
3//! Provides traits for implementing the Command side of CQRS.
4//! Commands represent intentions to change state in the system.
5//!
6//! # Example
7//!
8//! ```ignore
9//! use backbone_core::{Command, CommandHandler};
10//!
11//! // Define a command
12//! pub struct CreateUserCommand {
13//!     pub email: String,
14//!     pub name: String,
15//! }
16//!
17//! impl Command for CreateUserCommand {
18//!     type Result = UserId;
19//! }
20//!
21//! // Implement the handler
22//! pub struct CreateUserHandler {
23//!     user_repository: Arc<dyn UserRepository>,
24//! }
25//!
26//! #[async_trait::async_trait]
27//! impl CommandHandler<CreateUserCommand> for CreateUserHandler {
28//!     type Error = UserError;
29//!
30//!     async fn handle(&self, command: CreateUserCommand) -> Result<UserId, Self::Error> {
31//!         let user = User::new(command.email, command.name)?;
32//!         self.user_repository.create(user).await
33//!     }
34//! }
35//! ```
36
37use async_trait::async_trait;
38
39/// Marker trait for CQRS commands.
40///
41/// Commands represent an intent to change the system state.
42/// Each command should have a single handler.
43pub trait Command: Send + Sync {
44    /// The result type returned after successful command execution.
45    type Result: Send + Sync;
46}
47
48/// Handler for executing commands.
49///
50/// Each command type should have exactly one handler.
51/// Handlers contain the business logic for processing commands.
52#[async_trait]
53pub trait CommandHandler<C: Command>: Send + Sync {
54    /// Error type for command execution failures.
55    type Error: std::error::Error + Send + Sync;
56
57    /// Execute the command and return the result.
58    async fn handle(&self, command: C) -> Result<C::Result, Self::Error>;
59}
60
61/// Command dispatcher for routing commands to their handlers.
62///
63/// Provides a central point for command execution with
64/// optional middleware support (logging, validation, etc.).
65#[async_trait]
66pub trait CommandDispatcher: Send + Sync {
67    /// Dispatch a command to its handler.
68    async fn dispatch<C: Command>(
69        &self,
70        command: C,
71    ) -> Result<C::Result, Box<dyn std::error::Error + Send + Sync>>;
72}
73
74/// Trait for commands that can be validated before execution.
75pub trait ValidatableCommand: Command {
76    /// Validation error type.
77    type ValidationError: std::error::Error + Send + Sync;
78
79    /// Validate the command before execution.
80    fn validate(&self) -> Result<(), Self::ValidationError>;
81}
82
83/// Extension trait for handlers that support validated commands.
84#[async_trait]
85pub trait ValidatingCommandHandler<C: ValidatableCommand + 'static>: CommandHandler<C> {
86    /// Handle the command with validation.
87    async fn handle_validated(&self, command: C) -> Result<C::Result, Self::Error> {
88        // Note: Validation should be called by the dispatcher
89        self.handle(command).await
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    struct TestCommand {
98        value: i32,
99    }
100
101    impl Command for TestCommand {
102        type Result = i32;
103    }
104
105    struct TestHandler;
106
107    #[async_trait]
108    impl CommandHandler<TestCommand> for TestHandler {
109        type Error = std::io::Error;
110
111        async fn handle(&self, command: TestCommand) -> Result<i32, Self::Error> {
112            Ok(command.value * 2)
113        }
114    }
115
116    #[tokio::test]
117    async fn test_command_handler() {
118        let handler = TestHandler;
119        let command = TestCommand { value: 21 };
120        let result = handler.handle(command).await.unwrap();
121        assert_eq!(result, 42);
122    }
123}