Supporting a Backend Cache Manager
This section is intended for those looking to implement a custom backend cache manager, or understand how the CacheManager
trait works.
The CacheManager
trait
The CacheManager
trait is the main trait that needs to be implemented to support a new backend cache manager. It has three methods that it requires:
get
: retrieve a cached response given the provided cache keyput
: store a response and related policy object in the cache associated with the provided cache keydelete
: remove a cached response from the cache associated with the provided cache key
Because the methods are asynchronous, they currently require async_trait
to be derived. This may change in the future.
The get
method
The get
method is used to retrieve a cached response given the provided cache key. It returns an Result<Option<(HttpResponse, CachePolicy)>, BoxError>
where HttpResponse
is the cached response and CachePolicy
is the associated cache policy object that provides us helpful metadata. If the cache key does not exist in the cache, Ok(None)
is returned.
The put
method
The put
method is used to store a response and related policy object in the cache associated with the provided cache key. It returns an Result<HttpResponse, BoxError>
where HttpResponse
is the passed response.
The delete
method
The delete
method is used to remove a cached response from the cache associated with the provided cache key. It returns an Result<(), BoxError>
.
How to implement a custom backend cache manager
This guide will use the cacache
backend cache manager as an example. The full source can be found here. There are several ways to accomplish this, so feel free to experiment!
Part One: The base structs
The first step is to create a struct that will hold the cache manager's configuration or potentially the cache itself. This struct will implement the CacheManager
trait. In this case, we'll call it CACacheManager
and it will have a field to store the path for the cache directory.
#![allow(unused)] fn main() { #[derive(Debug, Clone)] pub struct CACacheManager { /// Directory where the cache will be stored. pub path: PathBuf, } }
Next we will create a struct to store the response and accompanying policy object. This struct will be used to store the response and policy object in the cache. We'll call it Store
. This isn't strictly necessary, but I find this easier to work with.
#![allow(unused)] fn main() { #[derive(Debug, Deserialize, Serialize)] struct Store { response: HttpResponse, policy: CachePolicy, } }
This struct will also derive serde Deserialize and Serialize to ease the serialization and deserialization with bincode.
Part Two: Implementing the CacheManager
trait
Now that we have our base structs, we can implement the CacheManager
trait for our CACacheManager
struct. We'll start with the get
method, but first we must make sure we derive async_trait.
#![allow(unused)] fn main() { #[async_trait::async_trait] impl CacheManager for CACacheManager { ... }
The get
method accepts a &str
as the cache key and returns an Result<Option<(HttpResponse, CachePolicy)>, BoxError>
. We will read
function from cacache
to lookup the cache key in the cache directory. If the cache key does not exist, we'll return Ok(None)
. The object we will be serializing and deserializing is our Store
struct.
#![allow(unused)] fn main() { ... async fn get( &self, cache_key: &str, ) -> Result<Option<(HttpResponse, CachePolicy)>> { let store: Store = match cacache::read(&self.path, cache_key).await { Ok(d) => bincode::deserialize(&d)?, Err(_e) => { return Ok(None); } }; Ok(Some((store.response, store.policy))) } ... }
Next we'll implement the put
method. This method accepts a String
as the cache key, a HttpResponse
as the response, and a CachePolicy
as the policy object. It returns an Result<HttpResponse, BoxError>
. We will clone the response during our construction of the Store
struct, then serialize the Store
struct using serialize and write it to the cache directory using write
from cacache
.
#![allow(unused)] fn main() { ... async fn put( &self, cache_key: String, response: HttpResponse, policy: CachePolicy, ) -> Result<HttpResponse> { let data = Store { response: response.clone(), policy }; let bytes = bincode::serialize(&data)?; cacache::write(&self.path, cache_key, bytes).await?; Ok(response) } ... }
Finally we'll implement the delete
method. This method accepts a &str
as the cache key and returns an Result<(), BoxError>
. We will use remove
from cacache
to remove the object from the cache directory.
#![allow(unused)] fn main() { ... async fn delete(&self, cache_key: &str) -> Result<()> { Ok(cacache::remove(&self.path, cache_key).await?) } ... }
Our CACacheManager
struct now meets the requirements of the CacheManager
trait and is ready for use!