cache-kit

An async, ORM-agnostic caching library for Rust that helps you place clear cache boundaries between your database and application logic.

Get Started View on GitHub


What cache-kit Is

cache-kit is designed for modern Rust services that:

  • Use async runtimes (tokio)
  • Talk to databases through ORMs or query builders
  • Expose functionality via REST, gRPC, background workers, or API services

cache-kit focuses on how caching fits into your system, not on owning your framework, transport, or persistence layer.

Key Characteristics

  • Async-first and runtime-friendly — Built for tokio-based applications
  • ORM-agnostic — Works with any database layer
  • Backend-agnostic — Swap between Redis, Memcached, InMemory without code changes
  • Safe to embed — Use inside libraries, SDKs, and services
  • Explicit boundaries — Clear cache ownership and behavior

cache-kit sits between your database access and your application logic, not inside your HTTP framework or ORM.


What cache-kit Is Not

cache-kit deliberately does not:

  • Replace ORMs or query builders
  • Depend on HTTP, REST, or web frameworks
  • Hide cache behavior behind implicit magic
  • Claim strong consistency across distributed backends
  • Act as a full application framework

If you are looking for an all-in-one web stack, cache-kit is not that.


Where cache-kit Fits

A typical async Rust service using cache-kit looks like this:

REST / gRPC / Workers / API Services
      ↓
Application Logic
      ↓
  cache-kit
      ↙        ↘
Cache Backend   Database / ORM

The same cached logic can be reused across:

  • REST endpoints
  • gRPC services
  • Background jobs
  • Agent or API services layered on top of your data

Async-First by Design

cache-kit is built for async Rust:

  • Designed to work with tokio-based applications
  • Compatible with async ORMs like SQLx and SeaORM
  • Does not manage or impose a runtime

You bring your runtime — cache-kit fits into it.

Note: cache-kit is async-first and designed for modern async databases (SQLx, SeaORM, tokio-postgres). See the Async Programming Model page for details.


ORM-Agnostic and Database-Friendly

cache-kit does not depend on any ORM.

It operates on:

  • Serializable entities
  • Deterministic cache keys
  • Explicit cache boundaries

This means:

  • You can swap ORMs without rewriting cache logic
  • Cache behavior lives outside persistence concerns
  • Database models and cached entities remain your responsibility

ORM Compatibility

ORM/Database Layer Status Example
SQLx ✅ Tier-1 actixsqlx example
SeaORM ✅ Compatible Community contributions welcome
Diesel ✅ Compatible Community contributions welcome
tokio-postgres ✅ Compatible Works with any database layer

A reference implementation using Actix + SQLx is provided, but cache-kit is not tied to that stack.


Serialization Is Explicit

cache-kit treats serialization as a first-class, pluggable concern.

Supported Formats

  • Postcard (Tier-1, recommended for performance)
  • MessagePack (Planned, community contributions welcome)

Serialization is:

  • Independent of transport (HTTP, gRPC, workers)
  • Independent of cache backend (Redis, Memcached, InMemory)
  • Chosen explicitly by the user

Known Limitations

Some serialization formats (including Postcard) do not support certain data types out of the box.

For example:

  • Decimal types are not supported by Postcard
  • You must either:
    • Convert to supported primitives (e.g., store as i64 cents instead of Decimal dollars)
    • Implement custom serialization
    • Choose a different serialization strategy

cache-kit does not silently work around these limitations — they are part of the design trade-off.

See the Serialization Support page for detailed guidance.


Backend-Agnostic Caching

cache-kit supports multiple cache backends with explicit tiering:

Tier-0: Production-Grade

  • Redis and Redis-compatible managed services (AWS ElastiCache, DigitalOcean Managed Redis)
  • Memcached

Tier-1: Development & Testing

  • InMemory — Fast, zero-dependency, perfect for tests and local development

Backends are treated as replaceable implementations, not architectural commitments.

See the Cache Backend Support page for configuration details.


Design Philosophy

cache-kit focuses on:

  • Boundaries, not ownership — Integrate around ORMs, frameworks, and transports
  • Explicit behavior, not hidden magic — No surprises, predictable outcomes
  • Integration, not lock-in — Works with your existing stack

It is safe to use:

  • Inside libraries
  • Inside SDKs
  • Inside large services
  • Alongside existing frameworks and ORMs

cache-kit aims to be predictable, composable, and honest about trade-offs.


Quick Example

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

// 1. Define your entity
#[derive(Clone, Serialize, Deserialize)]
struct User {
    id: String,
    name: String,
}

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

// 3. Create a feeder
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; }
}

// 4. Create a repository
struct UserRepository;

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

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

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

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

    if let Some(user) = feeder.user {
        println!("User: {}", user.name);
    }

    Ok(())
}

Next Steps


License

MIT License - Copyright (c) 2025 Kishore kumar Neelamegam

See LICENSE for details.