# Adapters Module This module provides adapter implementations that bridge the repository layer with the extension's Redis operations. Adapters translate between the generic `RedisClient` trait and the extension-specific Redis module. ## Architecture The adapters module follows the **Adapter Pattern**, allowing the repository layer to remain decoupled from the specific Redis implementation: ```mermaid graph TD Repo[Repository Layer
#40;forge-repositories#41;] Trait[RedisClient Trait
#40;forge-shared#41;] Adapter[ExtensionRedisClient
#40;adapter#41;] Redis[Redis Module
#40;extension#41;] Repo --> Trait Trait --> Adapter Adapter --> Redis ``` This design enables: - **Testability**: Repositories can use mock adapters for testing - **Flexibility**: Different Redis implementations can be swapped without changing repositories - **Separation of Concerns**: Repository logic is independent of Redis connection details ## ExtensionRedisClient The `ExtensionRedisClient` is the primary adapter that implements the `RedisClient` trait from `forge_shared`. ### Responsibilities - **Translate Calls**: Convert trait method calls to Redis module function calls - **Error Handling**: Parse Redis operation results and convert to `Result` types - **Data Transformation**: Handle response parsing (e.g., JSON arrays for lists/sets) - **Logging**: Log debug information for Redis operations ### Implemented Operations #### Hash Operations | Method | Description | Returns | | -------------- | ------------------------------ | ------------------------ | | `hash_mset` | Set multiple fields atomically | `Result<(), String>` | | `hash_get_all` | Get all fields and values | `Result` | | `hash_get` | Get a single field value | `Result` | | `hash_del` | Delete a field | `Result<(), String>` | #### List Operations | Method | Description | Returns | | ------------ | --------------------- | ----------------------------- | | `list_rpush` | Append to list | `Result<(), String>` | | `list_range` | Get range of elements | `Result, String>` | | `list_del` | Remove by value | `Result<(), String>` | #### Set Operations | Method | Description | Returns | | ------------- | --------------- | ----------------------------- | | `set_add` | Add member | `Result<(), String>` | | `set_members` | Get all members | `Result, String>` | | `set_del` | Remove member | `Result<(), String>` | #### Common Operations | Method | Description | Returns | | ------------ | ------------------- | ---------------------- | | `key_exists` | Check if key exists | `Result` | | `delete_key` | Delete key | `Result<(), String>` | ### Usage Example ```rust use crate::adapters::ExtensionRedisClient; use forge_shared::RedisClient; // Create the adapter let client = ExtensionRedisClient::new(); // Use it with the RedisClient trait let fields = vec![ ("name".to_string(), "John".to_string()), ("age".to_string(), "30".to_string()), ]; client.hash_mset("user:123".to_string(), fields)?; // Retrieve data let data = client.hash_get_all("user:123".to_string())?; ``` ## Error Handling The adapter translates Redis string responses to Rust `Result` types: - **Success**: Returns `Ok(value)` with the appropriate type - **Error**: Returns `Err(message)` if the response starts with "Error:" ```rust // Redis module returns "OK" → Adapter returns Ok(()) // Redis module returns "Error: Connection failed" → Adapter returns Err("Error: Connection failed") ``` ### Response Parsing For operations that return collections, the adapter parses JSON responses: ```rust // list_range returns JSON: ["item1", "item2", "item3"] let items = client.list_range("mylist".to_string(), 0, -1)?; // items: Vec = vec!["item1", "item2", "item3"] ``` ## Contributing We welcome contributions to the adapters module! Follow these guidelines to add new adapter methods or create new adapters. ### Adding a New Method to ExtensionRedisClient To add a new method (e.g., `hash_exists`), follow these steps: 1. **Check the Trait**: Ensure the method is defined in the `RedisClient` trait in `forge_shared`. ```rust // In forge_shared/src/redis_client.rs pub trait RedisClient: Send + Sync { fn hash_exists(&self, key: String, field: String) -> Result; } ``` 2. **Implement the Method**: Add the implementation to `ExtensionRedisClient`. ```rust impl RedisClient for ExtensionRedisClient { fn hash_exists(&self, key: String, field: String) -> Result { // Call the Redis module function let result = redis::hash::hash_exists(key, field); // Parse the response match result.as_str() { "1" => Ok(true), "0" => Ok(false), _ if result.starts_with("Error:") => Err(result), _ => Err(format!("Unexpected response: {}", result)), } } } ``` 3. **Add Logging** (if needed): For debugging, log the operation. ```rust fn hash_exists(&self, key: String, field: String) -> Result { let result = redis::hash::hash_exists(key, field); log("debug", "DEBUG", &format!("hash_exists({}, {}): {}", key, field, result)); match result.as_str() { "1" => Ok(true), "0" => Ok(false), _ if result.starts_with("Error:") => Err(result), _ => Err(format!("Unexpected response: {}", result)), } } ``` 4. **Handle Response Types**: Match the return type to the trait signature. - **Unit type** (`()`): Return `Ok(())` on success - **Boolean**: Parse "1"/"0" to `true`/`false` - **String**: Return the value directly - **Vec**: Parse JSON array response - **Number**: Parse string to number ### Creating a New Adapter To create a new adapter (e.g., `MockRedisClient` for testing): 1. **Create the Module File**: Add `src/adapters/mock_client.rs`. 2. **Define the Struct**: Create the adapter struct. ```rust use forge_shared::RedisClient; use std::collections::HashMap; use std::sync::RwLock; /// Mock Redis client for testing. /// /// Uses RwLock to allow multiple concurrent readers while maintaining thread safety. pub struct MockRedisClient { data: RwLock>, } impl MockRedisClient { pub fn new() -> Self { Self { data: RwLock::new(HashMap::new()), } } } ``` 3. **Implement the Trait**: Implement all `RedisClient` methods. ```rust impl RedisClient for MockRedisClient { fn hash_mset(&self, key: String, fields: Vec<(String, String)>) -> Result<(), String> { // Acquire write lock only when modifying data let mut data = self.data.write().unwrap(); for (field, value) in fields { let hash_key = format!("{}:{}", key, field); data.insert(hash_key, value); } Ok(()) } fn hash_get(&self, key: String, field: String) -> Result { // Acquire read lock - multiple threads can read concurrently let data = self.data.read().unwrap(); let hash_key = format!("{}:{}", key, field); Ok(data.get(&hash_key) .map(|v| v.clone()) .unwrap_or_default()) } // ... implement other methods } ``` 4. **Register the Module**: Add to `src/adapters/mod.rs`. ```rust pub mod redis_client; pub mod mock_client; pub use redis_client::ExtensionRedisClient; pub use mock_client::MockRedisClient; ``` ### Concurrency Best Practices > [!IMPORTANT] > Choose the right synchronization primitive based on your access patterns and performance requirements. **Recommended Synchronization Primitives:** | Primitive | Use Case | Performance | Dependency | | --------------------- | ---------------------------------------- | ----------------------- | ---------------- | | **`RwLock`** | Read-heavy workloads, concurrent readers | Good (multiple readers) | Standard library | | **`Mutex`** | Write-heavy or exclusive access required | Fair (single lock) | Standard library | | **`DashMap`** | Extreme high-frequency reads/writes | Excellent (lock-free) | External crate | **When to use each:** - **`RwLock`**: Best for most use cases. Allows multiple concurrent readers, only blocks on writes. Use this by default. - **`Mutex`**: Only when you need exclusive access or operations are very lightweight (< 1μs). - **`DashMap`**: When profiling shows `RwLock` is a bottleneck and you need lock-free performance. **Why avoid `Mutex` for read-heavy workloads?** - Blocks all threads (readers and writers) on every access - No concurrent reads possible - Can cause performance bottlenecks in high-concurrency scenarios ### Best Practices - **Error Consistency**: Always check for "Error:" prefix in Redis responses - **Type Safety**: Ensure return types match the trait signature exactly - **Logging**: Log operations at DEBUG level for troubleshooting - **Response Parsing**: Handle all possible response formats (success, error, unexpected) - **Documentation**: Document the purpose and behavior of each method - **Testing**: Test adapters with both success and error scenarios