Database Management
Database Management API
Section titled “Database Management API”EvidentSource databases are the fundamental containers for events. Each database maintains its own event log, indexes, and associated State Views and State Changes.
Overview
Section titled “Overview”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
Creating Databases
Section titled “Creating Databases”gRPC API
Section titled “gRPC API”rpc CreateDatabase(CreateDatabaseRequest) returns (CreateDatabaseReply) {}
message CreateDatabaseRequest { string database_name = 1;}HTTP API
Section titled “HTTP API”POST /api/v1/dbContent-Type: application/json
{ "database_name": "my-events"}Rust API
Section titled “Rust API”use evidentsource_application::Application;
let database = app.create_database("my-events").await?;println!("Created database: {} at revision {}", database.name(), database.revision());Database Naming Rules
Section titled “Database Naming Rules”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
Response
Section titled “Response”{ "database": { "name": "my-events", "revision": 0, "created_at": "2024-01-15T10:00:00Z", "updated_at": "2024-01-15T10:00:00Z" }}Listing Databases
Section titled “Listing Databases”gRPC API
Section titled “gRPC API”rpc FetchCatalog(CatalogRequest) returns (stream CatalogReply) {}HTTP API
Section titled “HTTP API”GET /api/v1/dbRust API
Section titled “Rust API”let databases = app.list_database_catalog().await?;for db_name in databases { println!("Found database: {}", db_name);}Response
Section titled “Response”The catalog returns a list of all database names:
[ "orders", "customers", "inventory"]Fetching Database Metadata
Section titled “Fetching Database Metadata”Latest Database State
Section titled “Latest Database State”Get the current state of a database:
gRPC API
Section titled “gRPC API”rpc FetchLatestDatabase(LatestDatabaseRequest) returns (DatabaseReply) {}
message LatestDatabaseRequest { string database_name = 1;}HTTP API
Section titled “HTTP API”GET /api/v1/db/{database}Rust API
Section titled “Rust API”let database = app.latest_database("my-events").await?;println!("Database {} is at revision {}", database.name(), database.revision());Database at Specific Revision
Section titled “Database at Specific Revision”Query database state at a particular revision for consistent reads:
let database = app.database_at_revision("my-events", 100).await?;Database at Timestamp
Section titled “Database at Timestamp”Query database state as it existed at a specific point in time:
gRPC API
Section titled “gRPC API”rpc DatabaseEffectiveAtTimestamp(DatabaseEffectiveAtTimestampRequest) returns (DatabaseReply) {}
message DatabaseEffectiveAtTimestampRequest { string database_name = 1; google.protobuf.Timestamp at_timestamp = 2;}Rust API
Section titled “Rust API”use chrono::Utc;
let timestamp = Utc::now() - chrono::Duration::hours(1);let database = app.database_effective_at_timestamp( "my-events", timestamp).await?;Awaiting Database Updates
Section titled “Awaiting Database Updates”Block until a database reaches a specific revision, useful for ensuring read-after-write consistency:
gRPC API
Section titled “gRPC API”rpc AwaitDatabase(AwaitDatabaseRequest) returns (DatabaseReply) {}
message AwaitDatabaseRequest { string database_name = 1; uint64 at_revision = 2;}Rust API
Section titled “Rust API”// After writing at revision 100, ensure read sees at least that revisionlet database = app.await_database("my-events", 100).await?;assert!(database.revision() >= 100);Subscribing to Updates
Section titled “Subscribing to Updates”Subscribe to real-time database updates as new batches are transacted:
gRPC API
Section titled “gRPC API”rpc SubscribeDatabaseUpdates(DatabaseUpdatesSubscriptionRequest) returns (stream DatabaseReply) {}Rust API
Section titled “Rust API”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
Deleting Databases
Section titled “Deleting Databases”Permanently delete a database and all its data:
gRPC API
Section titled “gRPC API”rpc DeleteDatabase(DeleteDatabaseRequest) returns (DeleteDatabaseReply) {}
message DeleteDatabaseRequest { string database_name = 1;}HTTP API
Section titled “HTTP API”DELETE /api/v1/db/{database}Rust API
Section titled “Rust API”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
Error Handling
Section titled “Error Handling”Common errors when managing databases:
| Error Code | Description | Resolution |
|---|---|---|
ALREADY_EXISTS | Database name already taken | Choose a different name |
INVALID_ARGUMENT | Invalid database name format | Follow naming rules |
NOT_FOUND | Database doesn’t exist | Verify database name |
INTERNAL | Storage error | Check system health |
Rust Error Handling
Section titled “Rust Error Handling”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); }}Best Practices
Section titled “Best Practices”- Database Granularity: Create separate databases for different bounded contexts or domains
- Naming Conventions: Use descriptive, hierarchical names (e.g.,
orders-production,orders-staging) - Lifecycle Management: Implement retention policies for old databases
- Monitoring: Track database growth and revision rates
- Consistency: Use revision-based queries for consistent reads across multiple operations
Next Steps
Section titled “Next Steps”- Learn about Transactions for writing events
- Explore Querying Events for reading data
- Understand Bi-Temporal Indexing for efficient access
- Configure Constraints for consistency control