evento 1.8.0

A collection of libraries and tools that help you build DDD, CQRS, and event sourcing.
Documentation

Evento

Crates.io Documentation License

A collection of libraries and tools that help you build DDD, CQRS, and event sourcing applications in Rust.

More information about this crate can be found in the crate documentation.

Features

  • Event Sourcing: Store state changes as immutable events with complete audit trail
  • CQRS Pattern: Separate read and write models for scalable architectures
  • SQL Database Support: Built-in support for SQLite, PostgreSQL, and MySQL
  • Event Handlers: Async event processing with automatic retries
  • Event Subscriptions: Continuous event stream processing
  • Event Streaming: Real-time event streams (with stream feature)
  • Snapshots: Periodic state captures to optimize aggregate loading
  • Database Migrations: Automated schema management
  • Macro Free API: Clean, macro-free API (macros available optionally)
  • Type Safety: Fully typed events and aggregates with compile-time guarantees

Usage Example

use evento::{EventDetails, AggregatorName};
use serde::{Deserialize, Serialize};
use bincode::{Decode, Encode};

// Define events
#[derive(AggregatorName, Encode, Decode)]
struct UserCreated {
    name: String,
    email: String,
}

#[derive(AggregatorName, Encode, Decode)]
struct UserEmailChanged {
    email: String,
}

// Define aggregate
#[derive(Default, Serialize, Deserialize, Encode, Decode, Clone, Debug)]
struct User {
    name: String,
    email: String,
}

// Implement event handlers on the aggregate
#[evento::aggregator]
impl User {
    async fn user_created(&mut self, event: EventDetails<UserCreated>) -> anyhow::Result<()> {
        self.name = event.data.name;
        self.email = event.data.email;
        Ok(())
    }

    async fn user_email_changed(&mut self, event: EventDetails<UserEmailChanged>) -> anyhow::Result<()> {
        self.email = event.data.email;
        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_migrator::<sqlx::Sqlite>()?
        .run(&mut *conn, &evento::migrator::Plan::apply_all())
        .await?;

    let executor: evento::Sqlite = pool.into();

    // Create and save events
    let user_id = evento::create::<User>()
        .data(&UserCreated {
            name: "John Doe".to_string(),
            email: "john@example.com".to_string(),
        })?
        .metadata(&true)?
        .commit(&executor)
        .await?;

    // Update user
    evento::save::<User>(&user_id)
        .data(&UserEmailChanged {
            email: "newemail@example.com".to_string(),
        })?
        .metadata(&true)?
        .commit(&executor)
        .await?;

    // Load aggregate from events
    let user = evento::load::<User, _>(&executor, &user_id).await?;
    println!("User: {:?}", user.item);

    Ok(())
}

Event Handlers and Subscriptions

use evento::{Context, EventDetails, Executor};

// Define a handler function
#[evento::handler(User)]
async fn on_user_created<E: Executor>(
    context: &Context<'_, E>,
    event: EventDetails<UserCreated>,
) -> anyhow::Result<()> {
    println!("User created: {} ({})", event.data.name, event.data.email);
    // Trigger side effects, send emails, update read models, etc.
    Ok(())
}

// Subscribe to events
evento::subscribe("user-handlers")
    .handler(on_user_created())
    .run(&executor)
    .await?;

Feature Flags

  • macro (enabled by default)  Enable procedural macros for cleaner code
  • handler (enabled by default)  Enable event handlers with retry support
  • stream  Enable streaming support with tokio-stream
  • group  Enable event grouping functionality
  • sql  Enable all SQL database backends (SQLite, MySQL, PostgreSQL)
  • sqlite  SQLite support via sqlx
  • mysql  MySQL support via sqlx
  • postgres  PostgreSQL support via sqlx

Minimum Supported Rust Version

Evento's MSRV is 1.75.

Safety

This crate uses #![forbid(unsafe_code)] to ensure everything is implemented in 100% safe Rust.

Examples

The examples directory contains sample applications demonstrating various features:

  • todos - A complete todo application with CQRS, demonstrating command/query separation and event handlers

Getting Help

If you have questions or need help, please:

License

This project is licensed under the Apache-2.0 license.