Database Service Module
A unified database abstraction layer implementing the Strategy pattern for flexible data persistence in the RxMinder application.
Overview
This module provides a clean, consistent interface for all database operations while supporting multiple backend implementations. It automatically selects the appropriate strategy based on environment configuration.
Architecture
services/database/
├── README.md # This file
├── index.ts # Public exports
├── types.ts # Type definitions and interfaces
├── DatabaseService.ts # Main service class (Strategy Context)
├── MockDatabaseStrategy.ts # In-memory implementation
├── ProductionDatabaseStrategy.ts # CouchDB implementation
└── __tests__/ # Test files
├── DatabaseService.test.ts
└── MockDatabaseStrategy.test.ts
Quick Start
import { databaseService } from './services/database';
// Service automatically selects appropriate strategy
const users = await databaseService.getAllUsers();
const user = await databaseService.getUserById('user123');
Strategy Selection
The service automatically chooses the implementation based on environment:
| Environment | Strategy | Backend |
|---|---|---|
NODE_ENV=test |
MockDatabaseStrategy | In-memory |
| CouchDB URL configured | ProductionDatabaseStrategy | CouchDB |
| Default/Fallback | MockDatabaseStrategy | In-memory |
Environment Variables
# Production CouchDB
VITE_COUCHDB_URL=http://localhost:5984
COUCHDB_URL=http://localhost:5984
# Force mock strategy (optional)
VITE_COUCHDB_URL=mock
Core Interfaces
DatabaseStrategy
All implementations must conform to this interface:
interface DatabaseStrategy {
// User operations
createUser(user: Omit<User, '_rev'>): Promise<User>;
updateUser(user: User): Promise<User>;
getUserById(id: string): Promise<User | null>;
findUserByEmail(email: string): Promise<User | null>;
deleteUser(id: string): Promise<boolean>;
getAllUsers(): Promise<User[]>;
// Auth operations
createUserWithPassword(email: string, hashedPassword: string, username?: string): Promise<User>;
createUserFromOAuth(email: string, username: string, provider: string): Promise<User>;
// Medication operations
createMedication(userId: string, medication: Omit<Medication, '_id' | '_rev'>): Promise<Medication>;
updateMedication(medication: Medication): Promise<Medication>;
getMedications(userId: string): Promise<Medication[]>;
deleteMedication(id: string): Promise<boolean>;
// Settings operations
getUserSettings(userId: string): Promise<UserSettings>;
updateUserSettings(settings: UserSettings): Promise<UserSettings>;
// Doses operations
getTakenDoses(userId: string): Promise<TakenDoses>;
updateTakenDoses(takenDoses: TakenDoses): Promise<TakenDoses>;
// Reminders operations
createCustomReminder(userId: string, reminder: Omit<CustomReminder, '_id' | '_rev'>): Promise<CustomReminder>;
updateCustomReminder(reminder: CustomReminder): Promise<CustomReminder>;
getCustomReminders(userId: string): Promise<CustomReminder[]>;
deleteCustomReminder(id: string): Promise<boolean>;
}
Implementations
MockDatabaseStrategy
Use Cases:
- Development environment
- Unit testing
- Integration testing
- Demos and prototyping
Features:
- In-memory storage using Maps
- Immediate consistency
- No network dependencies
- Automatic data reset between tests
- Fast operations
Limitations:
- Data lost on application restart
- No persistence across sessions
- Single-process only
ProductionDatabaseStrategy
Use Cases:
- Production deployments
- Staging environments
- Data persistence requirements
Features:
- CouchDB backend
- Persistent storage
- Document versioning with
_rev - ACID compliance
- Horizontal scaling support
Requirements:
- CouchDB server running
- Network connectivity
- Proper authentication/authorization
Usage Examples
Basic Operations
import { databaseService } from './services/database';
// Create a user
const user = await databaseService.createUser({
_id: 'user123',
username: 'john_doe',
email: 'john@example.com',
role: UserRole.USER,
status: AccountStatus.ACTIVE,
});
// Add medication
const medication = await databaseService.createMedication(user._id, {
name: 'Aspirin',
dosage: '100mg',
frequency: Frequency.Daily,
startTime: '08:00',
});
// Update user settings
const settings = await databaseService.getUserSettings(user._id);
await databaseService.updateUserSettings({
...settings,
notificationsEnabled: true,
});
Administrative Operations
// User management
await databaseService.suspendUser('user123');
await databaseService.activateUser('user123');
await databaseService.changeUserPassword('user123', 'new_hashed_password');
// Complete data deletion
await databaseService.deleteAllUserData('user123');
Strategy Inspection
// Check active strategy
console.log('Strategy:', databaseService.getStrategyType());
// Strategy-specific logic (avoid when possible)
if (databaseService.isUsingMockStrategy()) {
console.log('Using in-memory storage');
} else if (databaseService.isUsingProductionStrategy()) {
console.log('Using CouchDB storage');
}
Testing
Unit Tests
import { DatabaseService } from './DatabaseService';
import { MockDatabaseStrategy } from './MockDatabaseStrategy';
describe('DatabaseService', () => {
let service: DatabaseService;
beforeEach(() => {
// In test environment, automatically uses MockDatabaseStrategy
service = new DatabaseService();
});
test('should create user', async () => {
const user = await service.createUser(mockUser);
expect(user._id).toBeDefined();
expect(user.username).toBe(mockUser.username);
});
});
Test Utilities
import { testUtils } from '../../tests/setup';
const mockUser = testUtils.createMockUser();
const mockMedication = testUtils.createMockMedication();
Error Handling
DatabaseError
import { DatabaseError } from './services/database';
try {
const user = await databaseService.getUserById('invalid-id');
} catch (error) {
if (error instanceof DatabaseError) {
console.error('Database operation failed:', error.message);
}
}
Fallback Behavior
// Service automatically falls back to MockDatabaseStrategy
// if ProductionDatabaseStrategy fails to initialize
const service = new DatabaseService(); // Safe to use
Performance
MockDatabaseStrategy
- Pros: Instant operations, no I/O overhead
- Cons: Memory usage grows with data size
- Best for: Development, testing, small datasets
ProductionDatabaseStrategy
- Pros: Persistent storage, designed for scale
- Cons: Network latency, connection overhead
- Best for: Production, large datasets, multi-user
Security
Data Isolation
- Users can only access their own data
- User ID validation on all operations
- Role-based access for administrative functions
Input Validation
- TypeScript interfaces enforce type safety
- Required field validation
- Email format validation
- ID format validation
Authentication
- No anonymous access allowed
- User context required for all operations
- Password hashing handled by auth service
Migration Guide
From Legacy CouchDB Service
// Old import
import { couchdbService } from './services/couchdb';
// New import
import { databaseService } from './services/database';
// Old method signature
await couchdbService.updateMedication(userId, medication);
// New method signature
await databaseService.updateMedication(medication);
Breaking Changes
- Method signatures: Some methods now accept the full entity instead of separate parameters
- Return types: Consistent return types across all operations
- Error handling: Unified error types with
DatabaseError
Troubleshooting
Common Issues
1. Strategy Selection
# Check environment variables
echo $VITE_COUCHDB_URL
echo $NODE_ENV
# Verify strategy selection
console.log(databaseService.getStrategyType());
2. CouchDB Connection
# Test CouchDB connectivity
curl $VITE_COUCHDB_URL
# Check CouchDB logs
docker logs couchdb-container
3. Test Environment
# Ensure test environment
NODE_ENV=test npm test
# Verify mock strategy in tests
expect(databaseService.isUsingMockStrategy()).toBe(true);
Debug Logging
// Enable debug information
const strategy = databaseService.getStrategyType();
console.log(`Using ${strategy} for database operations`);
// Check environment
console.log('Environment:', process.env.NODE_ENV);
console.log('CouchDB URL:', process.env.VITE_COUCHDB_URL);
Development
Adding New Operations
- Update DatabaseStrategy interface in
types.ts - Implement in MockDatabaseStrategy
- Implement in ProductionDatabaseStrategy
- Add delegation method in
DatabaseService - Write tests for all implementations
- Update documentation
Creating New Strategies
class CustomDatabaseStrategy implements DatabaseStrategy {
// Implement all required methods
async createUser(user: Omit<User, '_rev'>): Promise<User> {
// Custom implementation
}
// ... implement all other methods
}
Contributing
- Tests required for all new features
- Type safety - no
anytypes - Error handling - proper error propagation
- Documentation - update README and JSDoc
- Strategy pattern - maintain abstraction
Dependencies
Production
- None (strategy implementations may have their own)
Development
@types/jest- Testing typests-jest- TypeScript testing@testing-library/react- Component testing
Changelog
v2.0.0
- ✅ Unified database service with Strategy pattern
- ✅ Removed legacy CouchDB service files
- ✅ Added comprehensive test coverage
- ✅ Improved error handling
- ✅ TypeScript strict mode compliance
v1.x.x
- ❌ Legacy CouchDB-only implementation (deprecated)
Maintainers: Development Team
Last Updated: January 2024
License: MIT