Event Sourcing
Event sourcing is the foundation of EvidentSource. Instead of storing just the current state of your data, event sourcing captures every change as an immutable event, creating a complete audit trail of how your system evolved over time.
Core Principles
Section titled “Core Principles”Events as First-Class Citizens
Section titled “Events as First-Class Citizens”In EvidentSource, every state change is represented as an event. Events are:
- Immutable: Once created, events cannot be modified or deleted
- Ordered: Events have a strict ordering through revision numbers
- Atomic: Events are transacted with all-or-nothing semantics
- Descriptive: Events describe what happened, not what to do
Event Structure
Section titled “Event Structure”EvidentSource uses the CloudEvents specification for all events:
{ "specversion": "1.0", "id": "unique-event-id", "source": "order-service", "subject": "order-123", "type": "com.example.OrderCreated", "time": "2024-01-15T10:30:00Z", "datacontenttype": "application/json", "data": { "orderId": "order-123", "customerId": "customer-456", "items": [...] }}Revisions and Transactions
Section titled “Revisions and Transactions”Atomic Transactions
Section titled “Atomic Transactions”Events in EvidentSource are transacted atomically. Each transaction:
- Contains one or more events
- Is assigned a unique revision number
- Succeeds or fails as a unit
- Maintains consistency through optimistic concurrency control
Revision Numbers
Section titled “Revision Numbers”Every event receives a revision number that:
- Starts at 1 and increments monotonically
- Provides total ordering across all events
- Enables point-in-time queries
- Supports optimistic concurrency control
Benefits of Event Sourcing
Section titled “Benefits of Event Sourcing”Complete Audit Trail
Section titled “Complete Audit Trail”Every change is captured, providing:
- Who made the change (from event metadata)
- What changed (event data)
- When it changed (event time and recorded time)
- Why it changed (event type and context)
Time Travel
Section titled “Time Travel”Navigate to any point in your system’s history:
- Replay events to reconstruct past states
- Debug issues by examining the exact sequence of events
- Analyze how your system evolved over time
Event Replay
Section titled “Event Replay”Rebuild state from events:
- Create new projections from existing events
- Fix bugs and replay events to correct derived data
- Test new business logic against historical data
Natural Integration
Section titled “Natural Integration”Event sourcing naturally supports:
- Event-driven architectures
- CQRS (Command Query Responsibility Segregation)
- Microservices communication
- Real-time streaming and analytics
Constraints and Guarantees
Section titled “Constraints and Guarantees”Optimistic Concurrency Control
Section titled “Optimistic Concurrency Control”EvidentSource uses constraints to ensure consistency:
// Ensure we're appending to the latest stateMinRevisionConstraint { revision: 42, selector: Some(EventSelector { stream: Some("order-service"), ..Default::default() })}Event Validation
Section titled “Event Validation”Events are validated for:
- Unique event IDs (no duplicates)
- Valid CloudEvents format
- Size limits (64KB per event)
- Required fields (id, source, type, specversion)
Consistency Guarantees
Section titled “Consistency Guarantees”- Read-after-write consistency: Queries immediately reflect committed events
- Atomic transactions: All events in a transaction succeed or fail together
- Strict ordering: Events maintain their revision order
Working with Events
Section titled “Working with Events”Writing Events
Section titled “Writing Events”Events are written atomically using the Transact operation:
// Kotlin client exampleval transaction = Transaction( events = listOf( CloudEvent( id = UUID.randomUUID().toString(), source = "order-service", type = "OrderCreated", subject = "order-123", data = orderData ) ), constraints = listOf( MinRevisionConstraint(revision = lastKnownRevision) ))
client.transact(database, transaction)Querying Events
Section titled “Querying Events”Query events using various filters:
// Query all events for a specific orderval query = EventQuery( eventSelection = EventSelection( selectors = listOf( EventSelector(subject = "order-123") ) ), direction = Forward)
client.queryEvents(database, query)Best Practices
Section titled “Best Practices”Event Design
Section titled “Event Design”- Make events descriptive: Use clear, business-meaningful event types
- Include sufficient context: Events should be self-contained
- Version event schemas: Plan for event evolution
- Use consistent naming: Follow a naming convention like
EntityVerbed
Event Granularity
Section titled “Event Granularity”- Not too fine: Avoid events for every field change
- Not too coarse: Don’t bundle unrelated changes
- Business-focused: Align events with business operations
- Meaningful units: Each event should represent a complete business action
Performance Considerations
Section titled “Performance Considerations”- Group related events: Combine events that occur together in a single transaction
- Use appropriate constraints: Only constrain what’s necessary
- Index strategically: Use stream, subject, and type for efficient queries
- Consider event size: Keep events focused and under 64KB
Next Steps
Section titled “Next Steps”- Learn about CloudEvents format in detail
- Explore Bi-Temporal Indexing for time-based queries
- Understand State Views for materializing event data
- Discover State Changes for command handling