Event Sourcing
Synap SDK is built on event sourcing - a powerful architectural pattern that records every change as an immutable event.
What is Event Sourcing?
Instead of storing only the current state, event sourcing stores all changes that led to the current state.
###Traditional Approach (State-Based)
// Update task directly
database.tasks.update({ id: '123' }, {
status: 'done',
completedAt: new Date()
})
// Lost information:
// - Who completed it?
// - When exactly?
// - What was it before?Event Sourcing Approach
// Record the event
await synap.entities.update(taskId, {
metadata: { status: 'done' }
})
// Creates immutable event:
{
eventType: 'entities.update.validated',
data: { status: 'done' },
userId: 'user_123',
timestamp: '2024-12-20T15:30:00Z',
version: 5
}
// Benefits:
// ✅ Complete audit trail
// ✅ Who, what, when recorded
// ✅ Can replay to any point in timeHow Synap SDK Uses Event Sourcing
Write Path (Mutations)
All mutations are event-sourced:
// 1. SDK publishes event
await synap.entities.create({ ... })
↓
// 2. Event logged to database
events.log({
eventType: 'entities.create.requested',
data: { ... }
})
↓
// 3. Inngest worker processes
entitiesWorker.handle(event)
↓
// 4. Database updated
database.entities.insert({ ... })
↓
// 5. Validation event emitted
events.log({
eventType: 'entities.create.validated',
data: { ... }
})Event-Sourced Operations:
entities.create()entities.update()entities.delete()relations.create()relations.delete()
Read Path (Queries)
All queries read directly from the database for speed:
// Fast, direct database query
const tasks = await synap.entities.list({ type: 'task' })
↓
// No event processing needed
database.entities.findMany({ type: 'task' })Direct-Read Operations:
entities.get()entities.list()entities.search()relations.get()relations.getRelated()events.getHistory()
Benefits
1. Complete Audit Trail
Every change is recorded forever:
// See full history
const history = await synap.events.getHistory(entityId)
console.log(`Entity modified ${history.length} times`)
history.forEach(event => {
console.log(`${event.createdAt}: ${event.eventType} by ${event.userId}`)
})Use Cases:
- Compliance (GDPR, SOC2)
- Security audits
- User accountability
- Debugging
2. Time Travel
Replay events to see past states:
// Get state at any point in time
const eventsUntil = history.filter(e =>
e.createdAt <= new Date('2024-12-01')
)
// Rebuild state
let pastState = {}
eventsUntil.forEach(event => {
if (event.eventType includes 'create') pastState = event.data
if (event.eventType.includes('update')) pastState = { ...pastState, ...event.data }
})Use Cases:
- Undo/redo
- Historical reporting
- Data recovery
- Root cause analysis
3. Reliability
Async processing with automatic retries:
// If worker fails, Inngest retries automatically
entitiesWorker.config({
retries: 3
})
// Your application stays responsive
// Events are processed in backgroundBenefits:
- Resilient to failures
- Never lose data
- Eventual consistency
- Scalable processing
4. Real-Time Updates
Events trigger webhooks and broadcasts:
// When event is validated:
// 1. Webhook sent to configured URL
// 2. SSE broadcast to connected clients
// 3. Real-time UI updates
// Your frontend stays in sync automaticallyEvent Lifecycle
1. Request Event
await synap.entities.create({
type: 'task',
title: 'Review PR'
})
// Creates:
{
eventType: 'entities.create.requested',
aggregateId: 'task_123',
data: {
type: 'task',
title: 'Review PR'
},
version: 1
}2. Processing
// Inngest worker receives event
entitiesWorker.handle(event)
// Validates data
// Inserts into database
// Handles errors3. Validation Event
// On success:
{
eventType: 'entities.create.validated',
aggregateId: 'task_123',
data: {
id: 'task_123',
type: 'task',
title: 'Review PR',
createdAt: '2024-12-20T10:00:00Z'
},
version: 1
}4. Notification
// Broadcast via SSE
broadcast({
type: 'entity.created',
entityId: 'task_123'
})
// Trigger webhooks
webhook.send({
event: 'entities.create.validated',
data: { ... }
})Trade-Offs
Advantages ✅
- Complete history: Never lose data
- Audit trail: Every change tracked
- Debugging: Replay to find issues
- Flexibility: Add features without migrations
- Testability: Events are facts, easy to test
Considerations ⚠️
- Eventual consistency: Writes are async (usually < 100ms)
- Storage: Events accumulate over time
- Complexity: More moving parts than CRUD
For Synap SDK:
- ✅ Consistency delay is minimal (background workers)
- ✅ Storage managed by Synap infrastructure
- ✅ Complexity hidden behind simple API
##Common Patterns
Check Event Status
// Create entity
const { entityId } = await synap.entities.create({ ... })
// Wait a moment for processing
await new Promise(resolve => setTimeout(resolve, 100))
// Verify it exists
const entity = await synap.entities.get(entityId)Event-Driven Features
// Listen for validation events
// (In real app, use webhooks or SSE)
function onEntityCreated(event) {
if (event.data.type === 'task') {
// Auto-assign to team
synap.relations.create(
event.data.id,
defaultAssignee,
'assigned_to'
)
}
}Event Replay
// Rebuild aggregate state
async function getEntityVersion(entityId: string, version: number) {
const history = await synap.events.getHistory(entityId)
const eventsToReplay = history.slice(0, version)
return replayEvents(eventsToReplay)
}Best Practices
✅ Do’s
// Trust eventual consistency
const { entityId } = await synap.entities.create({ ... })
// Entity will exist very soon (< 100ms typically)
// Use events for debugging
const history = await synap.events.getHistory(entityId)
console.log('Event sequence:', history.map(e => e.eventType))
// Implement optimistic UI
setTasks([...tasks, newTask]) // Update immediately
await synap.entities.create(newTask) // Persist in background❌ Don’ts
// Don't assume immediate consistency
const { entityId } = await synap.entities.create({ ... })
const entity = await synap.entities.get(entityId) // Might fail!
// Add small delay if needed
await new Promise(r => setTimeout(r, 50))
const entity = await synap.entities.get(entityId) // Better
// Don't try to modify events
// Events are immutable - create new ones insteadLearn More
- Architecture → - System design
- Entities API → - Event-sourced operations
- Events API → - Access event history
- Examples → - See it in action
Last updated on