Comredis
Iuri Fernandes
Redis, Elixir, Ruby, Metaprogramming,
Testing and other Buzzwords
Ruby
require "redis"
redis = Redis.new
redis.set("hello ", "world") # => "OK"
redis.get("hello") # => "world"
Connection
management
Authentication
Communication
(Request-response)
Client
RESP
Server
“Requests are sent from the client to
the Redis server as arrays of strings
representing the arguments of the
command to execute.”
–Redis documentation
RESP
(REdis Serialization Protocol)
Ruby
# Get the value of a key.
#
# @param [String] key
# @return [String]
def get(key)
synchronize do |client|
client.call([:get, key])
end
end
https://github.com/redis/redis-rb/blob/master/lib/
redis.rb
Elixir
{:ok, conn} = Redix.start_link
# => {:ok, #PID<0.310.0>}
Redix.command(conn, ~w(SET hello world))
# => {:ok, "OK"}
Redix.command(conn, ~w(GET hello))
# => {:ok, "world"}
Elixir
Redix.pipeline(conn, [
~w(INCR foo), ~w(INCR foo), ~w(INCR foo 2)
])
#=> {:ok, [1, 2, 4]}
Elixir
Redix.pipeline(conn, [
~w(INCR foo), ~w(INCR foo), ~w(INCRBY foo 2)
])
#=> {:ok, [1, 2, 4]}
Ruby
redis = Redis.new
redis.set("hello ", "world") # => "OK"
redis.get("hello") # => "world"
Elixir
{:ok, conn} = Redix.start_link
# => {:ok, #PID<0.310.0>}
Redix.command(conn, ~w(SET hello world))
# => {:ok, "OK"}
Redix.command(conn, ~w(GET hello))
# => {:ok, "world"}
Elixir
{:ok, conn} = Redix.start_link
# => {:ok, #PID<0.310.0>}
Redix.command(conn, set("hello", "world")))
# => {:ok, "OK"}
Redix.command(conn, get("hello"))
# => {:ok, "world"}
Elixir
{:ok, conn} = Redix.start_link
# => {:ok, #PID<0.310.0>}
conn |> Redix.command(set("hello", "world")))
# => {:ok, "OK"}
conn |> Redix.command(get("hello"))
# => {:ok, "world"}
Elixir
defmodule Commands do
def get(key) do
~w(GET #{key})
end
def set(key, value) do
~w(SET #{key} #{value})
end
# ...
end
Bad idea!
lib/redis.rb =>
2730

LOC*
* Includes a lot of documentation
* Handles responses
* It does not support all the commands
Not for the redis-rb
Imagine if I could generate all these
functions automatically!
You can!
Metaprogramming
FTW!
Better, at compile
time!
{
...
"GET": {
"summary": "Get the value of a key",
"complexity": "O(1)",
"arguments": [
{
"name": "key",
"type": "key"
}
],
"since": "1.0.0",
"group": "string"
},
...
}
https://github.com/antirez/redis-doc/blob/master/commands.json
Parser
(Poison)
Commands
JSON
specification
Command
structured
data
Elixir
macros
Functions
Macro rules
Rule 1: Don’t Write Macros
Rule 2: Use Macros
Gratuitously
Elixir
defmodule Comredis do
use Comredis.Command.Generator
...
end
Abstract Syntax Tree
Expression
quoteunquote
1 + 1
quoteunquote
1 + 1
{:+, [context: Elixir, import: Kernel], [1, 1]}
Elixir
defmodule Comredis.Command.Generator do
defmacro __using__(_options) do
for command <- FileReader.load do
generate(command)
end
end
defp generate(command = %Command{}) do
quote do
@doc unquote doc(command)
unquote bodies(command,
Argument.split_options(command.arguments))
end
end
...
end
Elixir
defp bodies(command, {required_args, []}) do
args = argument_names(required_args)
quote do
def unquote(command.canonical_name)(unquote_splicing(args)) do
List.flatten [unquote(command.name), unquote_splicing(args)]
end
end
end
Elixir
defp bodies(%Command{canonical_name: :get, name: "GET"},
{[%Argument{canonical_name: :key}], []}) do
args = [{:key, [], Elixir}]
quote do
def unquote(command.canonical_name)(unquote_splicing(args)) do
List.flatten [unquote(command.name), unquote_splicing(args)]
end
end
end
defp bodies(%Command{canonical_name: :get, name: "GET"},
{%Argument{canonical_name: :key}, []}) do
quote do
def get(key) do
List.flatten ["GET", key]
end
end
end
How to test it?
Don’t test code generation, but the
generated code
Write tests for each function?
No, automatically test all of them
Property-based testing
Generator: random arguments compliant to
command arguments
Property: commands have a valid syntax
Use a Redis server as a test oracle
github.com/parroty/excheck (triq)
What is missing?
Type checking to use Dialyzer
More documentation
Something else?
Who uses similar ideas?
Elixir - unicode representation
ex2ms - ETS match expressions
Questions?
@fqiuri
github.com/iurifq/comredis

Comredis