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}