Skip to content

Commit

Permalink
[#88] Nebulex.Adapters.Nil adapter for disabling cache
Browse files Browse the repository at this point in the history
  • Loading branch information
cabol committed Jan 3, 2021
1 parent b015a40 commit d626e20
Show file tree
Hide file tree
Showing 13 changed files with 391 additions and 66 deletions.
31 changes: 17 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,21 @@ The supported caches and their adapters are:

Cache | Nebulex Adapter | Dependency
:-----| :---------------| :---------
Generational Local Cache (ETS + Shards) | [Nebulex.Adapters.Local][la] | Built-In
Partitioned (layer on top of a local cache) | [Nebulex.Adapters.Partitioned][pa] | Built-In
Replicated (layer on top of a local cache) | [Nebulex.Adapters.Replicated][ra] | Built-In
Multilevel (layer on top of existing caches) | [Nebulex.Adapters.Multilevel][ma] | Built-In
Cachex | Nebulex.Adapters.Cachex | [nebulex_adapters_cachex][nebulex_adapters_cachex]
Redis | NebulexRedisAdapter | [nebulex_redis_adapter][nebulex_redis_adapter]
Generational Local Cache | [Nebulex.Adapters.Local][la] | Built-In
Partitioned | [Nebulex.Adapters.Partitioned][pa] | Built-In
Replicated | [Nebulex.Adapters.Replicated][ra] | Built-In
Multilevel | [Nebulex.Adapters.Multilevel][ma] | Built-In
Nil (special adapter that disables the cache) | [Nebulex.Adapters.Nil][nil] | Built-In
Cachex | Nebulex.Adapters.Cachex | [nebulex_adapters_cachex][nbx_cachex]
Redis | NebulexRedisAdapter | [nebulex_redis_adapter][nbx_redis]

[la]: http://hexdocs.pm/nebulex/Nebulex.Adapters.Local.html
[pa]: http://hexdocs.pm/nebulex/Nebulex.Adapters.Partitioned.html
[ra]: http://hexdocs.pm/nebulex/Nebulex.Adapters.Replicated.html
[ma]: http://hexdocs.pm/nebulex/Nebulex.Adapters.Multilevel.html
[nebulex_adapters_cachex]: https://github.com/cabol/nebulex_adapters_cachex
[nebulex_redis_adapter]: https://github.com/cabol/nebulex_redis_adapter
[nil]: http://hexdocs.pm/nebulex/Nebulex.Adapters.Nil.html
[nbx_cachex]: https://github.com/cabol/nebulex_adapters_cachex
[nbx_redis]: https://github.com/cabol/nebulex_redis_adapter

For example, if you want to use a built-in cache, add to your `mix.exs` file:

Expand All @@ -66,12 +68,12 @@ def deps do
end
```

In order to give more flexibility and loading only needed dependencies, Nebulex
makes all its dependencies as optional. For example:
In order to give more flexibility and fetch only needed dependencies, Nebulex
makes all dependencies optional. For example:

* For intensive workloads, we may want to use `:shards` as the backend for the
local adapter and having partitioned tables. In such a case, you have to add
`:shards` to the dependency list.
* For intensive workloads, you may want to use `:shards` as the backend for
the local adapter and having partitioned tables. In such a case, you have
to add `:shards` to the dependency list.

* For enabling the usage of
[declarative annotation-based caching via decorators][nbx_caching],
Expand All @@ -81,7 +83,8 @@ makes all its dependencies as optional. For example:
to add `:telemetry` to the dependency list.
See [telemetry guide][telemetry].

* Also, all the external adapters have to be added as a dependency as well.
* If you are using an adapter other than the built-in ones (e.g: Cachex or
Redis adapters), you have to add that dependency too.

[nbx_caching]: http://hexdocs.pm/nebulex/Nebulex.Caching.html
[telemetry]: http://hexdocs.pm/nebulex/telemetry.html
Expand Down
13 changes: 7 additions & 6 deletions guides/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ its simplicity, flexibility and pluggable architecture. In the same way
as Ecto, developers can provide their own cache (adapter) implementations.

In this guide, we're going to learn some basics about Nebulex, such as insert,
retrieve and destroy cache entries (key/value pairs).
retrieve and destroy cache entries.

## Adding Nebulex to an application

Expand Down Expand Up @@ -40,9 +40,9 @@ end
In order to give more flexibility and loading only needed dependencies, Nebulex
makes all its dependencies as optional. For example:

* For intensive workloads, we may want to use `:shards` as the backend for the
local adapter and having partitioned tables. In such a case, you have to add
`:shards` to the dependency list.
* For intensive workloads, you may want to use `:shards` as the backend for
the local adapter and having partitioned tables. In such a case, you have
to add `:shards` to the dependency list.

* For enabling the usage of
[declarative annotation-based caching via decorators][nbx_caching],
Expand All @@ -52,7 +52,8 @@ makes all its dependencies as optional. For example:
to add `:telemetry` to the dependency list.
See [telemetry guide][telemetry].

* Also, all the external adapters have to be added as a dependency as well.
* If you are using an adapter other than the built-in ones (e.g: Cachex or
Redis adapters), you have to add that dependency too.

[nbx_caching]: http://hexdocs.pm/nebulex/Nebulex.Caching.html
[telemetry]: http://hexdocs.pm/nebulex/telemetry.html
Expand Down Expand Up @@ -316,7 +317,7 @@ Nebulex also provides a function to flush all cache entries, like so:

```elixir
iex> Blog.Cache.flush()
:ok
_evicted_entries
```

## Info
Expand Down
10 changes: 1 addition & 9 deletions lib/nebulex/adapter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ defmodule Nebulex.Adapter do
"""
@type adapter_meta :: %{optional(atom) => term}

@typedoc "Proxy type to the options"
@type opts :: Nebulex.Cache.opts()

@doc """
The callback invoked in case the adapter needs to inject code.
"""
Expand All @@ -27,12 +24,7 @@ defmodule Nebulex.Adapter do
@doc """
Initializes the adapter supervision tree by returning the children.
"""
@callback init(opts) :: {:ok, child_spec, adapter_meta}
when child_spec:
:supervisor.child_spec()
| {module(), term()}
| module()
| nil
@callback init(config :: Keyword.t()) :: {:ok, :supervisor.child_spec(), adapter_meta}

@doc """
RExecutes the function `fun` passing as parameters the adapter and metadata
Expand Down
8 changes: 6 additions & 2 deletions lib/nebulex/adapters/local.ex
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ defmodule Nebulex.Adapters.Local do

@compile {:inline, list_gen: 1, newer_gen: 1}

## Adapter
## Nebulex.Adapter

@impl true
defmacro __before_compile__(_env) do
Expand Down Expand Up @@ -367,6 +367,8 @@ defmodule Nebulex.Adapters.Local do
{:ok, child, meta}
end

## Nebulex.Adapter.Entry

@impl true
def get(%{meta_tab: meta_tab, backend: backend, stats_counter: ref}, key, _opts) do
meta_tab
Expand Down Expand Up @@ -560,6 +562,8 @@ defmodule Nebulex.Adapters.Local do
|> update_stats(:put, ref)
end

## Nebulex.Adapter.Storage

@impl true
def size(%{meta_tab: meta_tab, backend: backend}) do
meta_tab
Expand All @@ -578,7 +582,7 @@ defmodule Nebulex.Adapters.Local do
|> update_stats(:flush, ref)
end

## Queryable
## Nebulex.Adapter.Queryable

@impl true
def all(%{meta_tab: meta_tab, backend: backend}, query, opts) do
Expand Down
10 changes: 7 additions & 3 deletions lib/nebulex/adapters/multilevel.ex
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ defmodule Nebulex.Adapters.Multilevel do
# Multi-level Cache Models
@models [:inclusive, :exclusive]

## Adapter
## Nebulex.Adapter

@impl true
defmacro __before_compile__(_env) do
Expand Down Expand Up @@ -202,6 +202,8 @@ defmodule Nebulex.Adapters.Multilevel do
{:ok, child_spec, meta}
end

## Nebulex.Adapter.Entry

@impl true
def get(%{levels: levels, model: model}, key, opts) do
fun = fn level, {default, prev} ->
Expand Down Expand Up @@ -316,6 +318,8 @@ defmodule Nebulex.Adapters.Multilevel do
end)
end

## Nebulex.Adapter.Storage

@impl true
def size(%{levels: levels}) do
Enum.reduce(levels, 0, fn l_meta, acc ->
Expand All @@ -330,7 +334,7 @@ defmodule Nebulex.Adapters.Multilevel do
end)
end

## Queryable
## Nebulex.Adapter.Queryable

@impl true
def all(%{levels: levels}, query, opts) do
Expand Down Expand Up @@ -361,7 +365,7 @@ defmodule Nebulex.Adapters.Multilevel do
)
end

## Transaction
## Nebulex.Adapter.Transaction

@impl true
def transaction(%{levels: levels} = adapter_meta, opts, fun) do
Expand Down
148 changes: 148 additions & 0 deletions lib/nebulex/adapters/nil.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
defmodule Nebulex.Adapters.Nil do
@moduledoc """
The **Nil adapter** is a special cache adapter that disables the cache;
it loses all the items saved on it and it returns `nil` for all the read
and `true` for all save operations. This adapter is mostly useful for tests.
## Example
Suppose you have an application using Ecto for database access and Nebulex
for caching. Then, you have defined a cache and a repo within it. Since you
are using a database, there might be some cases you may want to disable the
cache to avoid issues when running the test, for example, in some test cases,
when accessing the database you expect no data at all, but you could retrieve
the data from cache anyway because maybe it was cached in a previous test.
Therefore, you have to flush the cache before to run each test to make sure
the data is always empty. This is where the Nil adapter comes in, instead of
adding code to flush the cache before each test, you could define a test cache
using the Nil adapter for the tests.
One one hand, you have defined the cache in your application within
`lib/my_app/cache.ex`:
defmodule MyApp.Cache do
use Nebulex.Cache,
otp_app: :my_app,
adapter: Nebulex.Adapters.Local
end
And on the other hand, in the tests you have defined the test cache within
`test/support/test_cache.ex`:
defmodule MyApp.TestCache do
use Nebulex.Cache,
otp_app: :my_app,
adapter: Nebulex.Adapters.Nil
end
Now, we have to tell the app what cache to use depending on the environment,
for tests we want `MyApp.TestCache`, otherwise it is always `MyApp.Cache`.
We can do this very easy by introducing a new config parameter to decide
what cache module to use. For tests you can define the config
`config/test.exs`:
config :my_app,
nebulex_cache: MyApp.TestCache,
...
The final piece is to read the config parameter and start the cache properly.
Within `lib/my_app/application.ex` you could have:
def start(_type, _args) do
children = [
{Application.get_env(:my_app, :nebulex_cache, MyApp.Cache), []},
]
...
As you can see, by default `MyApp.Cache` is always used, unless the
`:nebulex_cache` option points to a different module, which will be
when tests are executed (`:test` env).
"""

# Provide Cache Implementation
@behaviour Nebulex.Adapter
@behaviour Nebulex.Adapter.Entry
@behaviour Nebulex.Adapter.Storage
@behaviour Nebulex.Adapter.Queryable
@behaviour Nebulex.Adapter.Persistence
@behaviour Nebulex.Adapter.Stats

# Inherit default transaction implementation
use Nebulex.Adapter.Transaction

## Nebulex.Adapter

@impl true
defmacro __before_compile__(_env), do: :ok

@impl true
def init(_opts) do
child_spec = Supervisor.child_spec({Agent, fn -> :ok end}, id: {Agent, 1})
{:ok, child_spec, %{}}
end

## Nebulex.Adapter.Entry

@impl true
def get(_, _, _), do: nil

@impl true
def get_all(_, _, _), do: %{}

@impl true
def put(_, _, _, _, _, _), do: true

@impl true
def put_all(_, _, _, _, _), do: true

@impl true
def delete(_, _, _), do: :ok

@impl true
def take(_, _, _), do: nil

@impl true
def has_key?(_, _), do: false

@impl true
def ttl(_, _), do: nil

@impl true
def expire(_, _, _), do: true

@impl true
def touch(_, _), do: true

@impl true
def incr(_, _, _, _, _), do: 1

## Nebulex.Adapter.Storage

@impl true
def size(_), do: 0

@impl true
def flush(_), do: 0

## Nebulex.Adapter.Queryable

@impl true
def all(_, _, _), do: []

@impl true
def stream(_, _, _), do: Stream.each([], & &1)

## Nebulex.Adapter.Persistence

@impl true
def dump(_, _, _), do: :ok

@impl true
def load(_, _, _), do: :ok

## Nebulex.Adapter.Stats

@impl true
def stats_info(_), do: %Nebulex.Stats{}
end
10 changes: 7 additions & 3 deletions lib/nebulex/adapters/partitioned.ex
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ defmodule Nebulex.Adapters.Partitioned do
alias Nebulex.Cache.Cluster
alias Nebulex.RPC

## Adapter
## Nebulex.Adapter

@impl true
defmacro __before_compile__(env) do
Expand Down Expand Up @@ -295,6 +295,8 @@ defmodule Nebulex.Adapters.Partitioned do
end
end

## Nebulex.Adapter.Entry

@impl true
def get(adapter_meta, key, opts) do
call(adapter_meta, key, :get, [key, opts], opts)
Expand Down Expand Up @@ -419,6 +421,8 @@ defmodule Nebulex.Adapters.Partitioned do
call(adapter_meta, key, :touch, [key])
end

## Nebulex.Adapter.Storage

@impl true
def size(%{name: name, task_sup: task_sup} = adapter_meta) do
task_sup
Expand All @@ -444,7 +448,7 @@ defmodule Nebulex.Adapters.Partitioned do
|> Enum.sum()
end

## Queryable
## Nebulex.Adapter.Queryable

@impl true
def all(%{name: name, task_sup: task_sup} = adapter_meta, query, opts) do
Expand Down Expand Up @@ -486,7 +490,7 @@ defmodule Nebulex.Adapters.Partitioned do
)
end

## Transaction
## Nebulex.Adapter.Transaction

@impl true
def transaction(%{name: name} = adapter_meta, opts, fun) do
Expand Down
Loading

0 comments on commit d626e20

Please sign in to comment.