CacheDecorator behaviour (cache_decorator v0.2.0)
View SourceProvides a caching decorator mechanism for Elixir functions.
This module allows you to easily add caching behavior to functions by using
the @cache and @invalidate module attributes. It decorates functions to
automatically cache their results and invalidate cache entries.
Usage
Use CacheDecorator in your module and specify a :cache_module that
implements the caching backend behaviour.
use CacheDecorator, cache_module: YourCacheModuleThen decorate your functions with:
@cache key: "cache_key_template", on: <pattern or list_of_patterns>- Caches the result of the function under the given key.
- The
:keycan contain placeholders like{arg_name}that will be replaced by the string representation of the corresponding function argument. - The
:onoption allows specifying one or multiple patterns to match against the result of the function call; result will be cached only if it matches one of these patterns. - If
:onis omitted, cache happens for any function call result. - You can provide additional options like
ttlwhich will be passed tocache_module.put/4asopts
@invalidate key: "cache_key_template", on: <pattern or list_of_patterns>- Invalidates the cache entry for the given key.
- The
:keycan contain placeholders like{arg_name}that will be replaced by the string representation of the corresponding function argument. - The
:onoption allows specifying one or multiple patterns to match against the result of the function call; cache invalidation only occurs if the result matches one of these patterns. - If
:onis omitted, cache invalidation happens after every call.
Behaviour callbacks you need to implement in your cache module
Your cache module (provided via :cache_module option) must implement the
following callbacks:
@callback get(decorator_opts :: Keyword.t(), key :: String.t()) ::
{:ok, nil} | {:ok, term()} | :error
@callback put(decorator_opts :: Keyword.t(), key :: String.t(), value :: term(), opts :: Keyword.t()) ::
:ok
@callback del(decorator_opts :: Keyword.t(), key :: String.t()) ::
:okHere:
decorator_optsare the options passed touse CacheDecoratorin your module, such as the cache module and any other opts.In the
put/4callback, theoptsargument is the keyword list of options provided in the@cachedecorator, for example thettloption.
This allows plugging any caching backend you want, by implementing these functions.
Example
Cache provider module implementing CacheDecorator behaviour (we use :cachex as example,
but it can be any cache implementation):
defmodule MyCache do
@behaviour CacheDecorator
def get(_decorator_opts, key) do
case Cachex.get(:default, key) do
{:ok, value} ->
{:ok, value}
{:error, _reason} ->
:error
end
end
def put(_decorator_opts, key, value, opts) do
expire = Keyword.get(opts, :ttl)
_ = Cachex.put(:default, key, value, expire: expire)
:ok
end
def del(_decorator_opts, key) do
_ = Cachex.del(:default, key)
:ok
end
endModule using cache decorators:
defmodule MyModule do
use CacheDecorator, cache_module: MyCache
@cache key: "cache_key_{user_id}"
def get_data(user_id) do
Storage.get_by_user_id(user_id)
end
@invalidate key: "cache_key_{user_id}", on: :ok
def update_data(%{user_id: user_id} = params) do
Storage.update(user_id, params)
end
endCache keys can reference function argument names wrapped in {} that will
be dynamically interpolated from the actual arguments on call.
If the cache is unavailable (e.g., backend error), the original function will be called without caching as fallback.
This decorator uses Elixir's @on_definition and @before_compile
hooks, and generates override functions that implement caching and
invalidation transparently.
Examples of decorator translation
Here are examples showing how functions decorated with caching decorators would look like without using the decorators.
Using @cache
Decorated function:
use CacheDecorator, cache_module: MyCache
@cache key: "prefix_{arg}"
def get(arg), do: expensive_computation(arg)Equivalent function without decorator:
def get(arg) do
key = "prefix_#{arg}"
case MyCache.get([], key) do
{:ok, nil} ->
value = expensive_computation(arg)
:ok = MyCache.put([], key, value, key: "prefix_{arg}")
value
{:ok, value} ->
value
:error ->
expensive_computation(arg)
end
endUsing @invalidate
Decorated function:
use CacheDecorator, cache_module: MyCache
@invalidate key: "prefix_{arg}", on: :ok
def update(arg), do: do_something(arg)Equivalent function without decorator:
def update(arg) do
result = do_something(arg)
case result do
:ok ->
:ok = MyCache.del([], "prefix_#{arg}")
result
_ ->
result
end
endIf the :on option is omitted, invalidation occurs unconditionally:
@invalidate key: "prefix_{arg}"
def update(arg), do: do_something(arg)Equivalent without decorator:
def update(arg) do
result = do_something(arg)
:ok = MyCache.del([], "prefix_#{arg}")
result
end
Summary
Types
Callbacks
@callback del(decorator_opts(), key()) :: :ok
@callback get(decorator_opts(), key()) :: {:ok, nil} | {:ok, value()} | :error
@callback put(decorator_opts(), key(), value(), opts()) :: :ok