EventCore Logo

EventCore

Multi-stream event sourcing with dynamic consistency boundaries

Crates.io Documentation License Build Status


Why EventCore?

Traditional event sourcing forces you to choose aggregate boundaries upfront, leading to complex workarounds when business logic spans multiple aggregates. EventCore eliminates this constraint with dynamic consistency boundaries - each command defines exactly which streams it needs, enabling atomic operations across multiple event streams.

🚀 Key Features

🔄 Multi-Stream Atomicity
Read from and write to multiple event streams in a single atomic operation. No more saga patterns for simple cross-aggregate operations.

🎯 Type-Safe Commands
Leverage Rust’s type system to ensure compile-time correctness. Illegal states are unrepresentable.

⚡ High Performance
Optimized for both in-memory and PostgreSQL backends with sophisticated caching and batching strategies.

🔍 Built-in CQRS
First-class support for projections and read models with automatic position tracking and replay capabilities.

🛡️ Production Ready
Battle-tested with comprehensive observability, monitoring, and error recovery mechanisms.

🧪 Testing First
Extensive testing utilities including property-based tests, chaos testing, and deterministic event stores.

Quick Example

#![allow(unused)]
fn main() {
use eventcore::prelude::*;

#[derive(Command)]
#[command(event = "BankingEvent")]
struct TransferMoney {
    from_account: AccountId,
    to_account: AccountId,
    amount: Money,
}

impl TransferMoney {
    fn read_streams(&self) -> Vec<StreamId> {
        vec![
            self.from_account.stream_id(),
            self.to_account.stream_id(),
        ]
    }
}

#[async_trait]
impl CommandLogic for TransferMoney {
    type State = BankingState;
    type Event = BankingEvent;

    async fn handle(
        &self,
        _: ReadStreams<Self::StreamSet>,
        state: Self::State,
        _: &mut StreamResolver,
    ) -> CommandResult<Vec<StreamWrite<Self::StreamSet, Self::Event>>> {
        // Validate business rules
        require!(state.balance(&self.from_account) >= self.amount,
            "Insufficient funds"
        );

        // Emit events - atomically written to both streams
        Ok(vec![
            emit!(self.from_account.stream_id(), 
                BankingEvent::Withdrawn { amount: self.amount }
            ),
            emit!(self.to_account.stream_id(),
                BankingEvent::Deposited { amount: self.amount }
            ),
        ])
    }
}
}

Getting Started

Use Cases

EventCore excels in domains where business operations naturally span multiple entities:

  • 💰 Financial Systems: Atomic transfers, double-entry bookkeeping, complex trading operations
  • 🛒 E-Commerce: Order fulfillment, inventory management, distributed transactions
  • 🏢 Enterprise Applications: Workflow engines, approval processes, resource allocation
  • 🎮 Gaming: Player interactions, economy systems, real-time state synchronization
  • 📊 Analytics Platforms: Event-driven architectures, audit trails, temporal queries

Performance

187,711
ops/sec (in-memory)
83
ops/sec (PostgreSQL)
12ms
avg latency
820,000
events/sec write

Community

Join our growing community of developers building event-sourced systems:

Resources

Supported By

EventCore is an open-source project supported by the community.


Built with ❤️ by the EventCore community

Released under the MIT License