Redis Integration Guide
Armature provides a unified Redis integration through armature-redis with connection pooling, pub/sub, and dependency injection.
Table of Contents
- Overview
- Features
- Installation
- Basic Usage
- Connection Pooling
- Pub/Sub
- Dependency Injection
- Using with Other Crates
- Configuration
- Best Practices
- Summary
Overview
The armature-redis crate provides a centralized Redis client that other Armature crates depend on. This ensures:
- Single Connection Pool: All crates share one pool
- DI Integration: Redis is injected from the DI container
- Consistent Configuration: One place to configure Redis
- Connection Reuse: Efficient resource utilization
Features
- โ Connection pooling with bb8
- โ Pub/Sub messaging
- โ Cluster support
- โ TLS encryption
- โ Sentinel support
- โ DI container integration
- โ Convenience methods for common operations
Installation
[dependencies]
armature-redis = "0.1"
With optional features:
[dependencies]
armature-redis = { version = "0.1", features = ["cluster", "tls"] }
Basic Usage
use armature_redis::{RedisService, RedisConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Configure Redis
let config = RedisConfig::builder()
.url("redis://localhost:6379")
.pool_size(10)
.build();
// Create service
let redis = RedisService::new(config).await?;
// Use convenience methods
redis.set_value("key", "value").await?;
let value: Option<String> = redis.get_value("key").await?;
// Or get a connection for raw commands
let mut conn = redis.get().await?;
redis::cmd("HSET")
.arg("hash")
.arg("field")
.arg("value")
.query_async(&mut *conn)
.await?;
Ok(())
}
Connection Pooling
The service manages a connection pool automatically:
use armature_redis::{RedisService, RedisConfig};
let config = RedisConfig::builder()
.url("redis://localhost:6379")
.pool_size(20) // Max connections
.min_idle(Some(5)) // Keep at least 5 idle
.connection_timeout(Duration::from_secs(5))
.build();
let redis = RedisService::new(config).await?;
// Get pool statistics
let stats = redis.pool_stats();
println!("Connections: {}, Idle: {}", stats.connections, stats.idle_connections);
Pub/Sub
Redis pub/sub for real-time messaging:
use armature_redis::{RedisService, RedisConfig};
let redis = RedisService::new(config).await?;
let pubsub = redis.pubsub()?;
// Subscribe to a channel
let mut subscription = pubsub.subscribe("events").await?;
// Spawn task to receive messages
tokio::spawn(async move {
while let Some(message) = subscription.recv().await {
println!("Received: {} on {}", message.payload, message.channel);
}
});
// Publish messages
pubsub.publish("events", "Hello, World!").await?;
Pattern subscriptions:
// Subscribe to all channels matching "user:*"
let mut subscription = pubsub.psubscribe("user:*").await?;
Dependency Injection
Register Redis as a singleton in your application:
use armature_framework::prelude::*;
use armature_redis::{RedisService, RedisConfig};
use std::sync::Arc;
#[module]
struct RedisModule;
#[module_impl]
impl RedisModule {
#[provider(singleton)]
async fn redis_service() -> Arc<RedisService> {
let config = RedisConfig::from_env().build();
Arc::new(RedisService::new(config).await.unwrap())
}
}
// Use in controllers
#[controller("/data")]
struct DataController;
#[controller_impl]
impl DataController {
#[get("/:key")]
async fn get_data(
&self,
#[inject] redis: Arc<RedisService>,
key: Path<String>,
) -> Result<Json<Value>, HttpError> {
let value: Option<String> = redis.get_value(&key).await?;
Ok(Json(json!({ "value": value })))
}
}
Using with Other Crates
Other Armature crates automatically use armature-redis when the redis feature is enabled:
armature-cache
use armature_cache::RedisCache;
use armature_redis::RedisService;
use std::sync::Arc;
#[module_impl]
impl CacheModule {
#[provider(singleton)]
fn redis_cache(redis: Arc<RedisService>) -> RedisCache {
RedisCache::from_service(redis)
}
}
armature-queue
use armature_queue::Queue;
use armature_redis::RedisService;
#[module_impl]
impl QueueModule {
#[provider(singleton)]
fn job_queue(redis: Arc<RedisService>) -> Queue {
Queue::from_service(redis, "jobs")
}
}
armature-ratelimit
use armature_ratelimit::RedisRateLimiter;
use armature_redis::RedisService;
#[module_impl]
impl RateLimitModule {
#[provider(singleton)]
fn rate_limiter(redis: Arc<RedisService>) -> RedisRateLimiter {
RedisRateLimiter::from_service(redis)
}
}
armature-distributed
use armature_distributed::{DistributedLock, LeaderElection};
use armature_redis::RedisService;
#[module_impl]
impl DistributedModule {
#[provider(singleton)]
fn distributed_lock(redis: Arc<RedisService>) -> DistributedLock {
DistributedLock::from_service(redis)
}
#[provider(singleton)]
fn leader_election(redis: Arc<RedisService>) -> LeaderElection {
LeaderElection::from_service(redis, "my-service")
}
}
armature-session
use armature_session::RedisSessionStore;
use armature_redis::RedisService;
#[module_impl]
impl SessionModule {
#[provider(singleton)]
fn session_store(redis: Arc<RedisService>) -> RedisSessionStore {
RedisSessionStore::from_service(redis)
}
}
Configuration
Environment Variables
# Required
REDIS_URL=redis://localhost:6379
# Optional
REDIS_POOL_SIZE=10
REDIS_DATABASE=0
REDIS_USERNAME=default
REDIS_PASSWORD=secret
REDIS_TLS=true
REDIS_CLUSTER=true
REDIS_CLUSTER_NODES=node1:6379,node2:6379,node3:6379
Programmatic Configuration
let config = RedisConfig::builder()
.url("redis://localhost:6379")
.pool_size(20)
.min_idle(5)
.database(1)
.username("user")
.password("secret")
.connection_timeout(Duration::from_secs(5))
.command_timeout(Duration::from_secs(30))
.tls(true)
.connection_name("my-app")
.build();
Cluster Mode
let config = RedisConfig::builder()
.cluster_nodes(vec![
"redis://node1:6379".to_string(),
"redis://node2:6379".to_string(),
"redis://node3:6379".to_string(),
])
.build();
Best Practices
1. Register as Singleton
Always register RedisService as a singleton to share the connection pool:
#[provider(singleton)] // Important!
async fn redis_service() -> Arc<RedisService> {
// ...
}
2. Use Environment Configuration
// Prefer environment-based config for flexibility
let config = RedisConfig::from_env().build();
3. Use Convenience Methods
The service provides convenience methods for common operations:
// Instead of raw commands
redis.set_value("key", "value").await?;
redis.get_value::<String>("key").await?;
redis.delete("key").await?;
redis.incr("counter", 1).await?;
redis.hset("hash", "field", "value").await?;
4. Handle Connection Errors
match redis.get_value::<String>("key").await {
Ok(Some(value)) => println!("Found: {}", value),
Ok(None) => println!("Key not found"),
Err(e) if e.is_retryable() => {
// Retry logic
}
Err(e) => return Err(e.into()),
}
5. Use Pub/Sub for Real-Time Events
// Publisher
redis.pubsub()?.publish("events", &event_json).await?;
// Subscriber
let mut sub = redis.pubsub()?.subscribe("events").await?;
while let Some(msg) = sub.recv().await {
handle_event(&msg.payload);
}
Summary
Key Concepts:
- Centralized Redis:
armature-redisis the single source for Redis connections - Connection Pooling: Efficient connection management with bb8
- DI Integration: Register once, inject everywhere
- Crate Integration: All Redis-dependent crates use
armature-redis - Pub/Sub: Built-in messaging support
Crates Using armature-redis:
| Crate | Purpose |
|---|---|
armature-cache |
Redis caching backend |
armature-queue |
Job queue storage |
armature-distributed |
Locks, leader election |
armature-ratelimit |
Rate limiting storage |
armature-session |
Session storage |
Configuration Quick Reference:
REDIS_URL=redis://localhost:6379
REDIS_POOL_SIZE=10
REDIS_PASSWORD=secret