| Crates.io | evento-macro |
| lib.rs | evento-macro |
| version | 2.0.0-alpha.12 |
| created_at | 2023-12-26 23:49:50.544275+00 |
| updated_at | 2026-01-21 23:46:18.353556+00 |
| description | A collection of libraries and tools that help you build DDD, CQRS, and event sourcing. |
| homepage | |
| repository | https://github.com/timayz/evento |
| max_upload_size | |
| id | 1081282 |
| size | 58,546 |
Procedural macros for the Evento event sourcing framework.
This crate provides macros that eliminate boilerplate when building event-sourced applications. It generates trait implementations, handler structs, and serialization code automatically.
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"
| 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 |
#[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:
AccountOpened, MoneyDeposited, MoneyWithdrawnAggregator trait implementation (provides aggregator_type())Event trait implementation (provides event_name())Debug, Clone, PartialEq, Default, and bitcode serializationThe aggregator type is formatted as "{package_name}/{enum_name}", e.g., "bank/BankAccount".
Pass additional derives as arguments:
#[evento::aggregator(serde::Serialize, serde::Deserialize)]
pub enum MyEvents {
// variants...
}
#[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
#[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 structhandle_money_deposited() constructor functionprojection::Handler<AccountBalanceView> trait implementation#[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?;
#[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(())
}
#[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)
}
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
When using these macros, your types must meet certain requirements:
#[aggregator]): Traits are automatically derivedDefault, Send, Sync, Cloneasync and return anyhow::Result<()>async, take Context first, and return anyhow::Result<()>Events are serialized using bitcode for compact binary representation. The #[aggregator] macro automatically adds the required bitcode derives:
bitcode::Encodebitcode::DecodeRust 1.75 or later.
This crate uses #![forbid(unsafe_code)] to ensure everything is implemented in 100% safe Rust.
See the LICENSE file in the repository root.