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