Expand description
The discern
crate is an implementation of the CQRS (Command Query Responsibility Segregation) pattern in Rust.
§Command Query Responsibility Segregation (CQRS)
CQRS is a pattern that separates the write operations (commands) from read operations (queries).
The CommandBus
is responsible for handling commands that change state in the system, while the QueryBus
is responsible for handling queries that retrieve data.
Commands are often associated with actions that modify the system’s state, while queries are used to retrieve data without making any modifications.
The CommandBus
and QueryBus
are designed to ensure that commands and queries are processed by their respective handlers, which are registered in handler registries.
This allows for decoupled, maintainable, and scalable code.
- CommandBus: Dispatches commands to their respective handlers.
- QueryBus: Dispatches queries to their respective handlers.
§Example: Handling Commands
The following example demonstrates how to define and handle commands in a CQRS-based system.
In this example, we create a CreateUserCommand
that is responsible for creating a new user.
The CreateUserCommandHandler
processes this command and returns the ID of the newly created user.
use discern::async_trait;
use discern::command_bus;
use discern::command::Command;
use discern::command::CommandHandler;
// Define the CreateUserCommand.
#[derive(Debug)]
struct CreateUserCommand {
username: String,
email: String,
}
// Define possible errors for the CreateUserCommand.
#[derive(Debug)]
enum CreateUserError {
UsernameAlreadyExists,
EmailAlreadyExists,
}
// Implement the Command trait for CreateUserCommand.
impl Command for CreateUserCommand {
type Metadata = u64; // Return the ID of the created user as metadata.
type Error = CreateUserError;
}
// Define a handler for the CreateUserCommand.
struct CreateUserCommandHandler {
// Add any dependencies needed by the handler.
}
#[async_trait]
impl CommandHandler<CreateUserCommand> for CreateUserCommandHandler {
async fn handle(&self, _command: CreateUserCommand) -> Result<u64, CreateUserError> {
// Handle command logic here, e.g., create a user, validate uniqueness, etc.
todo!("Implement user creation logic");
}
}
// Create a command bus with the CreateUserCommand and its handler.
let command_bus = command_bus! {
CreateUserCommand => CreateUserCommandHandler { /* ... */ },
};
// Create a command to add a new user.
let command = CreateUserCommand {
username: "alice".to_string(),
email: "alice@example.com".to_string(),
};
// Dispatch the command.
match command_bus.dispatch(command).await {
Ok(user_id) => println!("User created with ID: {}", user_id),
Err(err) => println!("Failed to create user: {:?}", err),
}
§Example: Handling Queries
//! # Example: Handling Queries
The following example demonstrates how to define and handle queries in a CQRS-based system.
In this example, we create a GetUserQuery
that is responsible for retrieving user information by user ID.
The GetUserQueryHandler
processes this query and returns the user’s information if found.
use discern::async_trait;
use discern::query::Query;
use discern::query::QueryHandler;
use discern::query_bus;
// Define the GetUserQuery.
#[derive(Debug)]
struct GetUserQuery {
user_id: u64,
}
// Define possible errors for the GetUserQuery.
#[derive(Debug)]
enum GetUserError {
UserNotFound,
}
// Define a User struct.
#[derive(Debug)]
struct User {
id: u64,
username: String,
email: String,
}
// Implement the Query trait for GetUserQuery.
impl Query for GetUserQuery {
type Output = User; // Return the user as output.
type Error = GetUserError;
}
// Define a handler for the GetUserQuery.
struct GetUserQueryHandler;
#[async_trait]
impl QueryHandler<GetUserQuery> for GetUserQueryHandler {
async fn handle(&self, _query: GetUserQuery) -> Result<User, GetUserError> {
// Handle query logic here, e.g., retrieve the user by ID.
todo!("Implement user retrieval logic");
}
}
// Create a query bus with the GetUserQuery and its handler.
let query_bus = query_bus! {
GetUserQuery => GetUserQueryHandler { /* ... */ },
};
// Create a query to retrieve the user with ID 1.
let query = GetUserQuery { user_id: 1 };
// Dispatch the query.
match query_bus.dispatch(query).await {
Ok(user) => println!("User found: {:?}", user),
Err(err) => println!("Failed to retrieve user: {:?}", err),
}
Modules§
- command
- The
command
module defines the core abstractions and components related to handling commands in the CQRS pattern. - macros
- The
macros
module provides convenient macros for creating and registering command and query buses. - query
- The
query
module defines the core abstractions and components related to handling queries in the CQRS pattern. - registry
- The
registry
module provides the infrastructure for managing and retrieving command and query handlers.
Macros§
- command_
bus - A macro for creating a
CommandBus
instance. - command_
registry - A macro for creating a
CommandHandlerRegistry
instance. - query_
bus - A macro for creating a
QueryBus
instance. - query_
registry - A macro for creating a
QueryHandlerRegistry
instance.
Attribute Macros§
- async_
trait - Re-exports the
async_trait
crate.