title: "Database Service Example" description: "" category: "Documentation" tags: [] last_updated: "March 28, 2025" version: "1.0"
title: "Database Service Example" description: "Examples of using the generic database service interfaces and providers" category: examples tags:
- database
- service
- generalization
- providers related:
- examples/repository-pattern-example.md
- roadmaps/25-generic-service-implementations.md
- reference/patterns/repository-pattern.md last_updated: March 27, 2025 version: 1.0
Database Service Example
This example demonstrates how to use the generic database service implementation, including defining providers, configuring the service, and performing database operations.
Overview
The Database Service implementation follows a provider-based architecture that enables:
- Abstracting database operations from specific implementations
- Supporting multiple database types through providers
- Configuration-based selection of database providers
- Easy testing with in-memory database implementations
Core Components
The database service consists of several key components:
- DatabaseOperations Trait: Defines core database operations
- DatabaseProvider Trait: Defines interface for creating database instances
- DatabaseProviderRegistry: Manages and creates database instances
- DatabaseConfig: Configures database connection settings
- InMemoryDatabase: Default in-memory implementation for testing
Basic Usage
Accessing the Database Service
The database service is accessible through the application's service registry:
use crate::core::services::ServiceRegistry;
use crate::core::services::database_service::DatabaseService;
// Get the service from service registry
let db_service = service_registry.get::<DatabaseService>();
// Use the database service
let result = db_service.create_database().await?;
Performing Basic Operations
Once you have a database instance, you can perform operations:
// Get a value
let user_json = db.get("users", "user-123").await?;
// Set a value
db.set("users", "user-456", &user_json_string).await?;
// Delete a value
let deleted = db.delete("users", "user-789").await?;
// Query with a filter
let active_users = db.query("users", "status='active'").await?;
Implementing a Custom Provider
You can implement your own database provider by implementing the DatabaseProvider
trait:
use crate::core::services::database_service::{DatabaseOperations, DatabaseProvider};
use crate::core::services::error::ServiceError;
use async_trait::async_trait;
pub struct MyCustomDatabaseProvider;
#[async_trait]
impl DatabaseProvider for MyCustomDatabaseProvider {
type Database = MyCustomDatabase;
async fn create_database(&self, config: DatabaseConfig) -> Result<Self::Database, ServiceError> {
// Create and return your database implementation
Ok(MyCustomDatabase::new(config))
}
fn supports(&self, config: &DatabaseConfig) -> bool {
config.provider_type == "custom"
}
}
pub struct MyCustomDatabase {
// Your database implementation details
}
#[async_trait]
impl DatabaseOperations for MyCustomDatabase {
async fn get(&self, collection: &str, key: &str) -> Result<Option<String>, ServiceError> {
// Implement get operation
}
// Implement other required operations...
}
Registering a Provider
Register your custom provider with the database service:
use crate::core::services::database_service::DatabaseProviderRegistry;
// Create a registry
let mut registry = DatabaseProviderRegistry::new();
// Register your provider
registry.register("custom", MyCustomDatabaseProvider);
// Create the database service with the registry
let db_service = DatabaseService::new(registry);
Using the In-Memory Database
The in-memory database provider is useful for testing:
use crate::core::services::memory_database::{InMemoryDatabaseProvider, InMemoryDatabase};
#[tokio::test]
async fn test_database_operations() {
// Create a provider and configuration
let provider = InMemoryDatabaseProvider::new();
let config = DatabaseConfig::default().with_provider("memory");
// Create a database instance
let db = provider.create_database(config).await.unwrap();
// Set a test value
db.set("test", "key1", "value1").await.unwrap();
// Get the value back
let value = db.get("test", "key1").await.unwrap();
assert_eq!(value, Some("value1".to_string()));
}
Configuration
Configure the database service in your application configuration:
# In config/default.yaml
database:
provider: memory # Could be postgres, mongodb, etc.
connection_string: ""
max_connections: 10
connection_timeout_ms: 5000
retry_attempts: 3
enable_logging: true
Loading the configuration:
use crate::core::config::AppConfig;
use crate::core::services::database_service::DatabaseConfig;
// Load from application config
let app_config = AppConfig::load()?;
let db_config = DatabaseConfig::from_app_config(&app_config);
// Or create it programmatically
let db_config = DatabaseConfig::default()
.with_provider("postgres")
.with_connection_string("postgres://user:pass@localhost/dbname")
.with_max_connections(20);
Complete Example
Here's a complete example showing how to set up and use the database service:
use crate::core::services::database_service::{
DatabaseService, DatabaseConfig, DatabaseProviderRegistry
};
use crate::core::services::memory_database::register_memory_database_provider;
async fn setup_database_service() -> Result<DatabaseService, ServiceError> {
// Create a provider registry
let mut registry = DatabaseProviderRegistry::new();
// Register the built-in memory provider
register_memory_database_provider(&mut registry);
// Create configuration
let config = DatabaseConfig::default()
.with_provider("memory")
.with_max_connections(5);
// Create service
let service = DatabaseService::new(registry)
.with_default_config(config);
// Initialize the service
service.init().await?;
Ok(service)
}
async fn example_usage(service: &DatabaseService) -> Result<(), ServiceError> {
// Create a database instance
let db = service.create_database().await?;
// Store user data
let user_data = r#"{"id":"user-123","name":"Alice","role":"admin"}"#;
db.set("users", "user-123", user_data).await?;
// Retrieve user data
if let Some(data) = db.get("users", "user-123").await? {
println!("User data: {}", data);
}
// Query users by role
let admins = db.query("users", "role='admin'").await?;
println!("Found {} admin users", admins.len());
Ok(())
}
Best Practices
- Provider Selection: Choose the appropriate provider based on your requirements
- Error Handling: Always handle database errors properly
- Connection Management: Reuse database connections where possible
- Testing: Use the in-memory database for testing
- Configuration: Externalize database configuration
- Transactions: Use transactions for multi-step operations
- Security: Always sanitize input to prevent injection attacks