Database Adapter
Connect to your database
Adapter Interface
Connect Ventyd to any database by implementing a simple adapter interface.
interface Adapter {
// Load all events for an entity
getEventsByEntityId(params: {
entityName: string;
entityId: string;
}): Promise<Event[]>;
// Save new events atomically
commitEvents(params: {
events: Event[];
}): Promise<void>;
}import type { Adapter } from 'ventyd';
const myAdapter: Adapter = {
// ...
}Implementation Requirements
Make sure your adapter follows these guidelines for reliable event sourcing:
getEventsByEntityId()
- Return events in chronological order (oldest first)
- Return empty array if entity doesn't exist
commitEvents()
- Save all events atomically (all or nothing)
- Handle empty array gracefully
- Make it idempotent if possible (use unique constraints on eventId)
Example Implementation
Here's a simple adapter using a generic database client:
import { createRepository } from 'ventyd';
import type { Adapter } from 'ventyd';
const adapter: Adapter = {
async getEventsByEntityId({ entityName, entityId }) {
// Load events from your database
return await db.query(
'SELECT * FROM events WHERE entity_name = ? AND entity_id = ? ORDER BY created_at ASC',
[entityName, entityId]
);
},
async commitEvents({ events }) {
if (events.length === 0) return;
// Save events atomically to your database
await db.insertMany(events);
}
};
const repository = createRepository(User, { adapter });Best Practices
Connection Pooling
Always use connection pooling for production databases to handle concurrent requests efficiently.
Create Indexes
Index on (entityName, entityId) for fast event lookups.
Handle Errors
Catch duplicate key errors to make operations idempotent.
Use Transactions
Use database transactions in commitEvents() to ensure atomicity.
Implement the adapter for your database of choice (MongoDB, PostgreSQL, MySQL, Redis, etc.) and you're ready to go!
In production, commitEvents() often needs to do more than just insert events — updating a read model, saving snapshots, and handling concurrent writes all need to happen atomically. When these operations aren't in one transaction, a crash between steps can leave your read model out of sync with your event log. See Transactions for how to handle this.
