multi_tier_cache/traits.rs
1//! Cache Backend Traits
2//!
3//! This module defines the trait abstractions that allow users to implement
4//! custom cache backends for both L1 (in-memory) and L2 (distributed) caches.
5//!
6//! # Architecture
7//!
8//! - `CacheBackend`: Core trait for all cache implementations
9//! - `L2CacheBackend`: Extended trait for L2 caches with TTL introspection
10//! - `StreamingBackend`: Optional trait for event streaming capabilities
11//!
12//! # Example: Custom L1 Backend
13//!
14/// ```rust,no_run
15/// use multi_tier_cache::error::{CacheError, CacheResult};
16/// use bytes::Bytes;
17/// use std::time::Duration;
18/// use futures_util::future::BoxFuture;
19/// use multi_tier_cache::CacheBackend;
20///
21/// struct MyCustomCache;
22///
23/// impl CacheBackend for MyCustomCache {
24/// fn get<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, Option<Bytes>> {
25/// Box::pin(async move { None })
26/// }
27///
28/// fn set_with_ttl<'a>(&'a self, _key: &'a str, _value: Bytes, _ttl: Duration) -> BoxFuture<'a, CacheResult<()>> {
29/// Box::pin(async move { Ok(()) })
30/// }
31///
32/// fn remove<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, CacheResult<()>> {
33/// Box::pin(async move { Ok(()) })
34/// }
35///
36/// fn health_check(&self) -> BoxFuture<'_, bool> {
37/// Box::pin(async move { true })
38/// }
39///
40/// fn name(&self) -> &'static str { "MyCache" }
41/// }
42/// ```
43use crate::error::CacheResult;
44use bytes::Bytes;
45use futures_util::future::BoxFuture;
46use std::time::Duration;
47
48/// Core cache backend trait for both L1 and L2 caches
49///
50/// This trait defines the essential operations that any cache backend must support.
51/// Implement this trait to create custom L1 (in-memory) or L2 (distributed) cache backends.
52///
53/// # Required Operations
54///
55/// - `get`: Retrieve a value by key
56/// - `set_with_ttl`: Store a value with a time-to-live
57/// - `remove`: Delete a value by key
58/// - `health_check`: Verify cache backend is operational
59///
60/// # Thread Safety
61///
62/// Implementations must be `Send + Sync` to support concurrent access across async tasks.
63///
64/// # Performance Considerations
65///
66/// - `get` operations should be optimized for low latency (target: <1ms for L1, <5ms for L2)
67/// - `set_with_ttl` operations can be slightly slower but should still be fast
68/// - Consider connection pooling for distributed backends
69///
70/// # Example
71///
72/// See module-level documentation for a complete example.
73pub trait CacheBackend: Send + Sync {
74 /// Get value from cache by key
75 ///
76 /// # Arguments
77 ///
78 /// * `key` - The cache key to retrieve
79 ///
80 /// # Returns
81 ///
82 /// * `Some(value)` - Value found in cache
83 /// * `None` - Key not found or expired
84 fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<Bytes>>;
85
86 /// Set value in cache with time-to-live
87 ///
88 /// # Arguments
89 ///
90 /// * `key` - The cache key
91 /// * `value` - The value to store (raw bytes)
92 /// * `ttl` - Time-to-live duration
93 ///
94 /// # Returns
95 ///
96 /// * `Ok(())` - Value successfully cached
97 /// * `Err(e)` - Cache operation failed
98 fn set_with_ttl<'a>(
99 &'a self,
100 key: &'a str,
101 value: Bytes,
102 ttl: Duration,
103 ) -> BoxFuture<'a, CacheResult<()>>;
104
105 /// Set value in cache with default TTL (5 minutes)
106 fn set<'a>(&'a self, key: &'a str, value: Bytes) -> BoxFuture<'a, CacheResult<()>> {
107 self.set_with_ttl(key, value, std::time::Duration::from_mins(5))
108 }
109
110 /// Remove value from cache
111 ///
112 /// # Arguments
113 ///
114 /// * `key` - The cache key to remove
115 ///
116 /// # Returns
117 ///
118 /// * `Ok(())` - Value removed (or didn't exist)
119 /// * `Err(e)` - Cache operation failed
120 fn remove<'a>(&'a self, key: &'a str) -> BoxFuture<'a, CacheResult<()>>;
121
122 /// Check if cache backend is healthy
123 ///
124 /// This method should verify that the cache backend is operational.
125 /// For distributed caches, this typically involves a ping or connectivity check.
126 ///
127 /// # Returns
128 ///
129 /// * `true` - Cache is healthy and operational
130 /// * `false` - Cache is unhealthy or unreachable
131 fn health_check(&self) -> BoxFuture<'_, bool>;
132
133 /// Remove keys matching a pattern
134 ///
135 /// # Arguments
136 ///
137 /// * `pattern` - Glob-style pattern (e.g. "user:*")
138 ///
139 /// # Returns
140 ///
141 /// * `Ok(())` - Pattern processed
142 /// * `Err(e)` - Operation failed
143 fn remove_pattern<'a>(&'a self, _pattern: &'a str) -> BoxFuture<'a, CacheResult<()>> {
144 Box::pin(async { Ok(()) })
145 }
146
147 /// Get the name of this cache backend
148 fn name(&self) -> &'static str;
149}
150
151// (No longer needed since traits are now dyn-compatible)
152
153/// Extended trait for L2 cache backends with TTL introspection
154///
155/// This trait extends `CacheBackend` with the ability to retrieve both a value
156/// and its remaining TTL. This is essential for implementing efficient L2-to-L1
157/// promotion with accurate TTL propagation.
158///
159/// # Use Cases
160///
161/// - L2-to-L1 promotion with same TTL
162/// - TTL-based cache warming strategies
163/// - Monitoring and analytics
164///
165/// # Example
166///
167/// ```rust,no_run
168/// use multi_tier_cache::error::{CacheError, CacheResult};
169/// use bytes::Bytes;
170/// use std::time::Duration;
171/// use futures_util::future::BoxFuture;
172/// use multi_tier_cache::{CacheBackend, L2CacheBackend};
173///
174/// struct MyDistributedCache;
175///
176/// impl CacheBackend for MyDistributedCache {
177/// fn get<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, Option<Bytes>> { Box::pin(async move { None }) }
178/// fn set_with_ttl<'a>(&'a self, _k: &'a str, _v: Bytes, _t: Duration) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async move { Ok(()) }) }
179/// fn remove<'a>(&'a self, _k: &'a str) -> BoxFuture<'a, CacheResult<()>> { Box::pin(async move { Ok(()) }) }
180/// fn health_check(&self) -> BoxFuture<'_, bool> { Box::pin(async move { true }) }
181/// fn name(&self) -> &'static str { "MyDistCache" }
182/// }
183///
184/// impl L2CacheBackend for MyDistributedCache {
185/// fn get_with_ttl<'a>(&'a self, _key: &'a str) -> BoxFuture<'a, Option<(Bytes, Option<Duration>)>> {
186/// Box::pin(async move { None })
187/// }
188/// }
189/// ```
190pub trait L2CacheBackend: CacheBackend {
191 /// Get value with its remaining TTL from L2 cache
192 fn get_with_ttl<'a>(&'a self, key: &'a str)
193 -> BoxFuture<'a, Option<(Bytes, Option<Duration>)>>;
194}
195
196// (No longer needed since traits are now dyn-compatible)
197
198/// Optional trait for cache backends that support event streaming
199///
200/// # Type Definitions
201///
202/// * `StreamEntry` - A single entry in a stream: `(id, fields)` where fields are `Vec<(key, value)>`
203pub type StreamEntry = (String, Vec<(String, String)>);
204
205/// Optional trait for cache backends that support event streaming
206///
207/// This trait defines operations for event-driven architectures using
208/// streaming data structures like Redis Streams.
209///
210/// # Capabilities
211///
212/// - Publish events to streams with automatic trimming
213/// - Read latest entries (newest first)
214/// - Read entries with blocking support
215///
216/// # Backend Requirements
217///
218/// Not all cache backends support streaming. This trait is optional and
219/// should only be implemented by backends with native streaming support
220/// (e.g., Redis Streams, Kafka, Pulsar).
221///
222/// # Example
223///
224/// ```rust,no_run
225/// use multi_tier_cache::error::{CacheError, CacheResult};
226/// use multi_tier_cache::StreamingBackend;
227/// use multi_tier_cache::traits::StreamEntry;
228/// use futures_util::future::BoxFuture;
229///
230/// struct MyStreamingCache;
231///
232/// impl StreamingBackend for MyStreamingCache {
233/// fn stream_add<'a>(
234/// &'a self,
235/// _stream_key: &'a str,
236/// _fields: Vec<(String, String)>,
237/// _maxlen: Option<usize>,
238/// ) -> BoxFuture<'a, CacheResult<String>> {
239/// Box::pin(async move { Ok("entry-id".to_string()) })
240/// }
241///
242/// fn stream_read_latest<'a>(
243/// &'a self,
244/// _stream_key: &'a str,
245/// _count: usize,
246/// ) -> BoxFuture<'a, CacheResult<Vec<StreamEntry>>> {
247/// Box::pin(async move { Ok(vec![]) })
248/// }
249///
250/// fn stream_read<'a>(
251/// &'a self,
252/// _stream_key: &'a str,
253/// _last_id: &'a str,
254/// _count: usize,
255/// _block_ms: Option<usize>,
256/// ) -> BoxFuture<'a, CacheResult<Vec<StreamEntry>>> {
257/// Box::pin(async move { Ok(vec![]) })
258/// }
259///
260/// fn stream_create_group<'a>(&'a self, _: &'a str, _: &'a str, _: &'a str) -> BoxFuture<'a, CacheResult<()>> {
261/// Box::pin(async move { Ok(()) })
262/// }
263///
264/// fn stream_read_group<'a>(&'a self, _: &'a str, _: &'a str, _: &'a str, _: usize, _: Option<usize>) -> BoxFuture<'a, CacheResult<Vec<StreamEntry>>> {
265/// Box::pin(async move { Ok(vec![]) })
266/// }
267///
268/// fn stream_ack<'a>(&'a self, _: &'a str, _: &'a str, _: &'a [String]) -> BoxFuture<'a, CacheResult<()>> {
269/// Box::pin(async move { Ok(()) })
270/// }
271/// }
272/// ```
273pub trait StreamingBackend: Send + Sync {
274 /// Add an entry to a stream
275 fn stream_add<'a>(
276 &'a self,
277 stream_key: &'a str,
278 fields: Vec<(String, String)>,
279 maxlen: Option<usize>,
280 ) -> BoxFuture<'a, CacheResult<String>>;
281
282 /// Read the latest N entries from a stream (newest first)
283 fn stream_read_latest<'a>(
284 &'a self,
285 stream_key: &'a str,
286 count: usize,
287 ) -> BoxFuture<'a, CacheResult<Vec<StreamEntry>>>;
288
289 /// Read entries from a stream with optional blocking
290 fn stream_read<'a>(
291 &'a self,
292 stream_key: &'a str,
293 last_id: &'a str,
294 count: usize,
295 block_ms: Option<usize>,
296 ) -> BoxFuture<'a, CacheResult<Vec<StreamEntry>>>;
297
298 /// Create a consumer group for a stream
299 fn stream_create_group<'a>(
300 &'a self,
301 stream_key: &'a str,
302 group_name: &'a str,
303 id: &'a str,
304 ) -> BoxFuture<'a, CacheResult<()>>;
305
306 /// Read entries from a stream as a consumer group
307 fn stream_read_group<'a>(
308 &'a self,
309 stream_key: &'a str,
310 group_name: &'a str,
311 consumer_name: &'a str,
312 count: usize,
313 block_ms: Option<usize>,
314 ) -> BoxFuture<'a, CacheResult<Vec<StreamEntry>>>;
315
316 /// Acknowledge entry processing
317 fn stream_ack<'a>(
318 &'a self,
319 stream_key: &'a str,
320 group_name: &'a str,
321 ids: &'a [String],
322 ) -> BoxFuture<'a, CacheResult<()>>;
323}