| Crates.io | evento |
| lib.rs | evento |
| version | 2.0.0-alpha.12 |
| created_at | 2022-04-14 12:11:12.688084+00 |
| updated_at | 2026-01-21 23:46:27.576558+00 |
| description | Event sourcing and CQRS toolkit with SQL persistence, projections, and subscriptions |
| homepage | |
| repository | https://github.com/timayz/evento |
| max_upload_size | |
| id | 567495 |
| size | 76,992 |
Event sourcing and CQRS toolkit with SQL persistence, projections, and subscriptions.
More information about this crate can be found in the crate documentation.
Add to your Cargo.toml:
[dependencies]
evento = { version = "2", features = ["sqlite"] }
bitcode = "0.6"
use evento::{Executor, metadata::{Event, Metadata}, projection::Projection};
// Define events using an enum
#[evento::aggregator]
pub enum User {
UserCreated {
name: String,
email: String,
},
UserEmailChanged {
email: String,
},
}
// Define a view/projection with cursor tracking
#[evento::projection]
#[derive(Debug)]
pub struct UserView {
pub name: String,
pub email: String,
}
// Define projection handlers - they update state from events
#[evento::handler]
async fn on_user_created(
event: Event<UserCreated>,
view: &mut UserView,
) -> anyhow::Result<()> {
view.name = event.data.name.clone();
view.email = event.data.email.clone();
Ok(())
}
#[evento::handler]
async fn on_email_changed(
event: Event<UserEmailChanged>,
view: &mut UserView,
) -> anyhow::Result<()> {
view.email = event.data.email.clone();
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Setup SQLite executor
let pool = sqlx::SqlitePool::connect("sqlite:events.db").await?;
let mut conn = pool.acquire().await?;
// Run migrations
evento::sql_migrator::new()?
.run(&mut *conn, &evento::migrator::Plan::apply_all())
.await?;
let executor: evento::Sqlite = pool.into();
// Create and save events
let user_id = evento::create()
.event(&UserCreated {
name: "John Doe".to_string(),
email: "john@example.com".to_string(),
})
.metadata(&Metadata::default())
.commit(&executor)
.await?;
// Update user
evento::aggregator(&user_id)
.original_version(1)
.event(&UserEmailChanged {
email: "newemail@example.com".to_string(),
})
.metadata(&Metadata::default())
.commit(&executor)
.await?;
// Build projection and load state
let result = Projection::<_, UserView>::new::<User>(&user_id)
.handler(on_user_created())
.handler(on_email_changed())
.execute(&executor)
.await?;
if let Some(user) = result {
println!("User: {} ({})", user.name, user.email);
}
Ok(())
}
Subscriptions process events in real-time with side effects:
use std::time::Duration;
use evento::{Executor, metadata::Event, subscription::{Context, SubscriptionBuilder}};
// Subscription handlers receive context and can perform side effects
#[evento::subscription]
async fn notify_user_created<E: Executor>(
context: &Context<'_, E>,
event: Event<UserCreated>,
) -> anyhow::Result<()> {
println!("New user: {}", event.data.name);
// Send welcome email, update external systems, etc.
Ok(())
}
// Start a subscription that continuously processes events
let subscription = SubscriptionBuilder::<evento::Sqlite>::new("user-notifier")
.handler(notify_user_created())
.routing_key("users")
.chunk_size(100)
.retry(5)
.delay(Duration::from_secs(10))
.start(&executor)
.await?;
// On application shutdown
subscription.shutdown().await?;
Use subscription_all to process all events from an aggregate without deserializing:
use evento::{Executor, metadata::RawEvent, subscription::Context};
#[evento::subscription_all]
async fn audit_user_events<E: Executor>(
context: &Context<'_, E>,
event: RawEvent<User>,
) -> anyhow::Result<()> {
println!("Event {} on user {}", event.name, event.aggregator_id);
Ok(())
}
macro (enabled by default) - Procedural macros for cleaner codegroup - Multi-executor support for querying across databasesrw - Read-write split executor for CQRS patternssql - Enable all SQL database backends (SQLite, MySQL, PostgreSQL)sqlite - SQLite support via sqlxmysql - MySQL support via sqlxpostgres - PostgreSQL support via sqlxfjall - Embedded key-value storage with FjallEvento's MSRV is 1.75.
This crate uses #![forbid(unsafe_code)] to ensure everything is implemented in 100% safe Rust.
The examples directory contains sample applications demonstrating various features:
If you have questions or need help, please:
This project is licensed under the Apache-2.0 license.