Skip to content

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.

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

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": [...]
}
}

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

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

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)

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

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

Event sourcing naturally supports:

  • Event-driven architectures
  • CQRS (Command Query Responsibility Segregation)
  • Microservices communication
  • Real-time streaming and analytics

EvidentSource uses constraints to ensure consistency:

// Ensure we're appending to the latest state
MinRevisionConstraint {
revision: 42,
selector: Some(EventSelector {
stream: Some("order-service"),
..Default::default()
})
}

Events are validated for:

  • Unique event IDs (no duplicates)
  • Valid CloudEvents format
  • Size limits (64KB per event)
  • Required fields (id, source, type, specversion)
  • 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

Events are written atomically using the Transact operation:

// Kotlin client example
val 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)

Query events using various filters:

// Query all events for a specific order
val query = EventQuery(
eventSelection = EventSelection(
selectors = listOf(
EventSelector(subject = "order-123")
)
),
direction = Forward
)
client.queryEvents(database, query)
  • 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
  • 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
  • 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