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}