MiniCQRS/ES - Simplifying CQRS for Rust
Simple, minimal, opinionated micro-framework to implement CQRS/ES in Rust. There are already a lot of opinionated libraries to do that but, in fact, I didn't agree with their opinions and I've seen an opportunity to improve my knowledge and practice with Rust.
This lightweight library offers the necessary components abstractions and minimal glue to streamline the implementation of CQRS architecture, making it a breeze to manage your application's data flow.
Features
-
It's a microframework, so micro that you mostly get only the frame, the work is on you:
- you write your own implementations;
- you chose the libraries, storage engines and other external tools;
- you can pick only the pieces you need (aggregate, command, events, queries, etc...).
-
Almost everything is async using Tokio runtime.
Architecture
-
Aggregates: Define your domain entities as aggregates, handle state changes, and apply events with straightforward traits.
-
Commands: Implement custom commands to change the state of your aggregates with minimal effort.
-
Event Store: Store and retrieve your events efficiently for seamless event-sourcing.
-
Snapshot Store: Optionally use snapshots to speed up aggregate state recovery.
-
Event Handling: Easily manage and consume events generated by your aggregates, to execute actions and real-time updates.
-
Queries: Implement custom queries to retrieve data from your read models.
Status
The library is somewhat usable for experimenting on some real use cases, but I wouldn't recommend it for production use yet, API can't be considered stable, hence it's still under active development, sudden breaking changes can be introduced.
Installation
Run the following Cargo command in your project directory:
cargo add mini_cqrs_es
Or add the following line to your Cargo.toml
:
[dependencies]
mini_cqrs_es = "0.7.0"
Usage
Once you have added the library to your project's dependencies, you can start using it by importing the library:
use mini_cqrs_es::*;
// or importing single components:
// use mini_cqrs_es::{Aggregate, etc...};
Being made almost entirely of traits, MiniCQRS/ES is very flexible but, of course, it also requires some boilerplate. For this reason, you can check out the examples directory for more details.
When you have implemented the various traits from the library, you can finally build your CQRS architecture.
Here's a snippet inspired by the game example:
// An implementation of the EvenStore trait
let event_store = InMemoryEventStore::new();
// An implementation of the SnapshotStore trait
let snapshot_store = InMemorySnapshotStore::<GameAggregate>::new();
// An AggregateManager that supports the SnapshotStore is already implemented by MiniCQRS/ES
let aggregate_manager = SnapshotAggregateManager::new(snapshot_store);
// An implementation of the Repository trait to handle read models
let repo = Arc::new(Mutex::new(InMemoryRepository::new()));
// A set of event consumers
let event_consumers = GameEventConsumersGroup {
main: GameMainConsumer::new(repo.clone()),
print: PrintEventConsumer {},
};
// The Cqrs type is provided by MiniCQRS/ES and wraps the previous components
let mut cqrs = Cqrs::new(aggregate_manager, event_store, event_consumers);
// Build a Command along with some arguments
let player_1 = Player { id: "player_1".to_string(), points: 0, };
let player_2 = Player { id: "player_2".to_string(), points: 0, };
let aggregate_id = Uuid::new_v4();
// every Command implementation knows the type of the target Aggregate
let cmd = CmdStartGame { player_1: player_1, player_2: player_2, goal: 3, };
// Execute the Command
cqrs.execute(aggregate_id, &cmd).await?;
// Query the read model
// every Query implementation can have its own shape, initialization logic and read model type as output
let query = GetGameQuery::new(aggregate_id, repo.clone());
let result = cqrs.query(&query).await?.unwrap();
Please note that, except for the Cqrs
and SnapshotAggregateManager
types, everything else is an implementation of some trait.
In particular, the InMemory*
types have been implemented to simulate storage. In a real use case scenario you will probably build wrappers around
some database clients or other storage solutions.
Documentation
Code is documented and is being improved whenever is needed. To see a complete implementation you can check the examples.
Testing
Considering that this library is made of traits, the only way to check they work is to run the examples.
Contributing
If you find any bugs or have any suggestions, please open an issue.
License
This project is open-source and available under the MIT License.