evento-macro

Crates.ioevento-macro
lib.rsevento-macro
version2.0.0-alpha.12
created_at2023-12-26 23:49:50.544275+00
updated_at2026-01-21 23:46:18.353556+00
descriptionA collection of libraries and tools that help you build DDD, CQRS, and event sourcing.
homepage
repositoryhttps://github.com/timayz/evento
max_upload_size
id1081282
size58,546
(snapiz)

documentation

https://docs.rs/evento

README

evento-macro

Procedural macros for the Evento event sourcing framework.

Overview

This crate provides macros that eliminate boilerplate when building event-sourced applications. It generates trait implementations, handler structs, and serialization code automatically.

Installation

This crate is typically used through the main evento crate with the macro feature enabled (on by default):

[dependencies]
evento = "2"

Or use this crate directly:

[dependencies]
evento-macro = "2"

Macros

Macro Type Purpose
#[evento::aggregator] Attribute Transform enum into event structs
#[evento::handler] Attribute Create projection handler from async function
#[evento::subscription] Attribute Create subscription handler for specific events
#[evento::subscription_all] Attribute Create subscription handler for all events
#[evento::projection] Attribute Add cursor field and implement ProjectionCursor
#[evento::snapshot] Attribute Implement snapshot restoration
#[derive(Cursor)] Derive Generate cursor struct and trait implementations
#[evento::debug_handler] Attribute Like handler with debug output
#[evento::debug_snapshot] Attribute Like snapshot with debug output

Usage

Defining Events with #[evento::aggregator]

Transform an enum into individual event structs with all required trait implementations:

#[evento::aggregator]
pub enum BankAccount {
    /// Event raised when a new bank account is opened
    AccountOpened {
        owner_id: String,
        owner_name: String,
        initial_balance: i64,
    },

    MoneyDeposited {
        amount: i64,
        transaction_id: String,
    },

    MoneyWithdrawn {
        amount: i64,
        transaction_id: String,
    },
}

This generates:

  • Individual structs: AccountOpened, MoneyDeposited, MoneyWithdrawn
  • Aggregator trait implementation (provides aggregator_type())
  • Event trait implementation (provides event_name())
  • Automatic derives: Debug, Clone, PartialEq, Default, and bitcode serialization

The aggregator type is formatted as "{package_name}/{enum_name}", e.g., "bank/BankAccount".

Additional Derives

Pass additional derives as arguments:

#[evento::aggregator(serde::Serialize, serde::Deserialize)]
pub enum MyEvents {
    // variants...
}

Projection State with #[evento::projection]

Automatically add cursor tracking to projection structs:

#[evento::projection]
#[derive(Debug)]
pub struct AccountBalanceView {
    pub balance: i64,
    pub owner: String,
}

// Generates:
// - Adds `pub cursor: String` field
// - Implements `ProjectionCursor` trait
// - Adds `Default` and `Clone` derives

Creating Projection Handlers with #[evento::handler]

Projection handlers are used to build read models by replaying events:

use evento::metadata::Event;

#[evento::handler]
async fn handle_money_deposited(
    event: Event<MoneyDeposited>,
    projection: &mut AccountBalanceView,
) -> anyhow::Result<()> {
    projection.balance += event.data.amount;
    Ok(())
}

// Register with a projection
let result = Projection::<_, AccountBalanceView>::new::<BankAccount>("account-123")
    .handler(handle_money_deposited())
    .execute(&executor)
    .await?;

The macro generates:

  • HandleMoneyDepositedHandler struct
  • handle_money_deposited() constructor function
  • projection::Handler<AccountBalanceView> trait implementation

Creating Subscription Handlers with #[evento::subscription]

Subscription handlers process events in real-time with side effects:

use evento::{Executor, metadata::Event, subscription::Context};

#[evento::subscription]
async fn on_money_deposited<E: Executor>(
    context: &Context<'_, E>,
    event: Event<MoneyDeposited>,
) -> anyhow::Result<()> {
    // Perform side effects: send notifications, update read models, etc.
    println!("Deposited: {}", event.data.amount);
    Ok(())
}

// Register with a subscription
let subscription = SubscriptionBuilder::<Sqlite>::new("deposit-notifier")
    .handler(on_money_deposited())
    .routing_key("accounts")
    .start(&executor)
    .await?;

Handling All Events with #[evento::subscription_all]

Handle all events from an aggregate type without deserializing:

use evento::{Executor, metadata::RawEvent, subscription::Context};

#[evento::subscription_all]
async fn on_any_account_event<E: Executor>(
    context: &Context<'_, E>,
    event: RawEvent<BankAccount>,
) -> anyhow::Result<()> {
    println!("Event {} on account {}", event.name, event.aggregator_id);
    Ok(())
}

Snapshot Restoration with #[evento::snapshot]

Implement snapshot restoration for projections:

use evento::context::RwContext;
use std::collections::HashMap;

#[evento::snapshot]
async fn restore(
    context: &RwContext,
    id: String,
    aggregators: &HashMap<String, String>,
) -> anyhow::Result<Option<AccountBalanceView>> {
    // Query snapshot from your storage
    // Return None to rebuild from events
    Ok(None)
}

Debug Macros

Use #[evento::debug_handler] or #[evento::debug_snapshot] to output the generated code to a file for inspection:

#[evento::debug_handler]
async fn handle_event(
    event: Event<MyEvent>,
    projection: &mut MyView,
) -> anyhow::Result<()> {
    // ...
}
// Generated code written to: target/evento_debug_handler_macro.rs

Requirements

When using these macros, your types must meet certain requirements:

  • Events (from #[aggregator]): Traits are automatically derived
  • Projections: Must implement Default, Send, Sync, Clone
  • Projection handlers: Must be async and return anyhow::Result<()>
  • Subscription handlers: Must be async, take Context first, and return anyhow::Result<()>

Serialization

Events are serialized using bitcode for compact binary representation. The #[aggregator] macro automatically adds the required bitcode derives:

  • bitcode::Encode
  • bitcode::Decode

Minimum Supported Rust Version

Rust 1.75 or later.

Safety

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

License

See the LICENSE file in the repository root.

Commit count: 182

cargo fmt