Skip to content

Commit

Permalink
[#189] Nebulex v3 initial draft
Browse files Browse the repository at this point in the history
- Ok/Error tuple Cache API.
- Remove deprecated module `Nebulex.Hook`.
- Use `NimbleOptions` for defining and validating cache options.
- Move adapters to separate repos.
- All commands optionally support a dynamic cache as the first argument.
- Deprecate `Nebulex.Adapter.Stats` in favor of `Nebulex.Adapter.Info`.
- Rename the Telemetry metadata field from `:function_name` to `:command`.
- Support dynamic cache in decorators.
- Evaluate the `:cache` option for decorated functions in runtime.
- Changes and improvements on Decorator API.
- Telemetry events for cache commands out-of-box.
- Unify `all` and `get_all` into one single callback `get_all`
  • Loading branch information
cabol committed Oct 15, 2023
1 parent afb5eb3 commit 5249c63
Show file tree
Hide file tree
Showing 126 changed files with 8,851 additions and 12,961 deletions.
4 changes: 2 additions & 2 deletions .credo.exs
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@
## Refactoring Opportunities
#
{Credo.Check.Refactor.CondStatements, []},
{Credo.Check.Refactor.CyclomaticComplexity, []},
{Credo.Check.Refactor.CyclomaticComplexity, [max_complexity: 40]},
{Credo.Check.Refactor.FunctionArity, []},
{Credo.Check.Refactor.LongQuoteBlocks, [max_line_count: 300, ignore_comments: true]},
{Credo.Check.Refactor.LongQuoteBlocks, [max_line_count: 200]},
# {Credo.Check.Refactor.MapInto, []},
{Credo.Check.Refactor.MatchInCondition, []},
{Credo.Check.Refactor.NegatedConditionsInUnless, []},
Expand Down
6 changes: 0 additions & 6 deletions .dialyzer_ignore.exs

This file was deleted.

21 changes: 21 additions & 0 deletions .doctor.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
%Doctor.Config{
ignore_modules: [
Nebulex.Cache.Impl,
Nebulex.Cache.Options,
Nebulex.Cache.QuerySpec,
Nebulex.Caching.Options,
Nebulex.Dialyzer.CachingDecorators
],
ignore_paths: [],
min_module_doc_coverage: 40,
min_module_spec_coverage: 0,
min_overall_doc_coverage: 80,
min_overall_moduledoc_coverage: 100,
min_overall_spec_coverage: 0,
exception_moduledoc_required: true,
raise: false,
reporter: Doctor.Reporters.Full,
struct_type_spec_required: true,
umbrella: false,
failed: false
}
16 changes: 16 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
locals_without_parens = [
# Nebulex.Utils
unwrap_or_raise: 1,
wrap_ok: 1,
wrap_error: 1,
wrap_error: 2,

# Nebulex.Cache.Utils
defcacheapi: 2,

# Nebulex.Adapter
defcommand: 1,
defcommand: 2,
defcommandp: 1,
defcommandp: 2,

# Nebulex.Caching
dynamic_cache: 2,
keyref: 1,
keyref: 2,

Expand Down
35 changes: 17 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,26 @@ jobs:
strategy:
matrix:
include:
- elixir: 1.14.x
otp: 25.x
- elixir: 1.15.x
otp: 26.x
os: 'ubuntu-latest'
style: true
coverage: true
sobelow: true
dialyzer: true
doctor: true
- elixir: 1.15.x
otp: 25.x
os: 'ubuntu-latest'
- elixir: 1.14.x
otp: 25.x
os: 'ubuntu-latest'
- elixir: 1.13.x
otp: 24.x
os: 'ubuntu-latest'
- elixir: 1.11.x
- elixir: 1.12.x
otp: 23.x
os: 'ubuntu-20.04'
inch-report: true
- elixir: 1.9.x
otp: 22.x
os: 'ubuntu-20.04'

env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
Expand Down Expand Up @@ -87,27 +90,23 @@ jobs:
if: ${{ matrix.style }}

- name: Run tests
run: |
epmd -daemon
mix test --trace
run: mix test
if: ${{ !matrix.coverage }}

- name: Run tests with coverage
run: |
epmd -daemon
mix coveralls.github
run: mix coveralls.github
if: ${{ matrix.coverage }}

- name: Run sobelow
run: mix sobelow --exit --skip
run: mix sobelow --skip --exit Low
if: ${{ matrix.sobelow }}

- name: Restore PLT Cache
uses: actions/cache@v3
id: plt-cache
with:
path: priv/plts
key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plt-v1
key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plt-v3-1
if: ${{ matrix.dialyzer }}

- name: Create PLTs
Expand All @@ -120,6 +119,6 @@ jobs:
run: mix dialyzer --format github
if: ${{ matrix.dialyzer && steps.plt-cache.outputs.cache-hit != 'true' }}

- name: Doc coverage report
run: MIX_ENV=docs mix inch.report
if: ${{ matrix.inch-report }}
- name: Run documentation health check
run: mix doctor
if: ${{ matrix.doctor }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ erl_crash.dump
/priv
.sobelow*
/config
Elixir*
123 changes: 47 additions & 76 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
![CI](https://github.com/cabol/nebulex/workflows/CI/badge.svg)
[![Coverage Status](https://img.shields.io/coveralls/cabol/nebulex.svg)](https://coveralls.io/github/cabol/nebulex)
[![Inline docs](http://inch-ci.org/github/cabol/nebulex.svg)](http://inch-ci.org/github/cabol/nebulex)
[![Hex Version](https://img.shields.io/hexpm/v/nebulex.svg)](https://hex.pm/packages/nebulex)
[![Docs](https://img.shields.io/badge/docs-hexpm-blue.svg)](https://hexdocs.pm/nebulex)
[![License](https://img.shields.io/hexpm/l/nebulex.svg)](LICENSE)
Expand All @@ -17,7 +16,7 @@ underlying caching implementations, such as [Redis][redis],
[Memcached][memcached], or even other Elixir cache implementations like
[Cachex][cachex]. Additionally, it provides totally out-of-box features such as
[cache usage patterns][cache_patterns],
[declarative annotation-based caching][nbx_caching], and
[declarative decorator-based caching][nbx_caching], and
[distributed cache topologies][cache_topologies], among others.

See the [getting started guide](http://hexdocs.pm/nebulex/getting-started.html)
Expand All @@ -28,82 +27,73 @@ for more information.
[cachex]: https://github.com/whitfin/cachex
[redis]: https://redis.io/
[memcached]: https://memcached.org/
[nbx_caching]: http://hexdocs.pm/nebulex/Nebulex.Caching.html
[nbx_caching]: http://hexdocs.pm/nebulex/Nebulex.Caching.Decorators.html
[cache_patterns]: http://hexdocs.pm/nebulex/cache-usage-patterns.html
[cache_topologies]: https://docs.oracle.com/middleware/1221/coherence/develop-applications/cache_intro.htm

## Usage

You need to add `nebulex` as a dependency to your `mix.exs` file. However, in
the case you want to use an external (a non built-in adapter) cache adapter,
you also have to add the proper dependency to your `mix.exs` file.

The supported caches and their adapters are:
You need to add both Nebulex and the cache adapter as a dependency to your
`mix.exs` file. The supported caches and their adapters are:

Cache | Nebulex Adapter | Dependency
:-----| :---------------| :---------
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]
Generational Local Cache | Nebulex.Adapters.Local | [nebulex_adapters_local][la]
Partitioned | Nebulex.Adapters.Partitioned | [nebulex_adapters_partitioned][pa]
Replicated | Nebulex.Adapters.Replicated | [nebulex_adapters_replicated][ra]
Multilevel | Nebulex.Adapters.Multilevel | [nebulex_adapters_multilevel][ma]
Redis | NebulexRedisAdapter | [nebulex_redis_adapter][nbx_redis]
Cachex | Nebulex.Adapters.Cachex | [nebulex_adapters_cachex][nbx_cachex]
Distributed with Horde | Nebulex.Adapters.Horde | [nebulex_adapters_horde][nbx_horde]
Multilevel with cluster broadcasting | NebulexLocalMultilevelAdapter | [nebulex_local_multilevel_adapter][nbx_local_multilevel]
Ecto Postgres table | Nebulex.Adapters.Ecto | [nebulex_adapters_ecto][nbx_ecto_postgres]

[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
[nil]: http://hexdocs.pm/nebulex/Nebulex.Adapters.Nil.html
[nbx_cachex]: https://github.com/cabol/nebulex_adapters_cachex
[la]: https://github.com/elixir-nebulex/nebulex_adapters_local
[pa]: https://github.com/elixir-nebulex/nebulex_adapters_partitioned
[ra]: https://github.com/elixir-nebulex/nebulex_adapters_replicated
[ma]: https://github.com/elixir-nebulex/nebulex_adapters_multilevel
[nbx_redis]: https://github.com/cabol/nebulex_redis_adapter
[nbx_cachex]: https://github.com/cabol/nebulex_adapters_cachex
[nbx_horde]: https://github.com/eliasdarruda/nebulex_adapters_horde
[nbx_local_multilevel]: https://github.com/slab/nebulex_local_multilevel_adapter
[nbx_ecto_postgres]: https://github.com/hissssst/nebulex_adapters_ecto


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

```elixir
def deps do
[
{:nebulex, "~> 2.5"},
{:shards, "~> 1.1"}, #=> When using :shards as backend
{:decorator, "~> 1.4"}, #=> When using Caching Annotations
{:telemetry, "~> 1.0"} #=> When using the Telemetry events (Nebulex stats)
{:nebulex, "~> 3.0"},
#=> When using the local cache adapter
{:nebulex_adapters_local, "~> 3.0"},
#=> When using Caching Annotations
{:decorator, "~> 1.4"},
#=> When using the Telemetry events
{:telemetry, "~> 1.2"}
]
end
```

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

* 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],
[declarative decorator-based caching via decorators][nbx_caching],
you have to add `:decorator` to the dependency list.

* For enabling Telemetry events to be dispatched when using Nebulex,
you have to add `:telemetry` to the dependency list.
See [telemetry guide][telemetry].

* If you want to use an external adapter (e.g: Cachex or Redis adapter), you
have to add the adapter dependency too.

[telemetry]: http://hexdocs.pm/nebulex/telemetry.html

Then run `mix deps.get` in your shell to fetch the dependencies. If you want to
use another cache adapter, just choose the proper dependency from the table
above.

Finally, in the cache definition, you will need to specify the `adapter:`
respective to the chosen dependency. For the local built-in cache it is:
respective to the chosen dependency. For the local cache would be:

```elixir
defmodule MyApp.Cache do
Expand All @@ -113,28 +103,29 @@ defmodule MyApp.Cache do
end
```

## Quickstart example
## Quickstart example using caching decorators

Assuming you are using `Ecto` and you want to use declarative caching:

```elixir
# In the config/config.exs file
config :my_app, MyApp.PartitionedCache,
primary: [
gc_interval: :timer.hours(12),
backend: :shards,
partitions: 2
]

# Defining a Cache with a partitioned topology
defmodule MyApp.PartitionedCache do
config :my_app, MyApp.Cache,
gc_interval: :timer.hours(12),
# Max 1M entries
max_size: 1_000_000,
# Max 2GB of memory
allocated_memory: 2_000_000_000,
gc_cleanup_min_timeout: :timer.seconds(10),
gc_cleanup_max_timeout: :timer.minutes(10)

# Defining the cache
defmodule MyApp.Cache do
use Nebulex.Cache,
otp_app: :my_app,
adapter: Nebulex.Adapters.Partitioned,
primary_storage_adapter: Nebulex.Adapters.Local
adapter: Nebulex.Adapters.Local
end

# Some Ecto schema
# Ecto schema
defmodule MyApp.Accounts.User do
use Ecto.Schema

Expand All @@ -153,26 +144,24 @@ end

# The Accounts context
defmodule MyApp.Accounts do
use Nebulex.Caching
use Nebulex.Caching, cache: MyApp.Cache

alias MyApp.Accounts.User
alias MyApp.PartitionedCache, as: Cache
alias MyApp.Repo

@ttl :timer.hours(1)

@decorate cacheable(cache: Cache, key: {User, id}, opts: [ttl: @ttl])
@decorate cacheable(key: {User, id}, opts: [ttl: @ttl])
def get_user!(id) do
Repo.get!(User, id)
end

@decorate cacheable(cache: Cache, key: {User, username}, opts: [ttl: @ttl])
@decorate cacheable(key: {User, username}, opts: [ttl: @ttl])
def get_user_by_username(username) do
Repo.get_by(User, [username: username])
end

@decorate cache_put(
cache: Cache,
keys: [{User, user.id}, {User, user.username}],
match: &match_update/1,
opts: [ttl: @ttl]
Expand All @@ -183,10 +172,7 @@ defmodule MyApp.Accounts do
|> Repo.update()
end

@decorate cache_evict(
cache: Cache,
keys: [{User, user.id}, {User, user.username}]
)
@decorate cache_evict(keys: [{User, user.id}, {User, user.username}])
def delete_user(%User{} = user) do
Repo.delete(user)
end
Expand Down Expand Up @@ -239,27 +225,12 @@ the directory [benchmarks](./benchmarks).
To run a benchmark test you have to run:

```
$ MIX_ENV=test mix run benchmarks/{BENCH_TEST_FILE}
```

Where `BENCH_TEST_FILE` can be any of:

* `local_with_ets_bench.exs`: benchmark for the local adapter using
`:ets` backend.
* `local_with_shards_bench.exs`: benchmark for the local adapter using
`:shards` backend.
* `partitioned_bench.exs`: benchmark for the partitioned adapter.

For example, for running the benchmark for the local adapter using `:shards`
backend:

```
$ MIX_ENV=test mix run benchmarks/local_with_shards_bench.exs
$ mix run benchmarks/benchmark.exs
```

Additionally, you can also run performance tests using `:basho_bench`.
See [nebulex_bench example](https://github.com/cabol/nebulex_examples/tree/master/nebulex_bench)
for more information.
> The benchmark uses the adapter `Nebulex.Adapters.Nil`; it is more focused on
> measuring the Nebulex abstraction layer performance rather than a specific
> adapter.
## Contributing

Expand Down
Loading

0 comments on commit 5249c63

Please sign in to comment.