Elixir <-> Redis datastructure adapter
In your mix.exs
file:
def deps do
[{:ex_sider, "~> 0.1"},
# the following is only needed if using a Redix pool:
{:poolboy, "~> 1.5"},
{:redix, ">= 0.0.0"}]
end
In your config file:
config ex_sider,
redis_adapter: MyApp.RedixPool # currently the only supported adapter, see below
# also make sure to configure the redis adapter correctly
This can be used (potentially, if necessary) with different Redis adapters, but for now I'll stick with Redix. From the example we can create a new RedixPool e.g. like so:
# Copied from github.com/whatyouhide/redix README.md
defmodule MyApp.RedixPool do
use Supervisor
@redis_connection_params host: "localhost", password: ""
def start_link do
Supervisor.start_link(__MODULE__, [])
end
def init([]) do
pool_opts = [
name: {:local, :redix_poolboy},
worker_module: Redix,
size: 10,
max_overflow: 5,
]
children = [
:poolboy.child_spec(:redix_poolboy, pool_opts, @redis_connection_params)
]
supervise(children, strategy: :one_for_one, name: __MODULE__)
end
def command(command) do
:poolboy.transaction(:redix_poolboy, &Redix.command(&1, command))
end
def pipeline(commands) do
:poolboy.transaction(:redix_poolboy, &Redix.pipeline(&1, commands))
end
end
We now update our ex_sider
config with the correct module name (see above), and also make sure that the RedixPool is started when we start our Application:
defmodule MyApp do
use Application
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
# ...
supervisor(MyApp.RedixPool, [[]]),
# ...
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
Finally, after this setup, we can use the ex_redis
modules like any normal Map, Set or List, e.g.:
(Actually, take this with a grain of salt: Since this is an ongoing effort, interfaces might be incomplete - but please request specific improvements or contribute!)
redis_set = RedisSet.new("my-set-name")
# by default, ex_sider uses a 'binary' mode, where it pipes all
# terms given to it into :erlang.term_to_binary/1, and all terms
# that it retrieves through :erlang.binary_to_term/1
data = ["surprisingly", :we_can_store, "all kinds of data!!!", 1, 1, 1]
# we can use for comprehensions:
for x <- data, into: redis_set, do: x
# and any kind of Enum operation, e.g.:
Enum.to_list(redis_set)
# => ["surprisingly", :we_can_store, "all kinds of data!!!", 1]
# note the missing 1's because we are using a RedisSet
Note that this functionality might be made more implicit in the future by implementing the Access, Enumerable and Collectable functionality
redis_hash = RedisHash.new("my-hash-name") # initializes the redishash with the korrekt key
RedisHash.push(redis_hash, %{"some_value" => 123, "abc" => :abc}) # note: keys must be binaries!
# => :ok
RedisHash.pull(redis_hash)
# => %{"some_value" => 123, "abc" => :abc}
Essentially, this is a RedisHash under the hood, but caches data and changes locally. It can be used to remotely cache elixir maps with simple push/pull sync semantics and no strategy for conflict resolution. The use case for this is to store (also Erlang-Node independant) the state of a process that will only ever be existing once in the cluster, but might be restarted often.
redis_cache = RedisCache.new("my-hash-name") # pulls the existing state from the repo automatically if any
redis_cache = RedisCache.merge(redis_cache, %{"some" => :values, "that_i_want" => "to store"}) # does a local caching
RedisCache.unpushed_changes?(redis_cache)
# => true
redis_cache = RedisCache.push(redis_cache) # pushes the local changes
RedisCache.unpushed_changes?(redis_cache)
# => false
Mutability - All datastructures implemented here are mutable, that means, that every operation that changes any part of them (i.e. writes data) will change for all parts of the application that have a reference to this datastructure. This is because we actually only implement a thin adapter layer based on Elixir Protocols, that interface with redis in order to store data.
Binary Data - Any data will, by default, be stored as an erlang term that is being converted to binary beforehand. That means that - in case you access Redis without ex_sider
- that you will have to call :erlang.binary_to_term
on anything that you retrieve from it. If that is not an option for you, simply disable binary mode when initialising the datastructure:
# to disable binary mode (only values that are binaries can be used then, like e.g. elixir strings)
redis_set = RedisSet.new("my-set-name", binary_mode: false)
This Project - This project is supposed to be a helper to make interfacing with Redis simpler. It is by no means: complete, perfectly documented or otherwise done. Any help is appreciated, just fork & PR, create issues etc. Business as usual.
bien sûr.