This example illustrates how to integrate Nebulex and Ecto extremely easy, using nebulex_ecto library.
This is the behaviour that we want and is precisely what nebulex_ecto
does:
-
For retrieve-like functions, the idea is to access the cache first, if the requested data is found there, then it is returned right away, otherwise, try to retrieve the data from the repo (database), and if the data is found, then put it into cache so the next time it can be retrieved directly from there.
-
For write functions (insert, update, delete, ...), the idea is to run an eviction logic, which can be delete the data from cache or just replace it.
Check out Nebulex.Ecto.Repo documentation.
The module NebulexEctoExample.CacheableRepo
uses Nebulex.Ecto.Repo
to encapsulate the required logic described above.
As you can see in the config, this module encapsulates the
cache and the repo.
Let's see how it works!
First of all, let's setup the database:
$ mix ecto.create
$ mix ecto.migrate
Now we have the database setup and ready to be used.
Open an interactive Elixir console:
iex -S mix
Let's play a bit:
iex(1)> alias NebulexEctoExample.Person
NebulexEctoExample.Person
iex(2)> alias NebulexEctoExample.CacheableRepo
NebulexEctoExample.CacheableRepo
iex(3)> person = %Person{first_name: "Carlos", last_name: "Bolanos", age: 33}
%NebulexEctoExample.Person{
__meta__: #Ecto.Schema.Metadata<:built, "people">,
age: 33,
first_name: "Carlos",
id: nil,
last_name: "Bolanos"
}
iex(4)> CacheableRepo.get(Person, 1)
15:45:25.700 [debug] QUERY OK source="people" db=2.3ms
SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 WHERE (p0."id" = $1) [1]
nil
iex(5)> NebulexEctoExample.Cache.get({Person, 1})
nil
iex(6)> {:ok, person} = CacheableRepo.insert(person)
15:45:53.386 [debug] QUERY OK db=2.9ms queue=0.1ms
INSERT INTO "people" ("age","first_name","last_name") VALUES ($1,$2,$3) RETURNING "id" [33, "Carlos", "Bolanos"]
{:ok,
%NebulexEctoExample.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 33,
first_name: "Carlos",
id: 1,
last_name: "Bolanos"
}}
iex(7)> CacheableRepo.get(Person, 1)
15:46:14.746 [debug] QUERY OK source="people" db=2.8ms decode=2.8ms
SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 WHERE (p0."id" = $1) [1]
%NebulexEctoExample.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 33,
first_name: "Carlos",
id: 1,
last_name: "Bolanos"
}
iex(8)> NebulexEctoExample.Cache.get({Person, 1})
%NebulexEctoExample.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 33,
first_name: "Carlos",
id: 1,
last_name: "Bolanos"
}
So far seems to be working as expected, let's force some evictions:
iex(9)> changeset = Ecto.Changeset.change person, last_name: "Andres"
#Ecto.Changeset<
action: nil,
changes: %{last_name: "Andres"},
errors: [],
data: #NebulexEctoExample.Person<>,
valid?: true
>
iex(10)> person = CacheableRepo.update!(changeset)
15:47:30.690 [debug] QUERY OK db=8.3ms
UPDATE "people" SET "last_name" = $1 WHERE "id" = $2 ["Andres", 1]
%NebulexEctoExample.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 33,
first_name: "Carlos",
id: 1,
last_name: "Andres"
}
iex(11)> NebulexEctoExample.Cache.get({Person, 1})
nil
iex(12)> person = CacheableRepo.update!(changeset, nbx_evict: :replace)
15:47:54.464 [debug] QUERY OK db=5.5ms decode=0.2ms
UPDATE "people" SET "last_name" = $1 WHERE "id" = $2 ["Andres", 1]
%NebulexEctoExample.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 33,
first_name: "Carlos",
id: 1,
last_name: "Andres"
}
iex(13)> NebulexEctoExample.Cache.get({Person, 1})
%NebulexEctoExample.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 33,
first_name: "Carlos",
id: 1,
last_name: "Andres"
}
It works! You can continue testing more functions!