Prerequisites

  • Rust: 1.75 or later
  • Tokio: 1.41 or later (async runtime)

For AI Agents & Automated Tools

If you’re using an AI agent, code generator, or automated tool to integrate cache-kit, see the Installation for Agents page for structured, machine-readable setup information.


Installation

Add cache-kit to your Cargo.toml:

[dependencies]
cache-kit = { version = "0.9" }
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.41", features = ["rt", "sync", "macros"] }

Feature Flags

cache-kit uses feature flags to enable optional backends:

Feature Description Default
inmemory In-memory cache backend ✅ Enabled
redis Redis backend with connection pooling ❌ Optional
memcached Memcached backend ❌ Optional
all Enable all backends ❌ Optional

Choosing Your API: CacheExpander vs CacheService

cache-kit provides two APIs for cache operations. For most use cases, use CacheService.

Quick Decision Table

Scenario Use Reason
Building web service (Axum, Actix) CacheService Already Arc’d, clone is cheap
Building a library CacheExpander Flexibility in Arc wrapping
Uncertain CacheService 90% of use cases

⚠️ Can’t decide? Use CacheService. It’s simpler and covers most cases.

📖 For deeper understanding: See Core Concepts for detailed explanations of CacheExpander and CacheService, including design philosophy, usage patterns, and examples throughout the documentation.

Use CacheService when:

  • Building web services (Axum, Actix, Rocket)
  • Need to share cache across threads
  • Want simple, ergonomic API

Key characteristics:

  • Already wrapped in Arc internally
  • Implements Clone (cheap reference counting)
  • Methods: .execute(), .execute_with_config()
  • Perfect for dependency injection

Example:

use cache_kit::{CacheService, backend::RedisBackend};

// Create once at startup
let cache = CacheService::new(backend);

// Share across services (Clone is cheap - just Arc increment)
let user_service = UserService::new(cache.clone());
let product_service = ProductService::new(cache.clone());
let order_service = OrderService::new(cache.clone());

CacheExpander (Low-Level API)

Use CacheExpander when:

  • Need custom Arc wrapping patterns
  • Building cache middleware or custom abstractions
  • Want explicit control over ownership

Key characteristics:

  • No built-in Arc wrapper
  • Methods: .with(), .with_config()
  • Requires manual Arc wrapping for sharing

Example:

use cache_kit::{CacheExpander, backend::RedisBackend};
use std::sync::Arc;

let expander = CacheExpander::new(backend);

// Must wrap in Arc manually for sharing
let cache = Arc::new(expander);
let user_service = UserService::new(cache.clone());

Basic Installation (InMemory Only)

[dependencies]
cache-kit = { version = "0.9" }

This provides the InMemory backend, perfect for:

  • Development
  • Testing
  • Single-instance services

Redis Backend

[dependencies]
cache-kit = { version = "0.9", features = ["redis"] }

Enables production-grade Redis caching with connection pooling.

Memcached Backend

[dependencies]
cache-kit = { version = "0.9", features = ["memcached"] }

Enables Memcached backend for high-performance distributed caching.

All Backends

[dependencies]
cache-kit = { version = "0.9", features = ["all"] }

Enables all available backends. Useful for:

  • Testing multiple backends
  • Switching backends based on environment
  • Benchmarking comparisons

Minimal Configuration

cache-kit requires minimal configuration. Here’s a complete working example:

use cache_kit::{
    CacheEntity, CacheFeed, DataRepository, CacheService,
    backend::InMemoryBackend,
    strategy::CacheStrategy,
};
use serde::{Deserialize, Serialize};

#[derive(Clone, Serialize, Deserialize)]
struct User {
    id: String,
    name: String,
}

impl CacheEntity for User {
    type Key = String;
    fn cache_key(&self) -> Self::Key { self.id.clone() }
    fn cache_prefix() -> &'static str { "user" }
}

struct UserFeeder {
    id: String,
    user: Option<User>,
}

impl CacheFeed<User> for UserFeeder {
    fn entity_id(&mut self) -> String { self.id.clone() }
    fn feed(&mut self, entity: Option<User>) { self.user = entity; }
}

