Redisライブラリ作りました
2016/03/21 Elixir MeetUp #2 in Drecom
@hagiyat
自己紹介
- Tomohiro Hagiya
- @hagiyat
- 最近マネーフォワードにJoinしました
- 最近はほぼRailsな毎日です
- 前職は株式会社nanapi
- 半年くらい、ほぼ毎日Elixir書いてました
今日の発表について
タイトル通りですが、ElixirでRedisを扱うライブラリを作った話をします
まずお詫び
前職でやっていた話なので、どういうプロダクトのどういうところで使っていたのか、みたいな話は一切しません
ElixirのRedis事情
Awesome Elixirによると
-
exredis - Redis client for Elixir.
-
red - Persist relationships between objects in Redis, in a graph-like way.
-
redo - Heroku's pipelining redis client for erlang.
-
yar - Yet another Redis client for Elixir.
でなければ
ElixirのRedis事情
実際のところ、機能的にはexredis一択
- いいところ
- 充実のAPI定義
- 一通りの機能が揃っている(transaction/pubsubなど)
- そうでもないところ
- コネクションプーリング機能がないので、毎回clientをstart_linkしてコネクションを作らないといけないので大変
- transaction/pipeline内ではAPIモジュールは使えない(気がする。Listでコマンドと引数を渡したような?)
ElixirのRedis事情
ちょっとプロダクションコードでガシガシ書くにはつらいかも・・・
なければ作るしかない
-
eredisx - Eredisx is a library for writing in Elixir seems syntax an API of Redis.
Redis Weekly
というところで紹介していただいたみたいです
(Elixir MeetUp #1 の@ohrdevの発表を見ていて、今まさにそのへん作ってるよ・・・!と思ってました)
eredisxの特徴
- Repo概念の導入
- APIモジュール & APIジェネレーター
- transaction/piplineをいい感じで書ける
- redis-object(ruby)風なModel定義
データアクセスライブラリ for Redisのスタンダードっぽくなるように考えました
eredisxの特徴
Repo概念の導入
- EctoっぽくConnectionを
Repo
という単位で管理できるようにした
- かつ、各Repoごとにコネクションプーリングできるようにした
- databaseごとに(もちろん別ホストでも)Repoがきれたりして便利かなーと思ってます
eredisxの特徴
APIモジュール
ex) Eredisx.Api.Hash.hget
def hget(key, field, options \\ [])
summary: Get the value of a hash field
options:
• repo: If you want to specify a non-default connection of redis, set the
name of the connection of redis.
@since: 2.0.0
- clientは?と思われるかもしれませんが、指定しなければデフォルトのRepoを使ってAPIアクセスします
- 説明で書いてある通り、optionsにrepoを指定することで別Repoを使用できます
eredisxの特徴
APIジェネレーター
ex) Eredisx.Api.Hash.hget
generate_apis """
HGET key field
summary: Get the value of a hash field
since: 2.0.0
"""
- 要はMacroを使ったコードジェネレータです
- 前ページの関数はこの仕組みで生成されたものです
-
redis-cli help @hash
で出力された文字列をペタっと貼るだけ
eredisxの特徴
transaction/piplineをいい感じで書ける
AnyRepo.pipeline do
key = "testvalue"
Eredisx.Api.String.set(key, 100)
Eredisx.Api.String.incr(key)
Eredisx.Api.String.set(key, "hogefuga")
end
- やっぱりdo-endで書きたい(個人の感想です)のでそうした
- (Redisに詳しくない方のための補足) 通常は1APIごとに1リクエストが発行されますが、pipelineであれば複数APIの実行が1リクエストで処理されます
eredisxの特徴
redis-object(ruby)風なModel定義
defmodule User.Details do
use Eredisx.Model.Hash,
repo: AnyRepo, # デフォルト以外のRepo指定ができる
keyformat: "user:#id#:details",
sequence: "user:id",
schema: [
nickname: "",
latest_login_at: Timex.Time.now(:secs),
updated_at: Timex.Time.now(:secs),
] # この定義がdefstructで展開される
end
User.Details.find("user:1:details") # %User{}
eredisxの特徴
redis-object(ruby)風なModel定義
- model単位でrepoの指定ができます
- シーケンシャルキーの発行を
keyformat
sequence
の定義によりある程度意識しなくてすむ
- Hash型の場合、schema定義によりStructでのデータのやりとりができるようになります
- find/saveといった、あまりRedisAPIを意識せずにすむような関数を用意しました
特徴としてはこんな感じです
もう少しだけ、狭いスコープの細かい話をします
APIジェネレーター
- repoやtransactionの概念を取り込むことを考えた時、最初はeredisxなどというものは作るつもりがなく、exredisにpull requestして拡張しようと思っていた
- しかし、根本な部分を変えすぎるパッチになってしまったので、やっぱり新しく作ろうと思った
- しかし・・APIは使いたいけど自分で全部実装してかつテストも書かなきゃいけないなんてダルすぎる
そうだ
Macroでコード生成して、APIを網羅すればいいじゃない

APIジェネレーター
- ドキュメントからコードを生成できるというところがセールスポイントのひとつです
- Elixirなら、コードジェネレータの結果をコンパイル済みのライブラリとして提供できる
- つまり、昨今のしっかり整備されているAPIであれば、スクレイピングでもしてドキュメントをとってくれば同じことができる
- バージョンアップへの追従など、もしかしたらすごくメンテナンスコストの低い仕組みになるんじゃないか!?
みなさまのご意見ご感想、お待ちしております
切り捨てたポイント
- Pub/Sub
- pubsubするためだけの専用のライブラリあるし、そっちと組み合わせて使った方がいいと思います
- Scripting
- あとはflushdbとかmonitorとか、redis-cliでしか使わないだろうと思ったものは入れないつもり

- 実はまだ完成していません
- あたかも完成品みたいな体の発表にしてしまったことをお詫びします
できてないポイント
- テストないです
- 前述のMacroのテストどうやるんだろう・・っていうところから何も書けないままここまで来てしまいました
- Repoの仕組みはまだ未完成
- このライブラリの生い立ち的に、とあるプロダクトにベッタリだった実装を引き剥がしたライブラリなので、Repoの仕組みは後付けだったのです
- 現状の地獄のようなMacroのコードをきれいにする
- Hexに登録
まとめ
- Eredisxではcontributorを募集しています
- Eredisxではライブラリアイコン・ロゴを募集しています
- Eredisxではsuponsorを募集しています
- Elixirたのしい