Skip to main content

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}