Skip to content

Database Management

EvidentSource databases are the fundamental containers for events. Each database maintains its own event log, indexes, and associated State Views and State Changes.

A database in EvidentSource:

  • Contains an immutable, append-only log of events
  • Maintains bi-temporal indexes for efficient querying
  • Tracks revisions for optimistic concurrency control
  • Supports State Views and State Changes for derived state and command processing
rpc CreateDatabase(CreateDatabaseRequest) returns (CreateDatabaseReply) {}
message CreateDatabaseRequest {
string database_name = 1;
}
POST /api/v1/db
Content-Type: application/json
{
"database_name": "my-events"
}
use evidentsource_application::Application;
let database = app.create_database("my-events").await?;
println!("Created database: {} at revision {}",
database.name(), database.revision());

Database names must:

  • Contain only alphanumeric characters, hyphens, and underscores
  • Be between 1 and 255 characters long
  • Be unique within the EvidentSource instance
  • Not start with a number
{
"database": {
"name": "my-events",
"revision": 0,
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-01-15T10:00:00Z"
}
}
rpc FetchCatalog(CatalogRequest) returns (stream CatalogReply) {}
GET /api/v1/db
let databases = app.list_database_catalog().await?;
for db_name in databases {
println!("Found database: {}", db_name);
}

The catalog returns a list of all database names:

[
"orders",
"customers",
"inventory"
]

Get the current state of a database:

rpc FetchLatestDatabase(LatestDatabaseRequest) returns (DatabaseReply) {}
message LatestDatabaseRequest {
string database_name = 1;
}
GET /api/v1/db/{database}
let database = app.latest_database("my-events").await?;
println!("Database {} is at revision {}",
database.name(), database.revision());

Query database state at a particular revision for consistent reads:

let database = app.database_at_revision("my-events", 100).await?;

Query database state as it existed at a specific point in time:

rpc DatabaseEffectiveAtTimestamp(DatabaseEffectiveAtTimestampRequest)
returns (DatabaseReply) {}
message DatabaseEffectiveAtTimestampRequest {
string database_name = 1;
google.protobuf.Timestamp at_timestamp = 2;
}
use chrono::Utc;
let timestamp = Utc::now() - chrono::Duration::hours(1);
let database = app.database_effective_at_timestamp(
"my-events",
timestamp
).await?;

Block until a database reaches a specific revision, useful for ensuring read-after-write consistency:

rpc AwaitDatabase(AwaitDatabaseRequest) returns (DatabaseReply) {}
message AwaitDatabaseRequest {
string database_name = 1;
uint64 at_revision = 2;
}
// After writing at revision 100, ensure read sees at least that revision
let database = app.await_database("my-events", 100).await?;
assert!(database.revision() >= 100);

Subscribe to real-time database updates as new batches are transacted:

rpc SubscribeDatabaseUpdates(DatabaseUpdatesSubscriptionRequest)
returns (stream DatabaseReply) {}
use tokio_stream::StreamExt;
let mut updates = app.subscribe_database_updates("my-events");
while let Some(result) = updates.next().await {
match result {
Ok(database) => {
println!("Database updated to revision {}",
database.revision());
}
Err(e) => eprintln!("Subscription error: {}", e),
}
}

The subscription:

  • Immediately yields the current database state
  • Then streams updates as new batches are transacted
  • Terminates if the database is deleted

Permanently delete a database and all its data:

rpc DeleteDatabase(DeleteDatabaseRequest) returns (DeleteDatabaseReply) {}
message DeleteDatabaseRequest {
string database_name = 1;
}
DELETE /api/v1/db/{database}
let deleted = app.delete_database("old-events").await?;
println!("Deleted database {} which had {} events",
deleted.name(), deleted.revision());

Warning: Database deletion is irreversible and removes:

  • All events in the database
  • All indexes (stream, subject, event type)
  • All State View definitions and materialized state
  • All State Change definitions

Common errors when managing databases:

Error CodeDescriptionResolution
ALREADY_EXISTSDatabase name already takenChoose a different name
INVALID_ARGUMENTInvalid database name formatFollow naming rules
NOT_FOUNDDatabase doesn’t existVerify database name
INTERNALStorage errorCheck system health
use evidentsource_api::{DatabaseCreationError, DatabaseQueryError};
match app.create_database("my-events").await {
Ok(database) => {
println!("Created: {}", database.name());
}
Err(DatabaseCreationError::DatabaseNameAlreadyExists(name)) => {
eprintln!("Database {} already exists", name);
}
Err(DatabaseCreationError::InvalidDatabaseName(err)) => {
eprintln!("Invalid name: {}", err);
}
Err(e) => {
eprintln!("Unexpected error: {}", e);
}
}
  1. Database Granularity: Create separate databases for different bounded contexts or domains
  2. Naming Conventions: Use descriptive, hierarchical names (e.g., orders-production, orders-staging)
  3. Lifecycle Management: Implement retention policies for old databases
  4. Monitoring: Track database growth and revision rates
  5. Consistency: Use revision-based queries for consistent reads across multiple operations