Expand description
§EventCore
Type-safe, multi-stream event sourcing for Rust with dynamic consistency boundaries.
EventCore lets a single command read from and atomically write to multiple event streams in one optimistic-concurrency-controlled transaction. You describe what a command does — which streams it touches, how it folds past events into state, and what new events it produces — and EventCore handles the infrastructure: loading state, detecting concurrent writes, retrying on conflict, and committing atomically.
APIs exposed only through feature flags whose names start with
experimental- are disabled by default and are not covered by EventCore’s
stable compatibility guarantee while they retain that prefix.
§Core concepts
- Stream — an ordered, append-only sequence of events identified by a
StreamId. Each stream has a version that increments with every append. - Command — a unit of business logic implementing
CommandLogic. Itsapplymethod folds events into command-local state (the write model); itshandlemethod validates business rules and returns the new events to append. The streams a command may touch are declared with#[derive(Command)]and the#[stream]attribute. execute— the canonical entry point. It loads the declared streams, folds them into state, callshandle, and atomically appends the resulting events with optimistic concurrency control, retrying per the suppliedRetryPolicy.- Projection — a read model built by replaying events. The legacy,
backend-neutral API implements
Projectorand usesrun_projection. With thepostgresfeature,eventcore::postgres::projectionsadds a separate effect-plus-progress transactional runner without changing the legacy API. Read models and write models remain on separate code paths.
§Quick start: your first command
This example defines a Deposit command for a bank account, executes it
against the in-memory store, and is fully runnable. Add eventcore and
eventcore-memory to your Cargo.toml, then:
use eventcore::{
execute, Command, CommandError, CommandLogic, Event, NewEvents, RetryPolicy, StreamId,
};
use eventcore_memory::InMemoryEventStore;
use serde::{Deserialize, Serialize};
// 1. Define your domain events. Each event knows which stream it belongs to.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
enum BankAccountEvent {
MoneyDeposited { account_id: StreamId, amount: u32 },
}
impl Event for BankAccountEvent {
fn stream_id(&self) -> &StreamId {
match self {
BankAccountEvent::MoneyDeposited { account_id, .. } => account_id,
}
}
fn event_type_name() -> &'static str {
"BankAccountEvent"
}
}
// 2. Define a command. `#[derive(Command)]` generates the stream
// declarations from the `#[stream]`-tagged fields.
#[derive(Command)]
struct Deposit {
#[stream]
account_id: StreamId,
amount: u32,
}
// 3. Implement the business logic: how events fold into state (`apply`)
// and what events the command produces (`handle`).
impl CommandLogic for Deposit {
type Event = BankAccountEvent;
type State = ();
fn apply(&self, state: Self::State, _event: &Self::Event) -> Self::State {
state
}
fn handle(&self, _state: Self::State) -> Result<NewEvents<Self::Event>, CommandError> {
Ok(vec![BankAccountEvent::MoneyDeposited {
account_id: self.account_id.clone(),
amount: self.amount,
}]
.into())
}
}
// 4. Execute the command against a store.
let rt = tokio::runtime::Runtime::new().expect("runtime");
rt.block_on(async {
let store = InMemoryEventStore::new();
let account_id = StreamId::try_new("account-42").expect("valid stream id");
let command = Deposit { account_id, amount: 100 };
execute(&store, command, RetryPolicy::new())
.await
.expect("deposit to succeed");
});From here, see the user manual
for projections, multi-stream atomicity, and backend selection, or the
eventcore-demo crate for a complete bank application backed by PostgreSQL.
§Reading events directly
Most applications never read events by hand — execute does it for you.
When you do need a stream’s raw history (for tooling or a projection),
EventStore::read_stream
returns a lazy EventStream, an async Stream of events. To materialize
the whole history into a Vec, pass it to the collect_events helper.
§Backends
EventCore works with several EventStore implementations:
eventcore-memory— a separate zero-dependency crate added directly to yourCargo.toml(used in the quick start above) for tests and examples.postgresfeature — PostgreSQL backend with ACID transactions, re-exported aseventcore::postgres.sqlitefeature — SQLite backend with optional SQLCipher encryption, re-exported aseventcore::sqlite.
§Error handling
executereturnsOk(ExecutionResponse)on success; the response exposesExecutionResponse::attemptsso callers can observe how many retries occurred.CommandErroris returned byexecuteon failure — business-rule violations, concurrency conflicts (after retries are exhausted), and store failures.EventStoreError(ineventcore-types) is returned by backend operations, including version conflicts and event deserialization failures.
Macros§
- require
- Validates a business rule condition and returns early with a
CommandErrorwhen the condition is false.
Structs§
- Attempt
Number - Attempt number for retry operations (1-based).
- Command
State Replay Checkpoint - A command-state projection checkpoint after one stream was fully replayed.
- Command
State Snapshot - A durable command-state read-model projection.
- Command
State Snapshot Id - Stable identifier for a command-state projection.
- Delay
Milliseconds - Delay in milliseconds for retry or backoff operations.
- Event
Stream - An async stream of events read from a single stream, generic over the consumer’s event payload type.
- Execution
Response - The result of a successful
executecall. - Failure
Context - Context provided to error handler when event processing fails.
- NewEvents
- Collection of new events produced by a command.
- Projection
Config - Configuration for running projections via
run_projection. - Retry
Context - Context information passed to metrics hooks during retry lifecycle.
- Retry
Policy - Configuration for automatic retry behavior on concurrency conflicts.
- Stream
Declarations - Stream
Id - Stream identifier domain type.
- Stream
Position - Opaque cursor representing a location in an
EventReaderdelivery sequence.
Enums§
- Backoff
Strategy - Defines the delay strategy between retry attempts.
- Command
Error - Error type for command execution failures.
- Failure
Strategy - Strategy for handling event processing failures.
- Projection
Error - Error type for projection operations.
Constants§
- COMMAND_
STATE_ SNAPSHOT_ REFRESH_ THRESHOLD - Number of reconstructed events at which an opt-in command-state projection is first persisted.
Traits§
- Command
Logic - Trait defining the business logic of a command.
- Command
Streams - Infrastructure trait describing the streams required to execute a command.
- Event
- Event trait for domain-first event sourcing.
- Metrics
Hook - Callback trait for integrating with metrics systems during retry lifecycle.
- Projector
- Trait for transforming events into read model updates.
- Stream
Resolver - Trait for runtime stream discovery when static declarations are insufficient.
Functions§
- collect_
events - Collect every event from a stream into a
Vec, in stream-version order. - execute
- Execute a command against the event store with a custom retry policy.
- run_
projection - Runs a projector against a backend that provides events, checkpoints, and coordination.
Derive Macros§
- Command
- Entry point for
#[derive(Command)].