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