Sourcerer is a lightweight, framework-agnostic event-sourcing toolkit written in Rust. It focuses on the write side of a typical CQRS architecture—aggregates, event stores, snapshots and up-casting—without dictating how you build projections or HTTP layers.
Pluggable stores – In-memory (tests),
sled(embedded) andsqlx-Postgres back-ends behind one trait.Snapshots & up-casters – Reduce rebuild time and evolve your event schema safely.
Optimistic locking – Automatic version checks to prevent lost updates.
Ergonomic macros –
#[derive(Event)]implements boilerplate for you:use sourcerer::Event;use sourcerer_derive::Event;// re-exported with the "derive" featureuse serde::{Serialize,Deserialize};#[derive(Serialize,Deserialize,Event)]#[event(version = 2, source = "urn:my-app")]// enum-level defaultsenumAccountEvent{Opened,#[event(version = 3)]// variant overrideCredited{amount:u64},#[event(source = "urn:custom")]// another overrideDebited(u64),}
[dependencies]
sourcerer = "0.1"# Optional back-ends - disable default features when you only need one.# default = ["in-memory"]# features = ["sled-storage", "postgres-storage", "derive"]Add sourcerer-deriveonly if you disabled the derive feature on the main crate.
sourcerer-derive = "0.1"use std::sync::Arc;use uuid::Uuid;use sourcerer::{Aggregate, async_trait};use sourcerer::store::in_memory::{InMemoryEventStore,InMemorySnapshotStore};use sourcerer::repository::GenericRepository;#[derive(Clone,Debug, serde::Serialize, serde::Deserialize, sourcerer_derive::Event)]#[event(version = 1)]enumBankEvent{Opened{balance:u64},Credited(u64),Debited(u64)}#[derive(Default,Debug)]structBank{id:Uuid,balance:u64,version:i64}#[async_trait]implAggregateforBank{typeId = Uuid;typeEvent = BankEvent;typeCommand = ();// left out for brevitytypeSnapshot = ();// dittotypeError = std::convert::Infallible;fnid(&self) -> &Self::Id{&self.id}fnversion(&self) -> i64{self.version}fnapply(&mutself,ev:&Self::Event){match ev {BankEvent::Opened{ balance } => self.balance = *balance,BankEvent::Credited(amt) => self.balance += *amt,BankEvent::Debited(amt) => self.balance -= *amt,}self.version += 1;}asyncfnhandle(&self,_cmd:Self::Command) -> Result<Vec<Self::Event>,Self::Error>{Ok(vec![])}fnfrom_snapshot(_:()) -> Self{Self::default()}fnsnapshot(&self) -> (){}}#[tokio::main]asyncfnmain() -> anyhow::Result<()>{let events = Arc::new(InMemoryEventStore::<Bank>::default());let snaps = Arc::new(InMemorySnapshotStore::<Bank>::default());let repo = GenericRepository::new(events,Some(snaps));let bank = Bank::default();
repo.save(&bank,vec![BankEvent::Opened{ balance:100}]).await?;Ok(())}- Built docs: https://docs.rs/sourcerer
- Examples in
examples/(to be added).
| Feature | Default? | Description |
|---|---|---|
in-memory | ✔ | Minimal, dependency-free store |
sled-storage | ❌ | Embedded persistent store using sled |
postgres-storage | ❌ | sqlx-based Postgres store |
derive | ✔ | Re-export sourcerer-derive |
Disable default features and opt-in as needed:
sourcerer = { version = "0.1", default-features = false, features = ["postgres-storage"] }- Streaming APIs (
impl Stream<Item = StoredEvent<_>>). - Auto-projection helpers.
- More derive macros (command helpers, snapshot versioning).
- Fork & clone.
just test(orcargo test --all-features).- Ensure
cargo clippy -D warningspasses.
Licensed under MIT.