armature_cache/traits.rs
1//! Cache store trait definition.
2
3use crate::error::CacheResult;
4use async_trait::async_trait;
5use std::time::Duration;
6
7/// Cache store trait for different cache backends.
8#[async_trait]
9pub trait CacheStore: Send + Sync {
10 /// Get a JSON value from the cache.
11 ///
12 /// # Arguments
13 ///
14 /// * `key` - The cache key
15 ///
16 /// # Returns
17 ///
18 /// Returns `Ok(Some(value))` if the key exists, `Ok(None)` if not found,
19 /// or an error if the operation fails.
20 async fn get_json(&self, key: &str) -> CacheResult<Option<String>>;
21
22 /// Set a JSON value in the cache.
23 ///
24 /// # Arguments
25 ///
26 /// * `key` - The cache key
27 /// * `value` - The JSON string value
28 /// * `ttl` - Optional time-to-live duration
29 async fn set_json(&self, key: &str, value: String, ttl: Option<Duration>) -> CacheResult<()>;
30
31 /// Delete a key from the cache.
32 ///
33 /// # Arguments
34 ///
35 /// * `key` - The cache key to delete
36 async fn delete(&self, key: &str) -> CacheResult<()>;
37
38 /// Check if a key exists in the cache.
39 ///
40 /// # Arguments
41 ///
42 /// * `key` - The cache key to check
43 async fn exists(&self, key: &str) -> CacheResult<bool>;
44
45 /// Clear all keys from the cache.
46 ///
47 /// **Warning:** This operation may be destructive and affect all keys.
48 async fn clear(&self) -> CacheResult<()>;
49
50 /// Get the TTL (time-to-live) of a key.
51 ///
52 /// # Arguments
53 ///
54 /// * `key` - The cache key
55 ///
56 /// # Returns
57 ///
58 /// Returns `Ok(Some(duration))` if the key has a TTL, `Ok(None)` if the key
59 /// has no expiration or doesn't exist.
60 async fn ttl(&self, key: &str) -> CacheResult<Option<Duration>>;
61
62 /// Set or update the expiration time for a key.
63 ///
64 /// # Arguments
65 ///
66 /// * `key` - The cache key
67 /// * `ttl` - The new time-to-live duration
68 async fn expire(&self, key: &str, ttl: Duration) -> CacheResult<()>;
69
70 /// Increment a numeric value.
71 ///
72 /// # Arguments
73 ///
74 /// * `key` - The cache key
75 /// * `delta` - The amount to increment by
76 ///
77 /// # Returns
78 ///
79 /// Returns the new value after incrementing.
80 async fn increment(&self, key: &str, delta: i64) -> CacheResult<i64>;
81
82 /// Decrement a numeric value.
83 ///
84 /// # Arguments
85 ///
86 /// * `key` - The cache key
87 /// * `delta` - The amount to decrement by
88 ///
89 /// # Returns
90 ///
91 /// Returns the new value after decrementing.
92 async fn decrement(&self, key: &str, delta: i64) -> CacheResult<i64>;
93
94 // ========== Batch Operations (Parallel) ==========
95
96 /// Get multiple keys in parallel.
97 ///
98 /// This operation fetches multiple cache keys concurrently, significantly
99 /// reducing total latency compared to sequential gets.
100 ///
101 /// # Arguments
102 ///
103 /// * `keys` - Slice of cache keys to fetch
104 ///
105 /// # Returns
106 ///
107 /// Returns a vector of `Option<String>` in the same order as the input keys.
108 /// `None` indicates the key was not found.
109 ///
110 /// # Performance
111 ///
112 /// - **Sequential:** O(n * network_latency)
113 /// - **Parallel:** O(max(network_latencies)) ≈ O(network_latency)
114 /// - **Speedup:** 10-100x for network-bound operations
115 ///
116 /// # Examples
117 ///
118 /// ```no_run
119 /// # use armature_cache::*;
120 /// # async fn example(cache: &impl CacheStore) -> CacheResult<()> {
121 /// // Fetch 100 user profiles in parallel
122 /// let keys: Vec<String> = (1..=100).map(|i| format!("user:{}", i)).collect();
123 /// let key_refs: Vec<&str> = keys.iter().map(|s| s.as_str()).collect();
124 /// let profiles = cache.get_many(&key_refs).await?;
125 ///
126 /// // Sequential: ~1000ms (10ms * 100)
127 /// // Parallel: ~15ms (max of all parallel requests)
128 /// # Ok(())
129 /// # }
130 /// ```
131 async fn get_many(&self, keys: &[&str]) -> CacheResult<Vec<Option<String>>> {
132 use futures::future::try_join_all;
133
134 let futures = keys.iter().map(|key| self.get_json(key));
135 try_join_all(futures).await
136 }
137
138 /// Set multiple key-value pairs in parallel.
139 ///
140 /// # Arguments
141 ///
142 /// * `items` - Slice of (key, value) tuples
143 /// * `ttl` - Optional time-to-live for all keys
144 ///
145 /// # Performance
146 ///
147 /// 10-100x faster than sequential sets for network-bound operations.
148 ///
149 /// # Examples
150 ///
151 /// ```no_run
152 /// # use armature_cache::*;
153 /// # async fn example(cache: &impl CacheStore) -> CacheResult<()> {
154 /// use std::time::Duration;
155 ///
156 /// let items = vec![
157 /// ("user:1", r#"{"name":"Alice"}"#.to_string()),
158 /// ("user:2", r#"{"name":"Bob"}"#.to_string()),
159 /// ];
160 ///
161 /// cache.set_many(&items, Some(Duration::from_secs(3600))).await?;
162 /// # Ok(())
163 /// # }
164 /// ```
165 async fn set_many(&self, items: &[(&str, String)], ttl: Option<Duration>) -> CacheResult<()> {
166 use futures::future::try_join_all;
167
168 let futures = items
169 .iter()
170 .map(|(key, value)| self.set_json(key, value.clone(), ttl));
171
172 try_join_all(futures).await?;
173 Ok(())
174 }
175
176 /// Delete multiple keys in parallel.
177 ///
178 /// # Arguments
179 ///
180 /// * `keys` - Slice of cache keys to delete
181 ///
182 /// # Performance
183 ///
184 /// 10-100x faster than sequential deletes.
185 ///
186 /// # Examples
187 ///
188 /// ```no_run
189 /// # use armature_cache::*;
190 /// # async fn example(cache: &impl CacheStore) -> CacheResult<()> {
191 /// // Bulk cache invalidation
192 /// let keys = vec!["session:1", "session:2", "session:3"];
193 /// cache.delete_many(&keys).await?;
194 /// # Ok(())
195 /// # }
196 /// ```
197 async fn delete_many(&self, keys: &[&str]) -> CacheResult<()> {
198 use futures::future::try_join_all;
199
200 let futures = keys.iter().map(|key| self.delete(key));
201 try_join_all(futures).await?;
202 Ok(())
203 }
204
205 /// Check existence of multiple keys in parallel.
206 ///
207 /// # Arguments
208 ///
209 /// * `keys` - Slice of cache keys to check
210 ///
211 /// # Returns
212 ///
213 /// Returns a vector of booleans in the same order as input keys.
214 ///
215 /// # Examples
216 ///
217 /// ```no_run
218 /// # use armature_cache::*;
219 /// # async fn example(cache: &impl CacheStore) -> CacheResult<()> {
220 /// let keys = vec!["user:1", "user:2", "user:3"];
221 /// let exists = cache.exists_many(&keys).await?;
222 ///
223 /// for (key, exists) in keys.iter().zip(exists.iter()) {
224 /// println!("{}: {}", key, exists);
225 /// }
226 /// # Ok(())
227 /// # }
228 /// ```
229 async fn exists_many(&self, keys: &[&str]) -> CacheResult<Vec<bool>> {
230 use futures::future::try_join_all;
231
232 let futures = keys.iter().map(|key| self.exists(key));
233 try_join_all(futures).await
234 }
235
236 /// Get TTL for multiple keys in parallel.
237 ///
238 /// # Arguments
239 ///
240 /// * `keys` - Slice of cache keys
241 ///
242 /// # Returns
243 ///
244 /// Returns a vector of `Option<Duration>` for each key.
245 ///
246 /// # Examples
247 ///
248 /// ```no_run
249 /// # use armature_cache::*;
250 /// # async fn example(cache: &impl CacheStore) -> CacheResult<()> {
251 /// let keys = vec!["session:1", "session:2"];
252 /// let ttls = cache.ttl_many(&keys).await?;
253 ///
254 /// for (key, ttl) in keys.iter().zip(ttls.iter()) {
255 /// match ttl {
256 /// Some(duration) => println!("{}: expires in {:?}", key, duration),
257 /// None => println!("{}: no expiration", key),
258 /// }
259 /// }
260 /// # Ok(())
261 /// # }
262 /// ```
263 async fn ttl_many(&self, keys: &[&str]) -> CacheResult<Vec<Option<Duration>>> {
264 use futures::future::try_join_all;
265
266 let futures = keys.iter().map(|key| self.ttl(key));
267 try_join_all(futures).await
268 }
269}