use std::sync::Arc;
use std::time::Duration;
use amalgam::{Cache, CacheRegistry, DefaultEntryOptionsProvider, EntryOptions};
struct UserService {
cache: Cache<String>,
}
impl UserService {
fn new(cache: Cache<String>) -> Self {
Self { cache }
}
async fn display_name(&self, user_id: &str) -> String {
let key = format!("user:{user_id}:name");
self.cache
.get_or_set(key, {
let user_id = user_id.to_owned();
move |ctx| async move { Ok(ctx.value(format!("User #{user_id}"))) }
})
.await
.unwrap_or_else(|_| "<unavailable>".to_owned())
}
}
struct KeyAwareOptionsProvider;
impl DefaultEntryOptionsProvider for KeyAwareOptionsProvider {
fn options_for(&self, key: &str) -> Option<EntryOptions> {
if key.starts_with("session:") {
Some(EntryOptions::new(Duration::from_secs(30)))
} else if key.starts_with("config:") {
Some(EntryOptions::new(Duration::from_secs(3600)))
} else {
None }
}
}
#[tokio::main]
async fn main() {
println!("== Pattern 1: one shared cache, injected into a service and cloned into tasks ==");
let cache: Cache<String> = Cache::builder().build();
let service = Arc::new(UserService::new(cache.clone()));
let name = service.display_name("42").await;
println!("service.display_name(\"42\") -> {name}");
let mut handles = Vec::with_capacity(3);
for task_id in 0..3 {
let service = Arc::clone(&service);
handles.push(tokio::spawn(async move {
let seen = service.display_name("42").await;
println!(" task {task_id} read the SAME shared cache -> {seen}");
seen
}));
}
for handle in handles {
let _ = handle.await;
}
cache
.set(
"user:42:name".to_owned(),
"Renamed via another handle".to_owned(),
)
.await;
let after = service.display_name("42").await;
println!("after writing via a separate clone, service sees -> {after}");
assert_eq!(
after, "Renamed via another handle",
"all clones share one instance"
);
println!("OK: every clone observed the same instance.\n");
println!("== Pattern 2: a registry of independent NAMED caches + options provider ==");
let registry: Arc<CacheRegistry<String>> = Arc::new(CacheRegistry::new());
let options_provider: Arc<dyn DefaultEntryOptionsProvider> = Arc::new(KeyAwareOptionsProvider);
registry.register(
"sessions",
Cache::builder()
.default_options_provider(Arc::clone(&options_provider))
.build(),
);
registry.register(
"config",
Cache::builder()
.default_options_provider(Arc::clone(&options_provider))
.build(),
);
println!(
"registered {} named caches: \"sessions\", \"config\"",
registry.len()
);
let sessions = registry.get("sessions").expect("sessions cache registered");
let config = registry.get("config").expect("config cache registered");
let token = sessions
.get_or_set("session:abc", |ctx| async move {
Ok(ctx.value("session-token-for-abc".to_owned()))
})
.await
.expect("factory is infallible");
println!("sessions[\"session:abc\"] -> {token}");
let config_view = config.try_get("session:abc", None).await;
println!(
"config[\"session:abc\"] present? {} (independent storage)",
config_view.has_value()
);
assert!(
!config_view.has_value(),
"named caches must not share storage"
);
let setting = config
.get_or_set("config:feature_x", |ctx| async move {
Ok(ctx.value("enabled".to_owned()))
})
.await
.expect("factory is infallible");
println!("config[\"config:feature_x\"] -> {setting} (1 h freshness via options provider)");
println!("OK: named caches are independent; the options provider drove per-key defaults.");
}