# Upgrading to v3.0

For v3, Nebulex introduces several breaking changes, including the Cache API
itself. This guide aims to highlight the most significant changes to make the
transition to v3 easier. Be aware that 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 where the new documentation should be consulted.

---

## 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:

```elixir
defp deps do
  [
    {:nebulex, "~> 3.0"},
    {:nebulex_local, "~> 3.0"},
    ...
  ]
end
```

---

## Update 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:

```diff
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]
end
```

---

## Update Cache API Calls

The most significant change is in the [Cache API](`Nebulex.Cache`). 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:

1. **Review your code's cache calls** and handle the new ok/error tuple response.
2. **Replace your cache calls** using the function version with the trailing
   bang (`!`).

Let's take a closer look at both approaches.

### 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**. 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:

```diff
- :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:

```diff
- 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:

```diff
- :ok = MyApp.Cache.put("key", "value")
- value = MyApp.Cache.get("key")
```

New code may look like this:

```elixir
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 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 {: .tip}
>
> 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:

```diff
- :ok = MyApp.Cache.put("key", "value")
+ :ok = MyApp.Cache.put!("key", "value")
```

Although 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:

```diff
- value = MyApp.Cache.get("key")
+ value = MyApp.Cache.get!("key")
```

Other alternatives:

```elixir
# 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:

```diff
- 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:

```diff
- 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`, Nebulex no longer provides a default transaction implementation
in core (`Nebulex.Adapter.Transaction`).

Adapters are now responsible for implementing transaction behavior according to
their 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 callback `flush` is deprecated, you should use `delete_all` instead:

```diff
- 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.

## 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:

```diff
- :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"](info-api.md) for more information.

## Removed 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 was removed, so
the callbacks `dump/2` and `load/2` are no longer available and must be removed
from your code.

```diff
- 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"](`c:Nebulex.Cache.get_all/2#query-specification`)
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`:

```diff
- results = MyApp.Cache.all()
+ results = MyApp.Cache.get_all!()
```

Passing a query as an argument:

```diff
- 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:

```diff
- results = MyApp.Cache.all(nil, return: :value)
+ results = MyApp.Cache.get_all!(select: :value)
```

> #### `stream` function {: .info}
>
> The same changes and examples apply to the `stream` function.

### Updating `get_all` calls

Similarly, you must update the `get_all` calls:

```diff
- 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`.

```diff
- 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](`Nebulex.Cache`) 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 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).

```diff
- @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:

```diff
- @decorate cacheable(cache: {MyApp.Caching, :get_cache, []})
+ @decorate cacheable(cache: &MyApp.Caching.get_cache/0)
```

The `get_cache` function may look like this:

```elixir
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 no longer supported. Instead, consider using the option
`:key`
like this:

```diff
- @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. 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.

```diff
- @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:

```diff
- @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:

```elixir
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 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:

```diff
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`:

```diff
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:

```diff
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](`m:Nebulex.Caching#module-compilation-time-options`)
for more information.

### `:references` option and dynamic caches

The option `:references` in the `cacheable` decorator supports referencing
a dynamic cache. For example:

```elixir
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"](Nebulex.Caching.Decorators.html) for more information._**
