1 unstable release

0.1.1 May 29, 2021
0.1.0 May 29, 2021

#1283 in Asynchronous

22 downloads per month

MIT license

93KB
1.5K SLoC

hitbox-actix

Build status Coverage Status

Hitbox-Actix is an asynchronous caching framework for Actix actor framework. It's designed for distributed and for single-machine applications.

Features

  • Automatic cache key generation.
  • Multiple cache backend implementations.
  • Stale cache mechanics.
  • Cache locks for dogpile effect preventions.
  • Distributed cache locks.
  • Detailed metrics out of the box.

Backend implementations

  • Redis
  • In-memory backend

Feature flags

  • derive - Support for Cacheable trait derive macros.
  • redis - Support for default redis backend.

Restrictions

Default cache key implementation based on serde_qs crate and have some restrictions.

Documentation

Flow diagrams:

Simple flow

Example

Dependencies:

[dependencies]
hitbox_actix = "0.1"

Code:

First, you should derive Cacheable trait for your actix Message:

use actix::prelude::*;
use actix_derive::{Message, MessageResponse};
use hitbox_actix::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(Message, Cacheable, Serialize)]
#[rtype(result = "Result<Pong, Error>")]
struct Ping {
    id: i32,
}

#[derive(MessageResponse, Deserialize, Serialize, Debug)]
struct Pong(i32);

#[derive(Debug)]
struct Error;

Next step is declare Upstream actor and implement actix Handler for Ping:

#[derive(Debug)]
struct UpstreamActor;

impl Actor for UpstreamActor {
    type Context = Context<Self>;
}

impl Handler<Ping> for UpstreamActor {
    type Result = ResponseFuture<<Ping as Message>::Result>;

    fn handle(&mut self, msg: Ping, _ctx: &mut Self::Context) -> Self::Result {
        println!("Handler::Ping");
        Box::pin(async move {
            actix_rt::time::sleep(core::time::Duration::from_secs(3)).await;
            Ok(Pong(msg.id))
        })
    }
}

The last step is initialize and start CacheActor and UpstreamActor:

use tracing_subscriber::EnvFilter;

#[actix_rt::main]
async fn main() -> Result<(), CacheError> {
    let filter = EnvFilter::new("hitbox=trace");
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::TRACE)
        .with_env_filter(filter)
        .init();

    let backend = RedisBackend::new()
        .await?
        .start();

    let cache = Cache::builder()
        .with_stale()
        .finish(backend)
        .start();
    let upstream = UpstreamActor.start();

    /// And send `Ping` message into cache actor
    let msg = Ping { id: 42 };
    let res = cache.send(msg.into_cache(&upstream)).await??;
    println!("{:#?}", res);
    Ok(())
}

Dependencies

~11MB
~184K SLoC