hitbox_backend/composition/policy/read/mod.rs
1//! Read policies for controlling read operations across cache layers.
2//!
3//! This module defines the CompositionReadPolicy trait and its implementations.
4//! Different strategies (sequential, race, parallel) can be used to optimize
5//! read performance based on application requirements.
6
7use async_trait::async_trait;
8use hitbox_core::{BoxContext, CacheKey, CacheValue, Offload};
9use std::future::Future;
10
11use crate::composition::CompositionLayer;
12
13pub mod parallel;
14pub mod race;
15pub mod sequential;
16
17pub use parallel::ParallelReadPolicy;
18pub use race::{RaceLoserPolicy, RaceReadPolicy};
19pub use sequential::SequentialReadPolicy;
20
21/// Result of a read operation including the value, source layer, and context.
22pub struct ReadResult<T> {
23 /// The cached value if found.
24 pub value: Option<CacheValue<T>>,
25 /// Which layer provided the data.
26 pub source: CompositionLayer,
27 /// The context from the layer that provided the data (for merging).
28 pub context: BoxContext,
29}
30
31/// Policy trait for controlling read operations across composition cache layers.
32///
33/// This trait encapsulates the **control flow strategy** (sequential, race, parallel)
34/// for reading from multiple cache layers (L1/L2), while delegating the actual read
35/// operations to provided closures. This design allows the same policy to be used at
36/// both the `CacheBackend` level (typed data) and `Backend` level (raw bytes).
37///
38/// # Type Parameters
39///
40/// The policy is generic over:
41/// * `T` - The return type (e.g., `CacheValue<User>` or `CacheValue<Raw>`)
42/// * `E` - The error type (e.g., `BackendError`)
43/// * `F1, F2` - Closures for reading from L1 and L2
44/// * `O` - The offload type for background task execution
45///
46/// # Example
47///
48/// ```ignore
49/// use hitbox_backend::composition::policy::CompositionReadPolicy;
50///
51/// let policy = SequentialReadPolicy::default();
52///
53/// // Use with CacheBackend level (key is cloned for each closure)
54/// let result = policy.execute_with(
55/// key.clone(),
56/// |k| async move { (l1.get::<User>(&k, &mut ctx).await, ctx) },
57/// |k| async move {
58/// let value = l2.get::<User>(&k, &mut ctx).await?;
59/// // Populate L1 on L2 hit
60/// if let Some(ref v) = value {
61/// l1.set::<User>(&k, v, v.ttl(), &mut ctx.clone_box()).await.ok();
62/// }
63/// Ok((value, ctx))
64/// },
65/// &offload,
66/// ).await?;
67/// ```
68#[async_trait]
69pub trait CompositionReadPolicy: Send + Sync {
70 /// Execute a read operation with custom read closures for each layer.
71 ///
72 /// The policy determines the control flow (when and how to call the closures),
73 /// while the closures handle the actual reading and any post-processing
74 /// (like L1 population or envelope wrapping).
75 ///
76 /// # Arguments
77 /// * `key` - The cache key to look up
78 /// * `read_l1` - Closure that reads from L1, returns (result, context)
79 /// * `read_l2` - Closure that reads from L2, returns (result, context)
80 /// * `offload` - Offload manager for spawning background tasks (e.g., losing race futures)
81 ///
82 /// # Returns
83 /// A `ReadResult` containing the value, source layer, and context from the layer
84 /// that provided the data. The context can be used for merging with outer context.
85 async fn execute_with<T, E, F1, F2, Fut1, Fut2, O>(
86 &self,
87 key: CacheKey,
88 read_l1: F1,
89 read_l2: F2,
90 offload: &O,
91 ) -> Result<ReadResult<T>, E>
92 where
93 T: Send + 'static,
94 E: Send + std::fmt::Debug + 'static,
95 F1: FnOnce(CacheKey) -> Fut1 + Send,
96 F2: FnOnce(CacheKey) -> Fut2 + Send,
97 Fut1: Future<Output = (Result<Option<CacheValue<T>>, E>, BoxContext)> + Send + 'static,
98 Fut2: Future<Output = (Result<Option<CacheValue<T>>, E>, BoxContext)> + Send + 'static,
99 O: Offload<'static>;
100}