π΄ In-Memory Caching & Redis
Redis is the standard in-memory storage engine for high-speed session management, caching, distributed locks, rate limiting, and real-time pub/sub.
Ferrox provides ferrox-database-redis, integrating the asynchronous redis crate with Tokio.
1. Connecting to Redisβ
Initialize the Redis client:
use redis::{AsyncCommands, Client};
use ferrox_errors::AppError;
pub struct RedisCache {
client: Client,
}
impl RedisCache {
pub fn new(redis_url: &str) -> Result<Self, AppError> {
let client = Client::open(redis_url)
.map_err(|e| AppError::DatabaseError(e.to_string()))?;
Ok(Self { client })
}
pub async fn set_str(&self, key: &str, value: &str, ttl_seconds: u64) -> Result<(), AppError> {
let mut con = self.client.get_async_connection().await
.map_err(|e| AppError::DatabaseError(e.to_string()))?;
let _: () = con.set_ex(key, value, ttl_seconds).await
.map_err(|e| AppError::DatabaseError(e.to_string()))?;
Ok(())
}
pub async fn get_str(&self, key: &str) -> Result<Option<String>, AppError> {
let mut con = self.client.get_async_connection().await
.map_err(|e| AppError::DatabaseError(e.to_string()))?;
let value: Option<String> = con.get(key).await
.map_err(|e| AppError::DatabaseError(e.to_string()))?;
Ok(value)
}
}
2. JSON Object Cachingβ
Store complex Rust structs in Redis using serde_json:
use serde::{Deserialize, Serialize};
impl RedisCache {
pub async fn set_json<T: Serialize>(&self, key: &str, value: &T, ttl_seconds: u64) -> Result<(), AppError> {
let json_str = serde_json::to_string(value)
.map_err(|e| AppError::InternalServerError(Box::new(e)))?;
self.set_str(key, &json_str, ttl_seconds).await
}
pub async fn get_json<T: for<'a> Deserialize<'a>>(&self, key: &str) -> Result<Option<T>, AppError> {
if let Some(json_str) = self.get_str(key).await? {
let obj = serde_json::from_str(&json_str)
.map_err(|e| AppError::InternalServerError(Box::new(e)))?;
Ok(Some(obj))
} else {
Ok(None)
}
}
}
3. Real-Time Pub/Sub Messagingβ
Publish events across distributed microservice instances using Redis channels:
pub async fn publish_event(&self, channel: &str, message: &str) -> Result<(), AppError> {
let mut con = self.client.get_async_connection().await
.map_err(|e| AppError::DatabaseError(e.to_string()))?;
let _: () = con.publish(channel, message).await
.map_err(|e| AppError::DatabaseError(e.to_string()))?;
Ok(())
}
4. β Best Practicesβ
- Always set TTLs on cache entries: Never store keys indefinitely without an expiration policy to avoid exhausting Redis RAM.