- Rework org and store UI state modules (rename/move store/getter files, add runtime and bridge wiring) - Update store UI components and page structure (navbar/cart split, new StoreView flow) - Apply broad markdown/YAML/HTML/CSS/JS formatting cleanup across docs, templates, and workflows
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:
graph TD
Repo[Repository Layer<br/>#40;forge-repositories#41;]
Trait[RedisClient Trait<br/>#40;forge-shared#41;]
Adapter[ExtensionRedisClient<br/>#40;adapter#41;]
Redis[Redis Module<br/>#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
Resulttypes - 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<String, String> |
hash_get |
Get a single field value | Result<String, String> |
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<Vec<String>, 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<Vec<String>, String> |
set_del |
Remove member | Result<(), String> |
Common Operations
| Method | Description | Returns |
|---|---|---|
key_exists |
Check if key exists | Result<bool, String> |
delete_key |
Delete key | Result<(), String> |
Usage Example
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:"
// 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:
// list_range returns JSON: ["item1", "item2", "item3"]
let items = client.list_range("mylist".to_string(), 0, -1)?;
// items: Vec<String> = 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:
-
Check the Trait: Ensure the method is defined in the
RedisClienttrait inforge_shared.// In forge_shared/src/redis_client.rs pub trait RedisClient: Send + Sync { fn hash_exists(&self, key: String, field: String) -> Result<bool, String>; } -
Implement the Method: Add the implementation to
ExtensionRedisClient.impl RedisClient for ExtensionRedisClient { fn hash_exists(&self, key: String, field: String) -> Result<bool, String> { // 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)), } } } -
Add Logging (if needed): For debugging, log the operation.
fn hash_exists(&self, key: String, field: String) -> Result<bool, String> { 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)), } } -
Handle Response Types: Match the return type to the trait signature.
- Unit type (
()): ReturnOk(())on success - Boolean: Parse "1"/"0" to
true/false - String: Return the value directly
- Vec: Parse JSON array response
- Number: Parse string to number
- Unit type (
Creating a New Adapter
To create a new adapter (e.g., MockRedisClient for testing):
-
Create the Module File: Add
src/adapters/mock_client.rs. -
Define the Struct: Create the adapter struct.
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<HashMap<String, String>>, } impl MockRedisClient { pub fn new() -> Self { Self { data: RwLock::new(HashMap::new()), } } } -
Implement the Trait: Implement all
RedisClientmethods.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<String, String> { // 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 } -
Register the Module: Add to
src/adapters/mod.rs.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<HashMap> |
Read-heavy workloads, concurrent readers | Good (multiple readers) | Standard library |
Mutex<HashMap> |
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 showsRwLockis 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