Core Concepts
The fundamentals of event sourcing with DeltaBase
Test List
Section titled “Test List”Here is a simple test list:
- First item
- Second item
- Third item
And here’s a numbered list:
- First numbered item
- Second numbered item
- Third numbered item
How DeltaBase Actually Works
Section titled “How DeltaBase Actually Works”Event sourcing sounds complex, but it’s just a different way of storing data. Instead of saving the current state, you save the events that led to that state.
Think of it like a bank statement. Your balance isn’t stored anywhere - it’s calculated from all the deposits and withdrawals. That’s event sourcing.
Events
Section titled “Events”Events are facts. Things that happened. They’re immutable and always past tense.
// Good events - past tense, factual{ type: 'order.placed', data: { customerId: 'abc', total: 99.99, items: [...] }}
{ type: 'payment.authorized', data: { orderId: '123', amount: 99.99, cardLast4: '1234' }}
// Bad events - present tense, commands{ type: 'place.order', // This is a command, not an event data: { ... }}
Event structure in DeltaBase:
type
- What happened (like ‘user.registered’)data
- The details of what happenedmetadata
- Optional context (who, why, etc.)
DeltaBase adds:
streamId
- Which entity this relates tostreamPosition
- Order within the streamglobalPosition
- Order across all eventseventId
- Unique identifiercreatedAt
- When it happened
Streams
Section titled “Streams”Streams are sequences of related events. Usually one per entity.
// Stream: user-123[ { type: 'user.registered', data: { email: 'john@example.com' } }, { type: 'user.email_verified', data: { verifiedAt: '...' } }, { type: 'user.profile_updated', data: { name: 'John Doe' } }]
// Stream: order-456[ { type: 'order.placed', data: { customerId: 'user-123', total: 99.99 } }, { type: 'order.paid', data: { paymentId: 'pay_abc' } }, { type: 'order.shipped', data: { trackingNumber: 'TRK123' } }]
Stream naming conventions:
user-{id}
for user eventsorder-{id}
for order eventssession-{id}
for session events- Whatever makes sense for your domain
Building State from Events
Section titled “Building State from Events”Current state is calculated by replaying events:
function buildUserFromEvents(events) { let user = { id: null, email: null, verified: false, name: null };
for (const event of events) { switch (event.type) { case 'user.registered': user.id = event.streamId; user.email = event.data.email; break;
case 'user.email_verified': user.verified = true; break;
case 'user.profile_updated': if (event.data.name) user.name = event.data.name; if (event.data.email) user.email = event.data.email; break; } }
return user;}
This is called aggregation - building current state from events.
Commands vs Queries (CQRS)
Section titled “Commands vs Queries (CQRS)”Commands change state by creating events:
// Command: Register a userasync function registerUser(email, name) { const userId = generateId();
await eventStore.appendToStream(userId, [ { type: 'user.registered', data: { email, name } } ]);
return userId;}
Queries read current state:
// Query: Get user profileasync function getUserProfile(userId) { const events = await eventStore.readStream(userId); return buildUserFromEvents(events);}
Why separate them?
- Commands can be optimized for writes
- Queries can be optimized for reads
- You can have multiple views of the same data
- Scales better under load
Optimistic Concurrency
Section titled “Optimistic Concurrency”Multiple people editing the same data? DeltaBase handles it with version numbers.
// Load current versionconst events = await eventStore.readStream('order-123');const currentVersion = events.length - 1;
// Try to append, expecting specific versiontry { await eventStore.appendToStream('order-123', [updateEvent], { expectedStreamVersion: currentVersion });} catch (error) { if (error.status === 409) { // Someone else modified it - handle the conflict console.log('Order was modified by someone else'); }}
This prevents the “lost update” problem where one person’s changes overwrite another’s.
Event Store vs Database
Section titled “Event Store vs Database”Traditional database:
UPDATE users SET email = 'new@example.com' WHERE id = 123;-- Old email is gone forever
Event store:
await eventStore.appendToStream('user-123', [ { type: 'user.email_changed', data: { oldEmail: 'old@example.com', newEmail: 'new@example.com' } }]);// Both emails are preserved in the audit trail
Key differences:
- Immutable: Events never change, only accumulate
- Auditable: Complete history of what happened
- Temporal: Can query any point in time
- Append-only: Optimized for writes
Projections and Views
Section titled “Projections and Views”Sometimes you need optimized read models. Build them from events:
// Projection: User summary viewasync function updateUserSummaryView(event) { switch (event.type) { case 'user.registered': await db.userSummaries.create({ userId: event.streamId, email: event.data.email, status: 'registered', createdAt: event.createdAt }); break;
case 'user.email_verified': await db.userSummaries.update(event.streamId, { status: 'verified' }); break; }}
Views vs Events:
- Events: The source of truth
- Views: Optimized for queries
- Eventually consistent: Views might lag behind events
Real-time Updates
Section titled “Real-time Updates”DeltaBase can notify your app when events happen:
// Subscribe to user eventsawait eventBus.subscribeWebhook('user.*', 'https://myapp.com/webhook');
// Your webhook receives:{ "event": { "type": "user.registered", "data": { "email": "john@example.com" }, "streamId": "user-123", "createdAt": "2024-01-01T12:00:00Z" }}
Perfect for updating UI in real-time or triggering workflows.
The Mental Model
Section titled “The Mental Model”Traditional thinking: “What is the current state?” Event sourcing thinking: “What happened to get to this state?”
Traditional: User has email “john@example.com” Event sourcing: User registered with “john@example.com”, then changed it from “old@example.com” to “john@example.com”
The difference is profound:
- You know why the state is what it is
- You can prove what happened
- You can replay scenarios to debug issues
- You can time travel to any point in history
When It Clicks
Section titled “When It Clicks”Event sourcing feels weird at first. You’re not updating records, you’re appending facts. But once it clicks, you realize it’s actually simpler:
- No complex UPDATE statements
- No lost data from overwrites
- No mysterious state changes
- No missing audit trails
Just facts, in order, forever.
That’s event sourcing. That’s DeltaBase.