cache-kit 0.9.0

A type-safe, fully generic, production-ready caching framework for Rust
Documentation
---
layout: single
title: Async Programming Model
description: "Understanding cache-kit's async-first design and tokio integration"
permalink: /async-model/
nav_order: 6
date: 2025-12-25
---

---

## Async-First Philosophy

cache-kit is built from the ground up as an **async-first** library. This design choice reflects the reality of modern Rust services where:

- Database queries are async (SQLx, SeaORM, tokio-postgres)
- HTTP handlers are async (Axum, Actix, warp)
- gRPC services are async (tonic)
- Background workers are async (tokio, async-std)

The cache layer sits between these components and must integrate seamlessly with async workflows.

---

## Tokio Runtime Integration

cache-kit is designed for `tokio`-based applications. The library does not:

- Spawn its own runtime
- Require a specific runtime configuration
- Impose threading models on your application

Instead, cache-kit operates within **your** existing tokio runtime.

### Runtime Requirements

```toml
[dependencies]
tokio = { version = "1.41", features = ["rt", "sync", "macros"] }
cache-kit = "0.9"
```

The minimum required tokio features:

- `rt` — Runtime support
- `sync` — Synchronization primitives (Arc, Mutex, RwLock)
- `macros``#[tokio::main]` attribute macro

---

## Interaction Model

The typical interaction flow follows this pattern:

```
Async Database → Async Cache → Async Application
```

All cache operations work seamlessly within async contexts. For detailed ORM integration examples (SQLx, SeaORM, Diesel), see [Database & ORM Compatibility](/database-compatibility).

---

## Why DataRepository is Async

The `DataRepository` trait uses **async** methods:

```rust
pub trait DataRepository<T: CacheEntity>: Send + Sync {
    async fn fetch_by_id(&self, id: &T::Key) -> Result<Option<T>>;
}
```

This design is intentional and provides several benefits:

1. **Native async support** — Aligns with modern Rust practices and integrates seamlessly with async databases
2. **Flexibility** — Works with both sync and async database layers (see [Database Compatibility]/database-compatibility for Diesel example using `spawn_blocking`)
3. **Backend compatibility** — Cache backends (Redis, Memcached) are inherently async

**Recommended async databases:**

- **SQLx** — Async, compile-time checked SQL
- **SeaORM** — Async ORM for Rust
- **tokio-postgres** — Pure async PostgreSQL client

For detailed repository implementation examples, see [Database & ORM Compatibility](/database-compatibility).

### ⚠️ NEVER use block_in_place + block_on

**NEVER use `block_in_place` + `Handle::current().block_on()`** — this pattern is incorrect. Always use `async fn` with `.await` for async databases. For synchronous ORMs like Diesel, use `tokio::task::spawn_blocking` (see [Database Compatibility](/database-compatibility) for examples).

---

## Async Cache Backends

Cache backends are fully async and follow the same initialization pattern:

```rust
// Redis
use cache_kit::backend::{RedisBackend, RedisConfig};
let config = RedisConfig::default(); // Uses localhost:6379
let backend = RedisBackend::new(config).await?;

// Memcached
use cache_kit::backend::{MemcachedBackend, MemcachedConfig};
let config = MemcachedConfig { servers: vec!["localhost:11211".to_string()], ..Default::default() };
let backend = MemcachedBackend::new(config)?;

// InMemory (lock-free via DashMap)
use cache_kit::backend::InMemoryBackend;
let backend = InMemoryBackend::new();

// All use the same expander API
let mut expander = CacheExpander::new(backend);
```

All backends work seamlessly within your async context — no special handling required.

---

## Runtime Choice is Yours

cache-kit does not:

- Require a specific tokio runtime configuration
- Spawn background tasks (no `tokio::spawn` calls)
- Create thread pools
- Impose executor choices

You control:

- Runtime flavor (multi-thread, current-thread)
- Worker thread count
- Task spawning strategy
- Shutdown behavior

---

## Best Practices

### DO

- ✅ Use `tokio::main` for your application entry point
- ✅ Make `DataRepository::fetch_by_id` an async function
- ✅ Use async database drivers (SQLx, SeaORM, tokio-postgres)
- ✅ Let cache-kit operate within your existing runtime
- ✅ Keep async boundaries explicit and clear

### DON'T

- ❌ Use `block_in_place` + `block_on` (incorrect pattern)
- ❌ Call `block_on` inside async contexts
- ❌ Create multiple tokio runtimes unnecessarily
- ❌ Assume cache-kit manages runtime lifecycle

---

## Example: Full Async Service

For complete working examples of tokio-based services using cache-kit with async operations:

- **[examples/actixsqlx]https://github.com/megamsys/cache-kit.rs/tree/main/examples/actixsqlx** — Actix Web integration with SQLx, async database operations, and cache-kit's async API
- **[examples/actixsqlx/src/services/user_service.rs]https://github.com/megamsys/cache-kit.rs/tree/main/examples/actixsqlx/src/services/user_service.rs** — Service layer implementation with caching

---

## Next Steps

- Learn about [Core Concepts]/concepts in cache-kit
- Explore [Database & ORM Compatibility]/database-compatibility for detailed ORM integration examples
- Review [API Frameworks]/api-frameworks for framework-specific integration patterns