Upgrading to v3.0
View SourceFor the v3, Nebulex introduces several breaking changes, including the Cache API itself. This guide aims to highlight most of these changes to make easier the transition to v3. Be aware this guide won't focus on every change, just the most significant ones that can affect how your application code interacts with the cache. Also, it is not a detailed guide about how to translate the current code from older versions to v3, just pointing out the areas the new documentation should be consulted on.
Built-In Adapters
All previously built-in adapters (Nebulex.Adapters.Local
,
Nebulex.Adapters.Partitioned
, Nebulex.Adapters.Replicated
, and
Nebulex.Adapters.Multilevel
) have been moved to separate repositories.
Therefore, you must add the adapter dependency to the list of dependencies
in your mix.exs
file.
For example, if you are using the local adapter:
defp deps do
[
{:nebulex, "~> 3.0"},
+ {:nebulex_local, "~> 3.0"},
...
]
end
Update Cache API calls
The most significant change is on the Cache API. Nebulex v3 has a new API based on ok/error tuples.
Nebulex v3 brings a new API with two flavors:
- An ok/error tuple API for all cache functions. This new approach is preferred when you want to handle different outcomes using pattern-matching.
- An alternative API version with trailing bang (
!
) functions. This approach is preferred if you expect the outcome to always be successful.
Therefore, there are two ways to address the API changes. The first is to review
your code's cache calls and handle the new ok/error tuple response. The second
is to replace your cache calls using the function version with the trailing bang
(!
). But let's take a closer look at them.
Using ok/error tuple API
As mentioned above, all the Cache API callbacks can return :ok
or
{:ok, result}
on success, or {:error, reason}
otherwise. Hence, you have
three options:
- Ignore the result. However, there will be some cases where you don't want (or can't) ignore the result. For example, when fetching a key, it is intended to use its value, hence, it doesn't make sense to ignore the result.
- Pattern-match the success case. If you decide to take this path, perhaps
you should consider using the trailing bang (
!
) functions mentioned in the next section. - Handle the success and error cases explicitly (the preferred option).
If you go for the first option of ignoring the result, you can replace your cache calls like this::
- :ok = MyApp.Cache.put("key", "value")
+ _ignore = MyApp.Cache.put("key", "value")
On the other hand, if you go for option two to pattern-match the success case:
- value = MyApp.Cache.get("key")
+ {:ok, value} = MyApp.Cache.get("key")
Finally, if you go for the third option, update the cache calls to handle the success and error cases:
- :ok = MyApp.Cache.put("key", "value")
- value = MyApp.Cache.get("key")
New code may look like this:
case MyApp.Cache.put("key", "value") do
:ok ->
#=> your logic handling success
{:error, reason} ->
#=> your logic handling the error
end
case MyApp.Cache.fetch("key") do
{:ok, value} ->
#=> your logic handling success
{:error, %Nebulex.KeyError{}} ->
#=> your logic handling when the key is not found
{:error, reason} ->
#=> your logic handling other errors
end
Notice that the fetch
function was introduced since v3. However, the get
function is still available, but it has slightly different semantics.
See the Cache API docs for more information.
Migrating to ok/error tuple API
You can apply the same idea in the examples above to migrate ALL the Cache API calls.
Using API version with trailing bang (!
) functions
The second way to address the API changes (and perhaps the easiest one) is to
replace your cache calls by using the function version with the trailing bang
(!
). For example:
- :ok = MyApp.Cache.put("key", "value")
+ :ok = MyApp.Cache.put!("key", "value")
Despite this fix of using bang functions (!
) may work for most cases, there
may be a few where the outcome is not the same or the patch is just not
applicable. See the next sections to learn more about it.
Update get
calls
The previous callback get/2
has changed the semantics a bit (aside from the
ok/error tuple API). Previously, returned nil
when the given key wasn't in
the cache. Now, the callback accepts an argument to specify the default value
when the key is not found (defaults to nil
).
The easiest and quickest way to update the get
calls is using the new version
of it with the trailing bang, like so:
- value = MyApp.Cache.get("key")
+ value = MyApp.Cache.get!("key")
Other alternatives:
# Using the default
value = MyApp.Cache.get!("key", "default")
# Handling the response
case MyApp.Cache.get("key", "default") do
{:ok, "default"} ->
#=> your logic handling success with default value
{:ok, value} ->
#=> your logic handling success
{:error, reason} ->
#=> your logic handling other errors
end
Update has_key?
calls
The new API does not have a "trailing bang function (!
)" version for the
has_key?
callback. Therefore, you must work with the ok/error tuple API.
For example:
- bool = MyApp.Cache.has_key?("key")
+ {:ok, bool} = MyApp.Cache.has_key?("key")
Update Transaction API calls
The new API does not have a "trailing bang function (!
)" version for the
Transaction API callbacks. Therefore, you must work with the ok/error tuple API.
For example:
- bool = MyApp.Cache.in_transaction?()
+ {:ok, bool} = MyApp.Cache.in_transaction?()
- value = MyApp.Cache.transaction(fn -> MyApp.Cache.get("key") end)
+ {:ok, value} = MyApp.Cache.transaction(fn -> MyApp.Cache.get!("key") end)
Update flush
calls
The callback flush
is deprecated, you should use delete_all
instead:
- MyApp.Cache.flush()
+ MyApp.Cache.delete_all()
Storing nil
values
Previously, Nebulex used to skip storing nil
values in the cache. The main
reason was the semantics assumed for the nil
value, being used to validate
whether a key existed in the cache or not. However, this could be a limitation.
Since Nebulex v3, any Elixir term can be stored in the cache (including nil
),
Nebulex doesn't perform any validation whatsoever. Any meaning or semantics
behind nil
(or any other term) is up to the user.
Additionally, a new callback fetch/2
is introduced, which is the base or main
function for retrieving a key from the cache; in fact, the get
callback is
implemented using fetch
underneath.
Deprecated Stats API
The Nebulex.Adapter.Stats
behaviour has been deprecated. Therefore, the
callbacks stats/0
and dispatch_stats/1
are no longer available and must
be removed from your code.
As a quick fix, you can update the stats calls using the new Info API, like this:
- :ok = MyApp.Cache.stats()
+ stats = MyApp.Cache.info!(:stats)
Since Nebulex v3, the adapter's Info API is introduced. This is a more generic API to get information about the cache, including the stats. Adapters are responsible for implementing the Info API and are also free to add the information specification keys they want. See c:Nebulex.Cache.info/2 and the "Cache Info Guide" for more information.
Deprecated Persistence API
Persistence can be implemented in many different ways depending on the use case. A persistence API with a specific implementation may not be very useful because it won't cover all possible use cases. For example, if your application is on AWS, perhaps it makes more sense to use S3 as persistent storage, not the local file system. Since Nebulex v3, persistence is an add-on that can be provided by the backend (e.g., Redis), another library, or the application itself. And it should be configured via the adapter's configuration (or maybe directly with the backend).
Therefore, the Nebulex.Adapter.Persistence
behaviour has been deprecated, so
the callbacks dump/2
and load/2
are no longer available and must be removed
from your code.
- MyApp.Cache.dump("my_backup")
- MyApp.Cache.load("my_backup")
Update Query API calls
The old Query API received as an argument the query to run against the adapter or backend, which could be any Elixir term (based on the adapter). Now, the Query API expects a "Query Spec" as an argument to work.
Updating all
calls
The callback all/2
was merged into get_all/2
; only the latter can be used
now. The new version of get_all/2
accepts a query-spec as a first argument.
Instead of using all
, you'll call get_all
:
- results = MyApp.Cache.all()
+ results = MyApp.Cache.get_all!()
Passing a query as an argument:
- results = MyApp.Cache.all(my_query)
+ results = MyApp.Cache.get_all!(query: my_query)
Besides, the option :return
is deprecated. You may consider using the
:select
option of the query-spec. For example:
- results = MyApp.Cache.all(nil, return: :value)
+ results = MyApp.Cache.get_all!(select: :value)
stream
function
The same changes and examples apply to the stream
function.
Updating get_all
calls
Similarly, you must update the get_all
calls:
- results = MyApp.Cache.get_all([k1, k2, ...])
+ results = MyApp.Cache.get_all!(in: [k1, k2, ...])
Updating count_all
and delete_all
calls
Similarly to get_all
, you must use a query-spec as the first argument when
calling count_all
or delete_all
.
- count = MyApp.Cache.count_all()
+ count = MyApp.Cache.count_all!()
- count = MyApp.Cache.delete_all()
+ count = MyApp.Cache.delete_all!()
- count = MyApp.Cache.delete_all(my_query)
+ count = MyApp.Cache.delete_all!(query: my_query)
See Cache API Docs for more information and examples.
Update caching decorators
Nebulex v3 introduces some changes and new features to the Declarative Caching API (a.k.a caching decorators). We will highlight mostly the changes and perhaps a few new features. However, it is highly recommended you check the documentation for more information about all the new features and changes.
Update :cache
option
The :cache
option doesn't support MFA tuples anymore. The possible values are
a cache module, a dynamic cache spec, or an anonymous function that optionally
receives the decorator's context as an argument and must return the cache to use
(a cache module or a dynamic cache spec).
- @decorate cacheable(cache: {MyApp.Caching, :get_cache, []})
+ @decorate cacheable(cache: &MyApp.Caching.get_cache/1)
Optionally, if you don't need the decorator context to compute the cache, use a 0-arity function instead:
- @decorate cacheable(cache: {MyApp.Caching, :get_cache, []})
+ @decorate cacheable(cache: &MyApp.Caching.get_cache/0)
The get_cache
function may look like this:
defmodule MyApp.Caching do
alias Nebulex.Caching.Decorators.Context
def get_cache(%Context{} = context) do
#=> return the cache
end
# Without the context
def get_cache do
#=> return the cache
end
end
Update :keys
option
The option :keys
is deprecated. Instead, consider using the option :key
like this:
- @decorate cacheable(cache: MyApp.Cache, keys: ["foo", "bar"])
+ @decorate cacheable(cache: MyApp.Cache, key: {:in, ["foo", "bar"]})
Update :key_generator
option
The :key_generator
option is deprecated. Instead, you can use the :key
option with an anonymous function that optionally receives the decorator's
context as an argument and must return the key to use.
- @decorate cacheable(cache: MyApp.Cache, key_generator: CustomKeyGenerator)
+ @decorate cacheable(cache: MyApp.Cache, key: &MyApp.Caching.compute_key/1)
Optionally, if you don't need the decorator context to compute the key, use a 0-arity function instead:
- @decorate cacheable(cache: MyApp.Cache, key_generator: CustomKeyGenerator)
+ @decorate cacheable(cache: MyApp.Cache, key: &MyApp.Caching.compute_key/0)
The compute_key
function may look like this:
defmodule MyApp.Caching do
alias Nebulex.Caching.Decorators.Context
def compute_key(%Context{} = context) do
#=> return the key
end
# Without the context
def compute_key do
#=> return the key
end
end
Update :default_key_generator
option
The Nebulex.Caching.KeyGenerator
behaviour is deprecated. You can use
an anonymous function for the :default_key_generator
option instead
(the function must be provided in the format &Mod.fun/arity
). Besides,
the :default_key_generator
option must be provided to use Nebulex.Caching
.
First, remove :default_key_generator
option from all places it is used:
defmodule MyApp.Cache do
use Nebulex.Cache,
otp_app: :my_app,
adapter: Nebulex.Adapters.Local,
- default_key_generator: __MODULE__
...
end
Then, add it to the modules using use Nebulex.Caching
:
defmodule MyApp.Books do
use Nebulex.Caching,
+ default_key_generator: &MyApp.Keygen.generate/1
...
end
Global options
Decorators may become verbose sometimes, since you have to provide options like
the :cache
on each decorator definition. Therefore, Nebulex supports adding
some of the common options globally when using use Nebulex.Caching
.
You can update your modules using declarative caching like this:
defmodule MyApp.Books do
use Nebulex.Caching,
+ cache: MyApp.Cache
+ match: &__MODULE__.match/1
+ on_error: :raise
- @decorate cacheable(cache: MyApp.Cache, key: id, match: &match/1, on_error: :raise)
+ @decorate cacheable(key: id)
def get_book(id) do
# ... logic to retrieve a book
end
- @decorate cache_put(cache: MyApp.Cache, key: book.id, match: &match/1, on_error: :raise)
+ @decorate cache_put(key: book.id)
def update_book(book, update_attrs) do
# ... logic to update a book
end
- @decorate cache_evict(cache: MyApp.Cache, key: book.id, on_error: :raise)
+ @decorate cache_evict(key: book.id)
def delete_book(book) do
# ... logic to delete a book
end
...
end
See Nebulex.Caching
options for more information.
:references
option and dynamic caches
The option :references
in the cacheable
decorator supports referencing
a dynamic cache. For example:
defmodule MyApp.Books do
use Nebulex.Caching
@decorate cacheable(cache: dynamic_cache(MyApp.Cache, :books_cache))
def find_book(isbn) do
# your logic ...
end
end
See "Caching Decorators" for more information.