Home Features Docs Demo
GitHub
Dark

Canon

Event sourcing for Rust.
Macro-driven aggregates, guaranteed delivery via outbox pattern, and battle-tested infrastructure from Kafka to Cassandra.

Get Started → See It Live
v0.1.0 · Apache 2.0 · Pure Rust
How it works

The Event Sourcing Pipeline

An external event arrives, an event handler reacts, a command is produced, a command handler emits an event, the outbox guarantees delivery. Nothing is lost.

react to external event
🌐
External Event
Adaptor
📥
Inbox
👁
Event Handler
Command
handle command & guarantee delivery
📥
Inbox
Command Handler
Event
🔒
Outbox (ACID)
Outbound Kafka
📚
Event Store
📈
Projections
📡
Publisher
Developer experience

Four Macros. Zero Boilerplate.

Define your aggregate, declare commands and events, write business logic. Canon generates all the wiring, dispatch, and infrastructure plumbing.

1
Define your aggregate
#[aggregate(snapshot_every = 50)] pub struct Ship { status: ShipStatus, fuel_level: f32, current_station: Option<StationId>, }
2
Declare commands
#[command(Ship, version = 1, produces = [ShipDeparted])] pub struct DepartForStation { pub destination: StationId, }
3
Write business logic
#[command_handler(Ship, version = 1)] impl DepartForStationHandler { type Error = FleetError; fn handle( &self, state: &Ship, cmd: DepartForStation, ) -> Result<ShipDeparted, FleetError> { if state.status != Docked { return Err(FleetError::NotDocked); } Ok(ShipDeparted { destination: cmd.destination }) } }
4
Declare events
#[event(Ship, version = 1)] pub struct ShipDeparted { pub destination: StationId, } #[event_combiner(Ship, version = 1)] impl ShipDeparted { fn combine(&self, state: &mut Ship) { state.status = ShipStatus::InFlight; } }
Built-in capabilities

Everything You Need

Production-grade event sourcing out of the box. No assembly required.

Outbox Pattern
Commands and events committed in a single ACID transaction. The outbox processor drains to Kafka with guaranteed delivery. Zero dual-write bugs, ever.
canon-core
Snapshotting
Automatic snapshots every N events. Hydrate aggregates with millions of events in milliseconds. Configurable per aggregate with a single macro attribute.
snapshot_every = N
Oversight Gates
Accumulate events in a window, inspect them, then decide: dispatch, wait, or discard. Correlation-keyed windows with TTL and dead-letter fallback.
window_ttl
Dead Letters
Failed events don't vanish. Automatic retry with crash-safe counters, then dead-letter storage. Requeue or discard via admin API. Full audit trail.
canon-deadletter
Counterfactual Replay
"What if we'd sent a different command?" Hydrate to a branch point, substitute a command, replay forward, and diff the resulting command streams.
CounterfactualReplay
Cross-Service Events
Bounded contexts communicating via Kafka. Each service owns its storage, publishes to its own topic. Adaptor crates bridge external events into the local inbox.
DDD
Modular by design

Trait Crate Architecture

Strict DAG dependency graph. Every infrastructure concern has a trait crate and a concrete implementation. Swap Cassandra for DynamoDB. Swap Kafka for Pulsar. The core never changes.

canon-core
Traits, types, proc macros, in-memory impls
event-store
Cassandra
command-store
YugabyteDB
snapshot-store
YugabyteDB
inbox
YugabyteDB
inbound-queue
Kafka (rskafka)
outbound-queue
Kafka (rskafka)
projection-store
YugabyteDB
publisher
Kafka (rskafka)
adaptor
Kafka (rskafka)
deadletter
YugabyteDB
canon-test
In-memory harness
See it in action

Fleet Operations Demo

A real-time supply chain game powered entirely by Canon's event sourcing pipeline. Every ship movement, every stock drain, every oversight gate — real events flowing through Kafka, Cassandra, and five independent services.

Alpha Depot
Beta Relay
Gamma Outpost
Delta Prime
Play the Demo →