struct UserRepository;

impl DataRepository<User> for UserRepository {
    async fn fetch_by_id(&self, id: &String) -> cache_kit::Result<Option<User>> {
        Ok(Some(User {
            id: id.clone(),
            name: "Alice".to_string(),
        }))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let backend = InMemoryBackend::new();
    let cache = CacheService::new(backend);
    let repository = UserRepository;

    let mut feeder = UserFeeder {
        id: "user_001".to_string(),
        user: None,
    };

    cache.execute(&mut feeder, &repository, CacheStrategy::Refresh).await?;

    println!("User: {:?}", feeder.user);
    Ok(())
}

Backend Configuration

⚠️ Production Backend Requirement

InMemory backend is for development/testing only. Use Redis or Memcached for production deployments.


InMemory Backend

No configuration required:

use cache_kit::{CacheService, backend::InMemoryBackend};

let cache = CacheService::new(InMemoryBackend::new());

The InMemory backend uses DashMap internally, providing:

  • Lock-free concurrent HashMap
  • Thread-safe operations
  • Zero external dependencies

Redis Backend

use cache_kit::{CacheService, backend::{RedisBackend, RedisConfig}};
use std::time::Duration;

let config = RedisConfig {
    host: "localhost".to_string(),
    port: 6379,
    username: None,
    password: None,
    database: 0,
    pool_size: 16,
    connection_timeout: Duration::from_secs(5),
};

let backend = RedisBackend::new(config).await?;
let cache = CacheService::new(backend);

Redis Configuration Options

Field Type Default Description
host String "localhost" Redis server hostname or IP
port u16 6379 Redis server port
username Option<String> None Redis username (Redis 6+)
password Option<String> None Redis password for authentication
database u32 0 Redis database number (0-15)
pool_size u32 16 Connection pool size
connection_timeout Duration 5s Connection timeout

Configuration Examples

use std::time::Duration;

// Basic configuration (all defaults)
let config = RedisConfig::default();

// Custom host and port
let config = RedisConfig {
    host: "redis.example.com".to_string(),
    port: 6379,
    ..Default::default()
};

// With authentication
let config = RedisConfig {
    host: "redis.example.com".to_string(),
    port: 6379,
    password: Some("secret".to_string()),
    database: 1,
    ..Default::default()
};

// With custom pool size
let config = RedisConfig {
    host: "localhost".to_string(),
    port: 6379,
    pool_size: 32,
    connection_timeout: Duration::from_secs(10),
    ..Default::default()
};

Environment-Based Configuration

use std::env;
use std::time::Duration;

let redis_host = env::var("REDIS_HOST")
    .unwrap_or_else(|_| "localhost".to_string());
let redis_port = env::var("REDIS_PORT")
    .ok()
    .and_then(|p| p.parse().ok())
    .unwrap_or(6379);
let redis_password = env::var("REDIS_PASSWORD").ok();

let config = RedisConfig {
    host: redis_host,
    port: redis_port,
    password: redis_password,
    ..Default::default()
};

let backend = RedisBackend::new(config).await?;

Memcached Backend

use cache_kit::{CacheService, backend::{MemcachedBackend, MemcachedConfig}};
use std::time::Duration;

let config = MemcachedConfig {
    servers: vec!["localhost:11211".to_string()],
    pool_size: 16,
    connection_timeout: Duration::from_secs(5),
};

let cache = CacheService::new(MemcachedBackend::new(config).await?);

Memcached Configuration Options

Field Type Default Description
servers Vec<String> Required Memcached server addresses
pool_size u32 16 Connection pool size
connection_timeout Duration 5s Connection timeout

Multiple Memcached Servers

use cache_kit::backend::{MemcachedBackend, MemcachedConfig};
use std::time::Duration;

let config = MemcachedConfig {
    servers: vec![
        "memcached-01:11211".to_string(),
        "memcached-02:11211".to_string(),
        "memcached-03:11211".to_string(),
    ],
    pool_size: 20,
    connection_timeout: Duration::from_secs(10),
};

let backend = MemcachedBackend::new(config).await?;

TTL Configuration

Configure time-to-live (TTL) for cached entries:

Global TTL

use std::time::Duration;
use cache_kit::{CacheService, observability::TtlPolicy, backend::InMemoryBackend};

let cache = CacheService::new(InMemoryBackend::new());
// Note: TTL configuration via CacheService is set through backend configuration

No TTL (Cache Forever)

use cache_kit::{CacheService, backend::InMemoryBackend};

// Don't set TTL - cached entries never expire
let cache = CacheService::new(InMemoryBackend::new());

Note: “Cache forever” is not recommended for production. Always set appropriate TTLs based on your data freshness requirements.


Environment-Based Configuration

Create a configuration module for your application:

use cache_kit::{CacheService, backend::{InMemoryBackend, RedisBackend, RedisConfig}};
use std::env;
use std::time::Duration;

pub enum Environment {
    Development,
    Production,
}

impl Environment {
    pub fn from_env() -> Self {
        match env::var("ENV").as_deref() {
            Ok("production") => Environment::Production,
            _ => Environment::Development,
        }
    }
}

pub async fn create_cache_service() -> Result<CacheService<impl cache_kit::backend::CacheBackend>, Box<dyn std::error::Error>> {
    match Environment::from_env() {
        Environment::Development => {
            Ok(CacheService::new(InMemoryBackend::new()))
        }
        Environment::Production => {
            let redis_host = env::var("REDIS_HOST")
                .unwrap_or_else(|_| "localhost".to_string());
            let redis_password = env::var("REDIS_PASSWORD").ok();

            let config = RedisConfig {
                host: redis_host,
                password: redis_password,
                pool_size: 20,
                connection_timeout: Duration::from_secs(10),
                ..Default::default()
            };

            let backend = RedisBackend::new(config).await?;
            Ok(CacheService::new(backend))
        }
    }
}

Usage:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cache = create_cache_service().await?;

