accelerator 0.1.1

MVP multi-level cache runtime with singleflight load de-duplication
Documentation
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Duration;

use accelerator::builder::LevelCacheBuilder;
use accelerator::config::CacheMode;
use accelerator::local;
use accelerator::macros::{cache_evict, cache_put, cacheable};
use accelerator::{CacheError, CacheResult, LevelCache};

#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
struct User {
    id: u64,
    name: String,
}

#[derive(Clone, Default)]
struct UserRepo {
    table: Arc<Mutex<HashMap<u64, User>>>,
}

impl UserRepo {
    async fn find_by_id(&self, user_id: u64) -> CacheResult<Option<User>> {
        Ok(self.table.lock().unwrap().get(&user_id).cloned())
    }

    async fn upsert(&self, user: User) -> CacheResult<()> {
        self.table.lock().unwrap().insert(user.id, user);
        Ok(())
    }

    async fn delete(&self, user_id: u64) -> CacheResult<()> {
        self.table.lock().unwrap().remove(&user_id);
        Ok(())
    }
}

#[derive(Debug, thiserror::Error)]
enum ServiceError {
    #[error(transparent)]
    Cache(#[from] CacheError),
}

type ServiceResult<T> = Result<T, ServiceError>;

struct UserService {
    cache: LevelCache<u64, User>,
    repo: UserRepo,
}

impl UserService {
    fn new(cache: LevelCache<u64, User>, repo: UserRepo) -> Self {
        Self { cache, repo }
    }

    #[cacheable(
        cache = self.cache,
        key = user_id,
        allow_stale = false,
        cache_none = true,
        on_cache_error = "ignore"
    )]
    async fn get_user(&self, user_id: u64) -> CacheResult<Option<User>> {
        self.repo.find_by_id(user_id).await
    }

    #[cache_put(
        cache = self.cache,
        key = user.id,
        value = Some(user.clone()),
        on_cache_error = "propagate"
    )]
    async fn save_user(&self, user: User) -> ServiceResult<()> {
        self.repo.upsert(user.clone()).await?;
        Ok(())
    }

    #[cache_evict(
        cache = self.cache,
        key = user_id,
        before = true,
        on_cache_error = "propagate"
    )]
    async fn delete_user(&self, user_id: u64) -> ServiceResult<()> {
        self.repo.delete(user_id).await?;
        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let local_backend = local::moka::<User>().max_capacity(10_000).build()?;
    let cache = LevelCacheBuilder::<u64, User>::new()
        .area("macro-demo")
        .mode(CacheMode::Local)
        .local(local_backend)
        .local_ttl(Duration::from_secs(120))
        .null_ttl(Duration::from_secs(15))
        .build()?;

    let repo = UserRepo::default();
    let service = UserService::new(cache, repo);

    service
        .save_user(User {
            id: 1001,
            name: "alice".to_string(),
        })
        .await?;

    let first = service.get_user(1001).await?;
    let second = service.get_user(1001).await?;
    println!("first={first:?}, second={second:?}");

    service.delete_user(1001).await?;
    let after_delete = service.get_user(1001).await?;
    println!("after_delete={after_delete:?}");

    Ok(())
}