multi_tier_cache/builder.rs
1//! Cache System Builder
2//!
3//! Provides a flexible builder pattern for constructing `CacheSystem` with custom backends.
4//!
5//! # Example: Using Default Backends
6//!
7//! ```rust,no_run
8//! use multi_tier_cache::CacheSystemBuilder;
9//!
10//! #[tokio::main]
11//! async fn main() -> anyhow::Result<()> {
12//! let cache = CacheSystemBuilder::new()
13//! .build()
14//! .await?;
15//! Ok(())
16//! }
17//! ```
18//!
19//! # Example: Custom L1 Backend
20//!
21//! ```rust,ignore
22//! use multi_tier_cache::{CacheSystemBuilder, CacheBackend};
23//! use std::sync::Arc;
24//!
25//! let custom_l1 = Arc::new(MyCustomL1Cache::new());
26//!
27//! let cache = CacheSystemBuilder::new()
28//! .with_l1(custom_l1)
29//! .build()
30//! .await?;
31//! ```
32
33use crate::traits::{CacheBackend, L2CacheBackend, StreamingBackend};
34use crate::{CacheManager, CacheSystem, CacheTier, L1Cache, L2Cache, TierConfig};
35use anyhow::Result;
36use std::sync::Arc;
37use tracing::info;
38
39/// Builder for constructing `CacheSystem` with custom backends
40///
41/// This builder allows you to configure custom L1 (in-memory) and L2 (distributed)
42/// cache backends, enabling you to swap Moka and Redis with alternative implementations.
43///
44/// # Multi-Tier Support (v0.5.0+)
45///
46/// The builder now supports dynamic multi-tier architectures (L1+L2+L3+L4+...).
47/// Use `.with_tier()` to add custom tiers, or `.with_l3()` / `.with_l4()` for convenience.
48///
49/// # Default Behavior
50///
51/// If no custom backends are provided, the builder uses:
52/// - **L1**: Moka in-memory cache
53/// - **L2**: Redis distributed cache
54///
55/// # Type Safety
56///
57/// The builder accepts any type that implements the required traits:
58/// - L1 backends must implement `CacheBackend`
59/// - L2 backends must implement `L2CacheBackend` (extends `CacheBackend`)
60/// - All tier backends must implement `L2CacheBackend` (for TTL support)
61/// - Streaming backends must implement `StreamingBackend`
62///
63/// # Example - Default 2-Tier
64///
65/// ```rust,no_run
66/// use multi_tier_cache::CacheSystemBuilder;
67///
68/// #[tokio::main]
69/// async fn main() -> anyhow::Result<()> {
70/// // Use default backends (Moka + Redis)
71/// let cache = CacheSystemBuilder::new()
72/// .build()
73/// .await?;
74///
75/// Ok(())
76/// }
77/// ```
78///
79/// # Example - Custom 3-Tier (v0.5.0+)
80///
81/// ```rust,ignore
82/// use multi_tier_cache::{CacheSystemBuilder, TierConfig};
83/// use std::sync::Arc;
84///
85/// let l1 = Arc::new(L1Cache::new().await?);
86/// let l2 = Arc::new(L2Cache::new().await?);
87/// let l3 = Arc::new(RocksDBCache::new("/tmp/cache").await?);
88///
89/// let cache = CacheSystemBuilder::new()
90/// .with_tier(l1, TierConfig::as_l1())
91/// .with_tier(l2, TierConfig::as_l2())
92/// .with_l3(l3) // Convenience method
93/// .build()
94/// .await?;
95/// ```
96pub struct CacheSystemBuilder {
97 // Legacy 2-tier configuration (v0.1.0 - v0.4.x)
98 l1_backend: Option<Arc<dyn CacheBackend>>,
99 l2_backend: Option<Arc<dyn L2CacheBackend>>,
100 streaming_backend: Option<Arc<dyn StreamingBackend>>,
101
102 // Multi-tier configuration (v0.5.0+)
103 tiers: Vec<(Arc<dyn L2CacheBackend>, TierConfig)>,
104}
105
106impl CacheSystemBuilder {
107 /// Create a new builder with no custom backends configured
108 ///
109 /// By default, calling `.build()` will use Moka (L1) and Redis (L2).
110 /// Use `.with_tier()` to configure multi-tier architecture (v0.5.0+).
111 #[must_use]
112 pub fn new() -> Self {
113 Self {
114 l1_backend: None,
115 l2_backend: None,
116 streaming_backend: None,
117 tiers: Vec::new(),
118 }
119 }
120
121 /// Configure a custom L1 (in-memory) cache backend
122 ///
123 /// # Arguments
124 ///
125 /// * `backend` - Any type implementing `CacheBackend` trait
126 ///
127 /// # Example
128 ///
129 /// ```rust,ignore
130 /// use std::sync::Arc;
131 /// use multi_tier_cache::CacheSystemBuilder;
132 ///
133 /// let custom_l1 = Arc::new(MyCustomL1::new());
134 ///
135 /// let cache = CacheSystemBuilder::new()
136 /// .with_l1(custom_l1)
137 /// .build()
138 /// .await?;
139 /// ```
140 #[must_use]
141 pub fn with_l1(mut self, backend: Arc<dyn CacheBackend>) -> Self {
142 self.l1_backend = Some(backend);
143 self
144 }
145
146 /// Configure a custom L2 (distributed) cache backend
147 ///
148 /// # Arguments
149 ///
150 /// * `backend` - Any type implementing `L2CacheBackend` trait
151 ///
152 /// # Example
153 ///
154 /// ```rust,ignore
155 /// use std::sync::Arc;
156 /// use multi_tier_cache::CacheSystemBuilder;
157 ///
158 /// let custom_l2 = Arc::new(MyMemcachedBackend::new());
159 ///
160 /// let cache = CacheSystemBuilder::new()
161 /// .with_l2(custom_l2)
162 /// .build()
163 /// .await?;
164 /// ```
165 #[must_use]
166 pub fn with_l2(mut self, backend: Arc<dyn L2CacheBackend>) -> Self {
167 self.l2_backend = Some(backend);
168 self
169 }
170
171 /// Configure a custom streaming backend
172 ///
173 /// This is optional. If not provided, streaming functionality will use
174 /// the L2 backend if it implements `StreamingBackend`.
175 ///
176 /// # Arguments
177 ///
178 /// * `backend` - Any type implementing `StreamingBackend` trait
179 ///
180 /// # Example
181 ///
182 /// ```rust,ignore
183 /// use std::sync::Arc;
184 /// use multi_tier_cache::CacheSystemBuilder;
185 ///
186 /// let kafka_backend = Arc::new(MyKafkaBackend::new());
187 ///
188 /// let cache = CacheSystemBuilder::new()
189 /// .with_streams(kafka_backend)
190 /// .build()
191 /// .await?;
192 /// ```
193 #[must_use]
194 pub fn with_streams(mut self, backend: Arc<dyn StreamingBackend>) -> Self {
195 self.streaming_backend = Some(backend);
196 self
197 }
198
199 /// Configure a cache tier with custom settings (v0.5.0+)
200 ///
201 /// Add a cache tier to the multi-tier architecture. Tiers will be sorted
202 /// by `tier_level` during build.
203 ///
204 /// # Arguments
205 ///
206 /// * `backend` - Any type implementing `L2CacheBackend` trait
207 /// * `config` - Tier configuration (level, promotion, TTL scale)
208 ///
209 /// # Example
210 ///
211 /// ```rust,ignore
212 /// use multi_tier_cache::{CacheSystemBuilder, TierConfig, L1Cache, L2Cache};
213 /// use std::sync::Arc;
214 ///
215 /// let l1 = Arc::new(L1Cache::new().await?);
216 /// let l2 = Arc::new(L2Cache::new().await?);
217 /// let l3 = Arc::new(RocksDBCache::new("/tmp").await?);
218 ///
219 /// let cache = CacheSystemBuilder::new()
220 /// .with_tier(l1, TierConfig::as_l1())
221 /// .with_tier(l2, TierConfig::as_l2())
222 /// .with_tier(l3, TierConfig::as_l3())
223 /// .build()
224 /// .await?;
225 /// ```
226 #[must_use]
227 pub fn with_tier(mut self, backend: Arc<dyn L2CacheBackend>, config: TierConfig) -> Self {
228 self.tiers.push((backend, config));
229 self
230 }
231
232 /// Convenience method to add L3 cache tier (v0.5.0+)
233 ///
234 /// Adds a cold storage tier with 2x TTL multiplier.
235 ///
236 /// # Arguments
237 ///
238 /// * `backend` - L3 backend (e.g., `RocksDB`, `LevelDB`)
239 ///
240 /// # Example
241 ///
242 /// ```rust,ignore
243 /// use std::sync::Arc;
244 ///
245 /// let rocksdb = Arc::new(RocksDBCache::new("/tmp/l3cache").await?);
246 ///
247 /// let cache = CacheSystemBuilder::new()
248 /// .with_l3(rocksdb)
249 /// .build()
250 /// .await?;
251 /// ```
252 #[must_use]
253 pub fn with_l3(mut self, backend: Arc<dyn L2CacheBackend>) -> Self {
254 self.tiers.push((backend, TierConfig::as_l3()));
255 self
256 }
257
258 /// Convenience method to add L4 cache tier (v0.5.0+)
259 ///
260 /// Adds an archive storage tier with 8x TTL multiplier.
261 ///
262 /// # Arguments
263 ///
264 /// * `backend` - L4 backend (e.g., S3, file system)
265 ///
266 /// # Example
267 ///
268 /// ```rust,ignore
269 /// use std::sync::Arc;
270 ///
271 /// let s3_cache = Arc::new(S3Cache::new("my-bucket").await?);
272 ///
273 /// let cache = CacheSystemBuilder::new()
274 /// .with_l4(s3_cache)
275 /// .build()
276 /// .await?;
277 /// ```
278 #[must_use]
279 pub fn with_l4(mut self, backend: Arc<dyn L2CacheBackend>) -> Self {
280 self.tiers.push((backend, TierConfig::as_l4()));
281 self
282 }
283
284 /// Build the `CacheSystem` with configured or default backends
285 ///
286 /// If no custom backends were provided via `.with_l1()` or `.with_l2()`,
287 /// this method creates default backends (Moka for L1, Redis for L2).
288 ///
289 /// # Multi-Tier Mode (v0.5.0+)
290 ///
291 /// If tiers were configured via `.with_tier()`, `.with_l3()`, or `.with_l4()`,
292 /// the builder creates a multi-tier `CacheManager` using `new_with_tiers()`.
293 ///
294 /// # Returns
295 ///
296 /// * `Ok(CacheSystem)` - Successfully constructed cache system
297 /// * `Err(e)` - Failed to initialize backends (e.g., Redis connection error)
298 ///
299 /// # Example - Default 2-Tier
300 ///
301 /// ```rust,no_run
302 /// use multi_tier_cache::CacheSystemBuilder;
303 ///
304 /// #[tokio::main]
305 /// async fn main() -> anyhow::Result<()> {
306 /// let cache = CacheSystemBuilder::new()
307 /// .build()
308 /// .await?;
309 ///
310 /// // Use cache_manager for operations
311 /// let manager = cache.cache_manager();
312 ///
313 /// Ok(())
314 /// }
315 /// ```
316 /// # Errors
317 ///
318 /// Returns an error if the default backends cannot be initialized.
319 pub async fn build(self) -> Result<CacheSystem> {
320 info!("Building Multi-Tier Cache System");
321
322 // NEW: Multi-tier mode (v0.5.0+)
323 if !self.tiers.is_empty() {
324 info!(
325 tier_count = self.tiers.len(),
326 "Initializing multi-tier architecture"
327 );
328
329 // Sort tiers by tier_level (ascending: L1 first, L4 last)
330 let mut tiers = self.tiers;
331 tiers.sort_by_key(|(_, config)| config.tier_level);
332
333 // Convert to CacheTier instances
334 let cache_tiers: Vec<CacheTier> = tiers
335 .into_iter()
336 .map(|(backend, config)| {
337 CacheTier::new(
338 backend,
339 config.tier_level,
340 config.promotion_enabled,
341 config.ttl_scale,
342 )
343 })
344 .collect();
345
346 // Create cache manager with multi-tier support
347 let cache_manager = Arc::new(CacheManager::new_with_tiers(
348 cache_tiers,
349 self.streaming_backend,
350 )?);
351
352 info!("Multi-Tier Cache System built successfully");
353 info!("Note: Using multi-tier mode - use cache_manager() for all operations");
354
355 return Ok(CacheSystem {
356 cache_manager,
357 l1_cache: None, // Multi-tier mode doesn't use concrete types
358 l2_cache: None,
359 });
360 }
361
362 // LEGACY: 2-tier mode (v0.1.0 - v0.4.x)
363 // Handle default vs custom backends
364 if self.l1_backend.is_none() && self.l2_backend.is_none() {
365 // Default path: Create concrete types once and reuse them
366 info!("Initializing default backends (Moka + Redis)");
367
368 let l1_cache = Arc::new(L1Cache::new()?);
369 let l2_cache = Arc::new(L2Cache::new().await?);
370
371 // Use legacy constructor that handles conversion to trait objects
372 let cache_manager =
373 Arc::new(CacheManager::new(l1_cache.clone(), l2_cache.clone()).await?);
374
375 info!("Multi-Tier Cache System built successfully");
376
377 Ok(CacheSystem {
378 cache_manager,
379 l1_cache: Some(l1_cache),
380 l2_cache: Some(l2_cache),
381 })
382 } else {
383 // Custom backend path
384 info!("Building with custom backends");
385
386 let l1_backend: Arc<dyn CacheBackend> = if let Some(backend) = self.l1_backend {
387 info!(backend = %backend.name(), "Using custom L1 backend");
388 backend
389 } else {
390 info!("Using default L1 backend (Moka)");
391 Arc::new(L1Cache::new()?)
392 };
393
394 let l2_backend: Arc<dyn L2CacheBackend> = if let Some(backend) = self.l2_backend {
395 info!(backend = %backend.name(), "Using custom L2 backend");
396 backend
397 } else {
398 info!("Using default L2 backend (Redis)");
399 Arc::new(L2Cache::new().await?)
400 };
401
402 let streaming_backend = self.streaming_backend;
403
404 // Create cache manager with trait objects
405 let cache_manager = Arc::new(CacheManager::new_with_backends(
406 l1_backend,
407 l2_backend,
408 streaming_backend,
409 )?);
410
411 info!("Multi-Tier Cache System built with custom backends");
412 info!("Note: Using custom backends - use cache_manager() for all operations");
413
414 Ok(CacheSystem {
415 cache_manager,
416 l1_cache: None, // Custom backends mode doesn't use concrete types
417 l2_cache: None,
418 })
419 }
420 }
421}
422
423impl Default for CacheSystemBuilder {
424 fn default() -> Self {
425 Self::new()
426 }
427}