eventastic/lib.rs
1//! # Eventastic
2//!
3//! A type-safe event sourcing and CQRS library for Rust with PostgreSQL persistence.
4//!
5//! Eventastic provides strong consistency guarantees through mandatory transactions,
6//! built-in idempotency checking, and reliable side effect processing via the
7//! transactional outbox pattern.
8//!
9//! ## Quick Start
10//!
11//! ```rust
12//! use eventastic::aggregate::{Aggregate, Context, Root, SideEffect};
13//! use eventastic::event::DomainEvent;
14//! use eventastic::memory::InMemoryRepository;
15//! use eventastic::repository::Repository;
16//!
17//! // Define your domain aggregate
18//! #[derive(Clone, Debug)]
19//! struct Counter {
20//! id: String,
21//! value: i32,
22//! }
23//!
24//! // Define your domain events
25//! #[derive(Clone, Debug, PartialEq, Eq)]
26//! enum CounterEvent {
27//! Created { event_id: String, initial_value: i32 },
28//! Incremented { event_id: String, amount: i32 },
29//! }
30//!
31//! impl DomainEvent for CounterEvent {
32//! type EventId = String;
33//! fn id(&self) -> &Self::EventId {
34//! match self {
35//! CounterEvent::Created { event_id, .. } => event_id,
36//! CounterEvent::Incremented { event_id, .. } => event_id,
37//! }
38//! }
39//! }
40//!
41//! // Define side effects (optional)
42//! #[derive(Clone, Debug, PartialEq, Eq)]
43//! struct NoSideEffect {
44//! id: String,
45//! }
46//!
47//! impl SideEffect for NoSideEffect {
48//! type SideEffectId = String;
49//! fn id(&self) -> &Self::SideEffectId {
50//! &self.id
51//! }
52//! }
53//!
54//! // Implement the Aggregate trait
55//! impl Aggregate for Counter {
56//! const SNAPSHOT_VERSION: u64 = 1;
57//! type AggregateId = String;
58//! type DomainEvent = CounterEvent;
59//! type ApplyError = String;
60//! type SideEffect = NoSideEffect;
61//!
62//! fn aggregate_id(&self) -> &Self::AggregateId {
63//! &self.id
64//! }
65//!
66//! fn apply_new(event: &Self::DomainEvent) -> Result<Self, Self::ApplyError> {
67//! match event {
68//! CounterEvent::Created { initial_value, .. } => Ok(Counter {
69//! id: "counter-1".to_string(),
70//! value: *initial_value,
71//! }),
72//! _ => Err("Counter must be created first".to_string()),
73//! }
74//! }
75//!
76//! fn apply(&mut self, event: &Self::DomainEvent) -> Result<(), Self::ApplyError> {
77//! match event {
78//! CounterEvent::Created { .. } => Err("Counter already created".to_string()),
79//! CounterEvent::Incremented { amount, .. } => {
80//! self.value += amount;
81//! Ok(())
82//! }
83//! }
84//! }
85//!
86//! fn side_effects(&self, _event: &Self::DomainEvent) -> Option<Vec<Self::SideEffect>> {
87//! None
88//! }
89//! }
90//!
91//! // Usage with in-memory repository
92//! # async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
93//! let repository = InMemoryRepository::<Counter>::new();
94//!
95//! // Create and persist a new counter
96//! let mut counter: Context<Counter> = Counter::record_new(
97//! CounterEvent::Created {
98//! event_id: "evt-1".to_string(),
99//! initial_value: 0,
100//! }
101//! )?;
102//!
103//! counter.record_that(CounterEvent::Incremented {
104//! event_id: "evt-2".to_string(),
105//! amount: 5,
106//! })?;
107//!
108//! // Save to repository
109//! let mut transaction = repository.begin_transaction().await?;
110//! transaction.store(&mut counter).await?;
111//! transaction.commit()?;
112//!
113//! // Load from repository
114//! let loaded_counter = repository.load(&"counter-1".to_string()).await?;
115//! assert_eq!(loaded_counter.state().value, 5);
116//! assert_eq!(loaded_counter.version(), 1);
117//! # Ok(())
118//! # }
119//! ```
120//!
121//! ## Architecture
122//!
123//! Eventastic is built around four core modules:
124//!
125//! - **[`aggregate`]** - Domain aggregates that encapsulate business logic and generate events
126//! - **[`event`]** - Domain events and event store abstractions for persistence
127//! - **[`repository`]** - Transaction-based persistence layer with read/write operations
128//! - **[`memory`]** - In-memory implementation for testing and development
129//!
130//! ### Transaction-First Design
131//!
132//! Unlike many event sourcing libraries, Eventastic requires transactions for all write
133//! operations. This ensures:
134//!
135//! - **ACID compliance** - All changes are atomic and consistent
136//! - **Idempotency** - Duplicate events are detected and handled gracefully
137//! - **Side effect reliability** - External operations are processed via outbox pattern
138//! - **Optimistic concurrency** - Concurrent modifications are detected and rejected
139//!
140//! ## Complete Example
141//!
142//! For an implementation demonstrating all concepts, see the
143//! [banking example](https://github.com/jdon/eventastic/tree/main/examples/bank)
144//! which shows.
145
146pub mod aggregate;
147pub mod event;
148pub mod repository;
149
150pub mod memory;
151
152#[cfg(test)]
153mod test_fixtures;