Nebulex v3 has several breaking changes, including in the Cache API itself. This guide covers the changes most likely to affect your application code. It does not list every change, and it is not a line-by-line translation guide; it points you to the areas where you should read the new documentation.
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"},
...
]
endUpdate Adapter Compile-Time Options
Some adapters support compile-time options like :primary_storage_adapter for
configuring the underlying storage adapter. In v3, these options must be wrapped
in the :adapter_opts keyword list.
For example, if you're using Nebulex.Adapters.Partitioned with a custom
primary storage adapter:
defmodule MyApp.PartitionedCache do
use Nebulex.Cache,
otp_app: :my_app,
adapter: Nebulex.Adapters.Partitioned,
- primary_storage_adapter: Nebulex.Adapters.Local
+ adapter_opts: [primary_storage_adapter: Nebulex.Adapters.Local]
endUpdate Cache API Calls
The most significant change is in 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. Use it when you want to handle the different outcomes with pattern matching.
- The same functions with a trailing bang (
!). Use them when you expect the call to succeed and want it to raise otherwise.
So there are two ways to update your code:
- Review each cache call and handle the new ok/error tuple response.
- Replace each cache call with its trailing-bang (
!) version.
Let's look at both.
Using the 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. This only works where the result doesn't matter; when you fetch a key you need its value, so you can't ignore it there.
- Pattern-match the success case. If you go this way, consider the
trailing-bang (
!) functions in the next section instead. - 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
endNotice that the fetch function was introduced in 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")Bang functions cover most cases, but a few calls behave differently or have no bang version. The next sections cover those.
Update get calls
get/2 changed a bit beyond the ok/error tuple. It used to return nil
when the key wasn't in the cache; now it takes a default value to return in
that case (nil by default).
The quickest way to update get calls is the trailing-bang version:
- 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
endUpdate 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)Adapter Transaction Implementation
Since v3.0.0, the default transaction implementation that
Nebulex.Adapter.Transaction used to provide is gone. The behaviour itself
remains; each adapter implements it according to its topology and locking
strategy (for example, local ETS-based locks or distributed locks).
If your adapter does not implement transactions, review your usage of
transaction/2 and related callbacks, and adapt your code accordingly.
Update flush calls
The flush callback was removed; use delete_all instead:
- MyApp.Cache.flush()
+ MyApp.Cache.delete_all()Storing nil values
Nebulex used to skip storing nil values, because nil was how it told a
missing key apart from a present one. That was a limitation.
Since v3, any Elixir term can be stored, including nil; Nebulex does no
validation. What nil (or any other term) means is up to you.
v3 also adds fetch/2, the base function for reading a key; get is
implemented on top of it.
Removed Stats API
The Nebulex.Adapter.Stats behaviour was removed. 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)The Info API, new in v3, is a more general way to get information about the
cache, including stats. Each adapter implements it and chooses which
information keys it returns. See Nebulex.Cache.info/2 and the
"Cache Info Guide".
Removed Persistence API
Persistence can be done in many ways depending on the use case, and a single built-in implementation would not cover them. For example, an application on AWS may want S3 as persistent storage rather than the local file system. Since v3, persistence is an add-on provided by the backend (e.g., Redis), another library, or the application itself, and configured through the adapter's configuration or the backend directly.
Therefore, the Nebulex.Adapter.Persistence behaviour was removed, 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 was removed; passing it now raises a
NimbleOptions.ValidationError. Use the :select option of the query-spec
instead. 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.
New read-through helpers
After migration, consider using fetch_or_store/3 and get_or_store/3
for common read-through caching patterns.
Update caching decorators
Nebulex v3 changes the declarative caching API (the caching decorators) and
adds new features. This section covers the changes and a few of the new
features; see the Nebulex.Caching docs for the rest.
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
endUpdate :keys option
The :keys option is no longer supported. Use :key instead:
- @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 no longer supported. Use :key with an
anonymous function instead. The function optionally receives the decorator
context 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
endUpdate :default_key_generator option
The Nebulex.Caching.KeyGenerator behaviour was removed. 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__
...
endThen, add it to the modules using use Nebulex.Caching:
defmodule MyApp.Books do
use Nebulex.Caching,
+ default_key_generator: &MyApp.Keygen.generate/1
...
endGlobal options
Repeating options like :cache on every decorator gets verbose, so some of
the common options can be set once in use Nebulex.Caching.
You can update your modules 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
...
endSee Nebulex.Caching options
for more information.
Dynamic caches
The :cache option accepts a dynamic cache, built with dynamic_cache/2.
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
endSee "Caching Decorators" for more information.