    // Your application logic
    Ok(())
}

Docker Compose for Development

Use Docker Compose to run Redis and Memcached locally:

version: "3.8"

services:
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes

  memcached:
    image: memcached:1.6-alpine
    ports:
      - "11211:11211"
    command: memcached -m 64

volumes:
  redis_data:

Start services:

docker-compose up -d

Test connections:

# Redis
redis-cli ping  # Should return: PONG

# Memcached
echo "stats" | nc localhost 11211  # Should return stats

Testing Configuration

For unit and integration tests, use the InMemory backend:

#[cfg(test)]
mod tests {
    use super::*;
    use cache_kit::backend::InMemoryBackend;

    #[tokio::test]
    async fn test_user_caching() {
        // Use InMemory backend for tests (no external dependencies)
        let backend = InMemoryBackend::new();
        let mut expander = CacheExpander::new(backend);

        // Your test logic
    }
}

Production Checklist

Before deploying cache-kit to production:

  • Backend selected: Redis or Memcached for production
  • Connection pooling configured: Set pool_size in config (default: 16) or via REDIS_POOL_SIZE/MEMCACHED_POOL_SIZE environment variables
  • TTL policies defined: Set TTLs based on data freshness requirements
  • Error handling implemented: Handle cache failures gracefully
  • Monitoring enabled: Track cache hit/miss rates
  • Environment variables set: REDIS_HOST, REDIS_PORT, REDIS_PASSWORD, REDIS_POOL_SIZE (for Redis) or MEMCACHED_SERVERS, MEMCACHED_POOL_SIZE (for Memcached)
  • Fallback strategy: Application works if cache is unavailable
  • Load tested: Verify performance under expected load

Common Configuration Patterns

Pattern 1: Shared Cache Across Services

use cache_kit::{CacheService, backend::{RedisBackend, RedisConfig}};

let backend = RedisBackend::new(config).await?;
let cache = CacheService::new(backend);

// CacheService is Clone - easily share across services
let user_service = UserService::new(cache.clone());
let product_service = ProductService::new(cache.clone());

Next Steps

Updated: