https://github.com/nanlong/eventide
Pragmatic Rust toolkit for Domain-Driven Design with first-class event sourcing and CQRS. Hexagonal architecture, async-native, zero database dependencies in the domain layer.
https://github.com/nanlong/eventide
aggregate async clean-architecture cqrs ddd domain-driven-design event-driven event-sourcing hexagonal-architecture proc-macros rust tokio
Last synced: 26 days ago
JSON representation
Pragmatic Rust toolkit for Domain-Driven Design with first-class event sourcing and CQRS. Hexagonal architecture, async-native, zero database dependencies in the domain layer.
- Host: GitHub
- URL: https://github.com/nanlong/eventide
- Owner: nanlong
- License: apache-2.0
- Created: 2025-09-28T20:02:04.000Z (10 months ago)
- Default Branch: main
- Last Pushed: 2026-05-02T05:54:10.000Z (3 months ago)
- Last Synced: 2026-06-04T05:14:06.085Z (about 2 months ago)
- Topics: aggregate, async, clean-architecture, cqrs, ddd, domain-driven-design, event-driven, event-sourcing, hexagonal-architecture, proc-macros, rust, tokio
- Language: Rust
- Homepage: https://docs.rs/eventide
- Size: 825 KB
- Stars: 5
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE-APACHE
Awesome Lists containing this project
README
# eventide
[](https://crates.io/crates/eventide)
[](https://docs.rs/eventide)
[](#license)
> 中文版本: [README.zh.md](README.zh.md)
A pragmatic Rust toolkit for **Domain-Driven Design** with first-class
support for **event sourcing** and **CQRS**, organised as a hexagonal
workspace.
```text
eventide-application → eventide-domain ← eventide-macros
↑
└──── (optional) infra-sqlx adapters
```
## Workspace layout
| Crate | What it ships |
|---------------------------------------------------|----------------------------------------------------------------------------------------|
| [`eventide`](eventide/) | Umbrella crate. Re-exports the three layers and a curated `prelude`. |
| [`eventide-domain`](eventide-domain/) | Pure domain layer: aggregates, entities, value objects, events, repository traits. |
| [`eventide-application`](eventide-application/) | Application layer: command bus, query bus, handlers, application context. |
| [`eventide-macros`](eventide-macros/) | Procedural macros (`#[entity]`, `#[entity_id]`, `#[domain_event]`, `#[value_object]`). |
## Quick start
Most users only need to depend on the umbrella crate:
```toml
[dependencies]
eventide = "0.1"
```
> A single dependency covers the common path. Macro-generated
> `Serialize` / `Deserialize` derives route through `eventide`'s internal
> `serde` re-export, so the macros work without a direct `serde`
> dependency. `eventide::tokio` exposes the Tokio runtime (with the
> `eventing` feature, default), and `eventide::async_trait` lets you
> write `#[async_trait]` on your own trait impls. Add `serde` / `tokio` /
> `async-trait` to your own `Cargo.toml` only when you reach for them
> outside these re-exports (extra derives, custom `#[serde(...)]`
> attributes, advanced runtime features). See
> [`eventide/README.md`](eventide/README.md) for details.
A minimal aggregate looks like this:
```rust
use eventide::prelude::*;
#[entity_id]
struct AccountId(String);
#[entity(id = AccountId)]
#[derive(Clone)]
struct BankAccount {
balance: i64,
}
#[derive(Debug)]
enum AccountCommand {
Deposit(i64),
Withdraw(i64),
}
#[domain_event(version = 1)]
enum AccountEvent {
#[event(event_type = "account.deposited")]
Deposited { amount: i64 },
#[event(event_type = "account.withdrawn")]
Withdrawn { amount: i64 },
}
impl Aggregate for BankAccount {
const TYPE: &'static str = "bank_account";
type Command = AccountCommand;
type Event = AccountEvent;
type Error = DomainError;
fn execute(&self, cmd: AccountCommand) -> Result, DomainError> {
match cmd {
AccountCommand::Deposit(n) if n > 0 => Ok(vec![AccountEvent::Deposited {
id: uuid::Uuid::new_v4().to_string(),
aggregate_version: self.version().next().value(),
amount: n,
}]),
AccountCommand::Withdraw(n) if n > 0 && self.balance >= n => {
Ok(vec![AccountEvent::Withdrawn {
id: uuid::Uuid::new_v4().to_string(),
aggregate_version: self.version().next().value(),
amount: n,
}])
}
_ => Err(DomainError::invalid_command("invalid amount or insufficient")),
}
}
fn apply(&mut self, event: &AccountEvent) {
match event {
AccountEvent::Deposited { aggregate_version, amount, .. } => {
self.balance += amount;
self.version = Version::from_value(*aggregate_version);
}
AccountEvent::Withdrawn { aggregate_version, amount, .. } => {
self.balance -= amount;
self.version = Version::from_value(*aggregate_version);
}
}
}
}
```
## Build, test, run examples
Requires Rust 1.85+ (workspace uses Rust 2024 edition).
```bash
cargo build --workspace
cargo test --workspace --all-features
# Domain examples
cargo run -p eventide-domain --example event_upcasting
cargo run -p eventide-domain --example event_repository
cargo run -p eventide-domain --example snapshot_repository
cargo run -p eventide-domain --example eventing_inmemory
# Application examples
cargo run -p eventide-application --example inmemory_command_bus
cargo run -p eventide-application --example inmemory_query_bus
```
## Why eventide
- **Hexagonal-friendly.** The domain layer defines abstractions (`EventRepository`, `SnapshotRepository`, `AggregateRepository`); infrastructure crates implement them.
- **Event sourcing built in.** Aggregates emit events, the engine persists them, and an upcasting chain handles schema evolution without touching historical data.
- **CQRS by default.** Separate `CommandBus` and `QueryBus` with type-safe handler registration.
- **Procedural macros that disappear.** `#[entity]`, `#[entity_id]`, `#[domain_event]`, and `#[value_object]` remove the boilerplate so business invariants stay readable.
- **Async-native.** Tokio-based event engine with cooperative dispatch, retry, and dead-letter handling.
- **No vendor lock-in.** The domain crate has zero database dependencies; pick (or write) the infrastructure adapter you need.
## Feature flags (umbrella crate)
| Flag | Default | What it does |
|----------------|---------|----------------------------------------------------------------------------------------------------|
| `eventing` | yes | Asynchronous event subsystem (bus / engine / dispatcher / reclaimer) on top of `tokio`. |
| `macros` | yes | Re-export `eventide-macros` as `eventide::macros`. |
| `application` | yes | Re-export `eventide-application` as `eventide::application`. |
| `infra-sqlx` | no | Opt-in `sqlx` conversions on serialized events / snapshots for Postgres-backed event stores. |
Pick individual sub-crates instead of the umbrella when you want finer control:
```toml
[dependencies]
eventide-domain = "0.1"
eventide-macros = "0.1"
# Skip eventide-application if you do not need the bus.
```
## Documentation
- API reference:
- Per-layer docs:
-
-
-
Each sub-crate also ships its own `README.md` with focused documentation.
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or )
- MIT license ([LICENSE-MIT](LICENSE-MIT) or )
at your option.
## Contributing
Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the work by you, as defined in the Apache-2.0
license, shall be dual licensed as above, without any additional terms or
conditions.