このAdvent Calendarで度々話題になって(して)いる、Riak Source Code Reading。
Erlang/OTPを全く知らない人にとっては、Erlangの文法はもちろん、ファイルやディレクトリ構成など未知のことだらけだと思う。
「Erlang/OTP何それ美味しいの」状態から脱却し、Riakのコードを楽しく読むために必要な知識を書いていこう。
用語について
RiakはErlang/OTPで作成されている。
Erlangとは、エリクソン社が開発したプログラミング言語。
OTPとは、システムを作るフレームワークみたいなものだ。
RubyとRuby on Railsの関係を想像する人もいるかもしれないが、Erlang/OTPはそれより相互が依存している。
故に、正式にはErlang/OTPみたいに”ひとつ”として呼ばれることが多い。
そのOTPとは何なのか。
正式にはOpen Telecom Platformという名前。
飛行機本には、"大規模、耐障害、分散といった特徴を持つアプリケーションを作るためのライブラリや手続きの集合体"との解説がある。
なんのことだかよくわからないが、結局のところ、必要とされる名前の関数を、中身空っぽでもいいので書いていくと、いつの間にかエラーレポートやホットコードスワッピングなど高度な機能を持ったアプリケーションが作れるという便利システム、といったところか。
Riakの可用性、耐障害性を実現するのに、このOTPは多大な貢献をしている。
で、Erlangにはgemのようなものは無い。
CEANっていうPerlで言うCPANのErlang版があるっちゃあるんだけど、活発な更新はされていないようだ。
そこで、Bashoが作っているrebarというOSSだ。
rebarは、githubなどにホストされている有用なライブラリの依存関係やErlang VMの設定などを管理し、バイナリのリリースを行うまで面倒を見てくれる。
もちろんRiakや関連プロジェクトでも使われている。
##とりあえずErlangアプリケーションを作ってみる
ただ説明を読むより、手を動かして小さなアプリケーションを作ってみたほうが良い。
まずはrebarについての理解を深めるため、小さなErlangアプリケーションを作ってみよう。
安心してほしい。設定ファイルを除き、Erlangは一行も書かない。
流れを大雑把に書くと、
- rebarをpullして使えるようにする
- rebarをアプリケーションルートにコピー
- myappという名前のアプリケーションを作成
- rebar.configを作成し、relディレクトリが見れるようにする
- relディレクトリを作成し、その中でmynodeという名前のノードを作成
- reltool.configを修正
- バイナリを生成
となる。
では、やってみよう。
1. rebarをpullして使えるようにする
[mymac@localhost devel]$ git clone https://github.com/basho/rebar.git
[mymac@localhost devel]$ cd rebar
[mymac@localhost rebar]$ ./bootstrap
2. rebarをアプリケーションルートにコピー
[mymac@localhost rebar]$ cd ..
[mymac@localhost devel]$ mkdir myriak
[mymac@localhost devel]$ cp rebar/rebar myriak
3. myappという名前のアプリケーションを作成
[mymac@localhost myriak]$ cd myriak
[mymac@localhost myriak]$ ./rebar create-app appid=myapp
==> myriak (create-app)
Writing src/myapp.app.src
Writing src/myapp_app.erl
Writing src/myapp_sup.erl
4. rebar.configを作成し、relディレクトリが見れるようにする
[mymac@localhost myriak]$ echo '{sub_dirs, ["rel"]}.' > rebar.config
5. relディレクトリを作成し、その中でmynodeという名前のノードを作成
[mymac@localhost myriak]$ mkdir rel
[mymac@localhost myriak]$ cd rel
[mymac@localhost rel]$ ls
[mymac@localhost rel]$ ../rebar create-node nodeid=mynode
==> rel (create-node)
Writing reltool.config
Writing files/erl
Writing files/nodetool
Writing files/mynode
Writing files/sys.config
Writing files/vm.args
Writing files/mynode.cmd
Writing files/start_erl.cmd
Writing files/install_upgrade.escript
6. reltool.configを修正
[mymac@localhost rel]$ mv reltool.config reltool.config.org
[mymac@localhost rel]$ sed -e 's/{incl_cond, include}/{incl_cond, include}, {lib_dir, ".."}/' reltool.config.org > reltool.config
7. バイナリを生成
[mymac@localhost rel]$ ../rebar generate
==> rel (generate)
では、Erlangを一行も書かずに生成したバイナリを使ってみよう。
[mymac@localhost rel]$ mynode/bin/mynode
Usage: mynode {start|start_boot <file>|foreground|stop|restart|reboot|ping|console|getpid|console_clean|console_boot <file>|attach|remote_console|upgrade}
[mymac@localhost rel]$ mynode/bin/mynode start
[mymac@localhost rel]$ mynode/bin/mynode ping
pong
[mymac@localhost rel]$ mynode/bin/mynode stop
ok
すばらしい!rebarすばらしい!
もちろんこれだけだと何の機能も持っていないので、アプリケーションを作る場合はこの状態をベースに独自のコードを追加していくことになる。
そのコードも、OTPの設計原則に則って書くことで、高度で実用的な機能を簡単に実現できるのだ。
OTPについてちょこっとだけ
以下のコードを見てほしい。
-module(myapp_server).
-behavior(gen_server).
-export([start/0, stop/0]).
-export([init/1, handle_call/3,handle_cast/2, handle_info/2, terminate/2, code_change/3]).
start() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
stop() ->
gen_server:call(?MODULE, stop).
init([]) ->
{ok, Riakc} = riakc_pb_socket:start_link("127.0.0.1", 8087),
{ok, Riakc}.
handle_call(_Request, _From, State) ->
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
OTPを活用するアプリケーションを書く場合、所定の関数を実装する必要がある。
behaviorと書かれた部分でOTPアプリケーションの種類を指定する。
これは代表的なbehavior、gen_serverだ。
この例では、init関数のみ独自のコードを書きかけている。(本当は格好いいアプリケーションを書き上げて今日のカレンダーでドヤ顔したかった...)
もし必要な関数が実装されていなければ、コンパイラはここを見てエラーを出力する。
関数名(引数) ->
なんか処理,
なんか処理,
ok.
みたいになっているだろう。
この関数名についてだが、引数の数が異なる関数は、名前が同じでも異なる関数として扱われる。
exportの部分でpublicにする関数を、関数名/引数の数 で指定している。
引数の数はarityと呼ばれ、Erlangでは重要なものなので注意してほしい。
最後に評価された値が返るので、この関数を呼ぶとokアトムが返ることになる。
まとめ
もはやRiak関係なくね...?
acknowledgements
この記事の内容のほぼ全てを@itawasaさんに教えてもらいました。
また、gen_serverとgen_fsmを中心に、behaviorに対する僕の誤解を解いてくれたのもitawasaさんです。
ありがとうございます。