Developmet
Application with Elixir
        @k1complete
abstract
• Elixirにおいて、本格的なアプリケーション
 を開発するために必要そうな内容を実例に
 そって紹介

• Erlang/OTPの知識を前提とせず頑張ってみ
 ます

• 最終的にはElixir、Erlang/OTPのドキュメン
 トを読む気になれば
Elixirとは
•   José Valimが開発しているErlang VM上に構築された、以下の
    特徴を持つプログラミング言語

    •   現代的な文法

    •   Erlang/OTPとのバイナリ相互互換性(並行指向、耐障害性
        といったErlang/OTPの特徴を全て持つ)。JavaVMでScala
        がしたことをErlang VMで行っているようなもの

    •   メタプログラミング支援機能

        •   プロトコル、レコードによるポリモーフィズム

        •   本物のマクロ
Erlang/OTPとは
•   Open Telecom Platformの略で、EricssonのOTPチームにより母体が開
    発され、Erlangコミュニティにより継続されている、通信アプリケー
    ションに必要とされる機能/非機能要件をサポートするライブラリ

    •   アプリケーション管理(applicationビヘイビア)

    •   汎用サーバ(gen_serverビヘイビア)

    •   状態遷移マシン(gen_fsmビヘイビア)

    •   スーパバイザ(supervisorビヘイビア)

    •   動的アップグレード(releaseハンドラ)

    •   ando more....!
お題: KVSサーバ
1. Key-Valueストア機能の要件(FR)

 1.1.ユーザごとに個別のKV空間を割り当てる。

 1.2.キーはアトムのみで登録と読み出し、削除、変更がで
    きる。

2. それ以外の要件(NFR)

 2.1.マルチユーザにサービスする事

 2.2.クライアントが接続しているサーバがクラッシュして
    も復活する事
全体の構造
      app                   起動と停止

                   クライアントの要求によりsupervisor for
   supervisor            serverの起動を行う

                   1サーバに一つ起動し、サーバクラッシュ
   supervisor
    supervisor       時の再起動と、KVSテーブル保持
    for server
     for server
                   1クライアントに一つ起動し、KVSの操作
  gen_server
  gen_server                 を実施
                   gen_serverのコールバックとして実装する
      ekvs         ETSを利用したKVSモジュール(not OTP)


なるだけOTPモジュールを組み合わせるのがOTP way
ekvs
• ekvsはETS(erlang term strage)を利用し
  て簡単にすます
 defmodule Ekvs do
  def new(id) do
                                       defp keys(_id, :"$end_of_table", a) do
   new(id, [])
                                        a
  end
                                       end
  def new(id, opts) do
                                       defp keys(id, r, a) do
   :ets.new(id, opts)
                                        keys(id, :ets.next(id, r), [r | a])
  end
                                       end
  def put(id, k, v) do
                                       def keys(id) do
   true = :ets.insert(id, {k, v})
                                        keys(id, :ets.first(id), [])
   id
                                       end
  end
                                       def delete(id, k) do
  def get(id, k) do
                                        :ets.delete(id, k)
   case :ets.lookup(id, k) do
                                        id
     [] -> nil
                                       end
     [{^k, v}] -> v
                                      end
   end
  end
gen_serverとは

• 並行指向のElixir(Erlang/OTP)では、単にマルチユー
  ザを束ねるプロセスをサーバとは考えない。

• サーバはそのリソースを管理するものと考える。

• サーバとして共通に必要な部分をビヘイビア(振る舞
  い)としてまとめたのがgen_server

• 固有部分は指定された名前のコールバック関数を実装
  することで、適切にgen_server側から呼び出される。
ekvsサーバの実装
           • elixirではGenServer.Behaviorモジュールがある
                 ため、これをuseしてコールバックを実装する。
defmodule Kvs.Server do
 use GenServer.Behavior                                    def handle_call({:delete, key}, _from, state) do
 def start_link(id, tbl_id) do                              {:reply, :ok, Ekvs.delete(state, key)}
  :gen_server.start_link({:local, id}, __MODULE__, [id,    end
tbl_id], [])                                               def handle_call({:keys}, _from, state) do
 end                                                        {:reply, Ekvs.keys(state), state}
 def init([_id, tbl_id]) do                                end
  :io.format("kvs.server[~p]~n", [tbl_id])                 def terminate(reason, state) do
  {:ok, tbl_id}                                             :error_logger.error_report('#{inspect __MODULE__}
 end                                                      crashed:n#{inspect reason}')
 def handle_call({:get, key}, _from, state) do              :error_logger.error_report('#{inspect __MODULE__}
  {:reply, Ekvs.get(state, key), state}                   snapshot:n#{inspect state}')
 end                                                        :ok
 def handle_call({:put, key, value}, _from, state) do      end
  {:reply, value, Ekvs.put(state, key, value)}            end
 end
supervisorとは
•   子プロセスの(再)起動に責任を持つビヘイビア。プログラマは再起動ポリシーと子プロセス
    のスペックを指定するだけで、異常事態(プロセスのクラッシュ)への対処を行ってくれる

    •   再起動ポリシー

        •   one_for_one 一つが落ちたら、その一つを再起動

        •   one_for_all 一つが落ちたら、全ての子プロセスを再起動

        •   rest_for_all 一つが落ちたら、そのプロセス以降に起動された物を全て再起動

        •   再起動トライ数、最大リトライ秒数...

    •   子プロセスのスペック

        •   restart :permanent(常に再起動), :transient(再起動されない), :temporary(異常終了の
            ときのみ再起動)

        •   type worker/supervisorのいずれか

        •   module_list 子プロセスを実装するモジュール。リリースハンドラはこれを元にアッ
            プグレード時に再起動するプロセスを決定します。
supervisor for
               GenServer
•   init()は以下のとおり
                                    defmodule Kvs.Srv.Sup do
    •   :ets.newでテーブルを作成し、           use Supervisor.Behavior, []
                                     def init(args) do
        それをKvs.Serverへ渡すように           key = args
        childspecを記述。                 tbl_id = :ets.new(key, [:public])
                                      childspec = {key,
                                        {Kvs.Server, :start_link, [key, tbl_id]},
    •   :one_for_oneタイプと
                                         :permanent,
        し、:permanent に存在さ                100000,
                                         :worker,
        せ、:workerタイプの子プロセス               [Kvs.Server]}
                                       {:ok, {{:one_for_one,
    •   サーバの起動は、                         10, # AllowedRestart(count)
                                         100}, # MaxSeconds(sec)
        Kvs.Server.start_linkを呼び出
                                         [childspec]}}
        す。                           end
                                    end
•   以上でsupervisor完了
Supervisor for
               Supervisor
                                       defmodule Kvs.Sup do
•   クライアントからの要求に応じ                      use Supervisor.Behavior, []
                                        def init(_args) do
    て、子プロセスを起動するような                      {:ok, {{:one_for_one,
    supervisorは、                               10, # AllowedRestart(count)
                                               100}, # MaxSeconds(sec)
    supervisor.start_childと                    []}}
                                        end
    supervisor.terminate_childを使う。      def connect(super_ref, key) do
                                         childspec = {key,
    •   connect関数: Kvs.SrvSupの生成          {Kvs.Srv.Sup, :start_link, [key]},
                                          :temporary,
        supervisor.start_child 関数によ
                                           10000, #shutown wait time(milisec)
        り動的に開始                             :supervisor,
                                           [Kvs.Srv.Sup]}
    •   quit関数: Kvs.SrvSupの終了            :supervisor.start_child(super_ref, childspec)
                                        end
        supervisor.terminate_child関数    def quit(super_ref, key) do
                                         :supervisor.terminate_child(super_ref, key)
        呼び出し
                                        end
                                       end
application
                        defmodule KvsApp do
• OTPアプリケーションと           @behaviour :application
                         def start() do
 して start, stop関数を用意      :ok = :application.start(:kvs)
                         end
                         def start(_type, args) do
 する必要がある。                 Kvs.Sup.start_link(:kvs_sup, args)
                         end
                         def stop() do
• 中身はトップレベルの              :application.stop(:kvs)
                         end
 supervisorモジュールであ       def stop(state) do
                          Kvs.Sup.stop(:application_stop, state)
                         end
 るKvs.Sup.start_linkや   end


 Kvs.Sup.stopを呼び出す
 だけ。
mix.exs
                            defmodule MyApp.MixFile do
• ビルド用のmix.exs               use Mix.Project
                             def project do
• ビルド定義もelixirスクリプトで記         [app: :kvs,
                               version: "0.0.1"]
  述。                         end
                             def application do
                              [mod: {KvsApp,[]},
• バージョンやアプリケーションの              applications: [:sasl ],
                               registered: [:kvs],
  メインモジュール、デスクリプ               description: 'Hello Server App']

  ションを指定するだけで、kvs.app        end
                            end
  は自動でmixがビルドしてくれる。

• mix new <app>で<app>用のセッ
  トアップをしてくれる。
github

今回のコードは以下に曝していますので
ご自由にどうぞ。



https://github.com/k1complete/kvs.git

Development app-with-elixir

  • 1.
  • 2.
    abstract • Elixirにおいて、本格的なアプリケーション を開発するために必要そうな内容を実例に そって紹介 • Erlang/OTPの知識を前提とせず頑張ってみ ます • 最終的にはElixir、Erlang/OTPのドキュメン トを読む気になれば
  • 3.
    Elixirとは • José Valimが開発しているErlang VM上に構築された、以下の 特徴を持つプログラミング言語 • 現代的な文法 • Erlang/OTPとのバイナリ相互互換性(並行指向、耐障害性 といったErlang/OTPの特徴を全て持つ)。JavaVMでScala がしたことをErlang VMで行っているようなもの • メタプログラミング支援機能 • プロトコル、レコードによるポリモーフィズム • 本物のマクロ
  • 4.
    Erlang/OTPとは • Open Telecom Platformの略で、EricssonのOTPチームにより母体が開 発され、Erlangコミュニティにより継続されている、通信アプリケー ションに必要とされる機能/非機能要件をサポートするライブラリ • アプリケーション管理(applicationビヘイビア) • 汎用サーバ(gen_serverビヘイビア) • 状態遷移マシン(gen_fsmビヘイビア) • スーパバイザ(supervisorビヘイビア) • 動的アップグレード(releaseハンドラ) • ando more....!
  • 5.
    お題: KVSサーバ 1. Key-Valueストア機能の要件(FR) 1.1.ユーザごとに個別のKV空間を割り当てる。 1.2.キーはアトムのみで登録と読み出し、削除、変更がで きる。 2. それ以外の要件(NFR) 2.1.マルチユーザにサービスする事 2.2.クライアントが接続しているサーバがクラッシュして も復活する事
  • 6.
    全体の構造 app 起動と停止 クライアントの要求によりsupervisor for supervisor serverの起動を行う 1サーバに一つ起動し、サーバクラッシュ supervisor supervisor 時の再起動と、KVSテーブル保持 for server for server 1クライアントに一つ起動し、KVSの操作 gen_server gen_server を実施 gen_serverのコールバックとして実装する ekvs ETSを利用したKVSモジュール(not OTP) なるだけOTPモジュールを組み合わせるのがOTP way
  • 7.
    ekvs • ekvsはETS(erlang termstrage)を利用し て簡単にすます defmodule Ekvs do def new(id) do defp keys(_id, :"$end_of_table", a) do new(id, []) a end end def new(id, opts) do defp keys(id, r, a) do :ets.new(id, opts) keys(id, :ets.next(id, r), [r | a]) end end def put(id, k, v) do def keys(id) do true = :ets.insert(id, {k, v}) keys(id, :ets.first(id), []) id end end def delete(id, k) do def get(id, k) do :ets.delete(id, k) case :ets.lookup(id, k) do id [] -> nil end [{^k, v}] -> v end end end
  • 8.
    gen_serverとは • 並行指向のElixir(Erlang/OTP)では、単にマルチユー ザを束ねるプロセスをサーバとは考えない。 • サーバはそのリソースを管理するものと考える。 • サーバとして共通に必要な部分をビヘイビア(振る舞 い)としてまとめたのがgen_server • 固有部分は指定された名前のコールバック関数を実装 することで、適切にgen_server側から呼び出される。
  • 9.
    ekvsサーバの実装 • elixirではGenServer.Behaviorモジュールがある ため、これをuseしてコールバックを実装する。 defmodule Kvs.Server do use GenServer.Behavior def handle_call({:delete, key}, _from, state) do def start_link(id, tbl_id) do {:reply, :ok, Ekvs.delete(state, key)} :gen_server.start_link({:local, id}, __MODULE__, [id, end tbl_id], []) def handle_call({:keys}, _from, state) do end {:reply, Ekvs.keys(state), state} def init([_id, tbl_id]) do end :io.format("kvs.server[~p]~n", [tbl_id]) def terminate(reason, state) do {:ok, tbl_id} :error_logger.error_report('#{inspect __MODULE__} end crashed:n#{inspect reason}') def handle_call({:get, key}, _from, state) do :error_logger.error_report('#{inspect __MODULE__} {:reply, Ekvs.get(state, key), state} snapshot:n#{inspect state}') end :ok def handle_call({:put, key, value}, _from, state) do end {:reply, value, Ekvs.put(state, key, value)} end end
  • 10.
    supervisorとは • 子プロセスの(再)起動に責任を持つビヘイビア。プログラマは再起動ポリシーと子プロセス のスペックを指定するだけで、異常事態(プロセスのクラッシュ)への対処を行ってくれる • 再起動ポリシー • one_for_one 一つが落ちたら、その一つを再起動 • one_for_all 一つが落ちたら、全ての子プロセスを再起動 • rest_for_all 一つが落ちたら、そのプロセス以降に起動された物を全て再起動 • 再起動トライ数、最大リトライ秒数... • 子プロセスのスペック • restart :permanent(常に再起動), :transient(再起動されない), :temporary(異常終了の ときのみ再起動) • type worker/supervisorのいずれか • module_list 子プロセスを実装するモジュール。リリースハンドラはこれを元にアッ プグレード時に再起動するプロセスを決定します。
  • 11.
    supervisor for GenServer • init()は以下のとおり defmodule Kvs.Srv.Sup do • :ets.newでテーブルを作成し、 use Supervisor.Behavior, [] def init(args) do それをKvs.Serverへ渡すように key = args childspecを記述。 tbl_id = :ets.new(key, [:public]) childspec = {key, {Kvs.Server, :start_link, [key, tbl_id]}, • :one_for_oneタイプと :permanent, し、:permanent に存在さ 100000, :worker, せ、:workerタイプの子プロセス [Kvs.Server]} {:ok, {{:one_for_one, • サーバの起動は、 10, # AllowedRestart(count) 100}, # MaxSeconds(sec) Kvs.Server.start_linkを呼び出 [childspec]}} す。 end end • 以上でsupervisor完了
  • 12.
    Supervisor for Supervisor defmodule Kvs.Sup do • クライアントからの要求に応じ use Supervisor.Behavior, [] def init(_args) do て、子プロセスを起動するような {:ok, {{:one_for_one, supervisorは、 10, # AllowedRestart(count) 100}, # MaxSeconds(sec) supervisor.start_childと []}} end supervisor.terminate_childを使う。 def connect(super_ref, key) do childspec = {key, • connect関数: Kvs.SrvSupの生成 {Kvs.Srv.Sup, :start_link, [key]}, :temporary, supervisor.start_child 関数によ 10000, #shutown wait time(milisec) り動的に開始 :supervisor, [Kvs.Srv.Sup]} • quit関数: Kvs.SrvSupの終了 :supervisor.start_child(super_ref, childspec) end supervisor.terminate_child関数 def quit(super_ref, key) do :supervisor.terminate_child(super_ref, key) 呼び出し end end
  • 13.
    application defmodule KvsApp do • OTPアプリケーションと @behaviour :application def start() do して start, stop関数を用意 :ok = :application.start(:kvs) end def start(_type, args) do する必要がある。 Kvs.Sup.start_link(:kvs_sup, args) end def stop() do • 中身はトップレベルの :application.stop(:kvs) end supervisorモジュールであ def stop(state) do Kvs.Sup.stop(:application_stop, state) end るKvs.Sup.start_linkや end Kvs.Sup.stopを呼び出す だけ。
  • 14.
    mix.exs defmodule MyApp.MixFile do • ビルド用のmix.exs use Mix.Project def project do • ビルド定義もelixirスクリプトで記 [app: :kvs, version: "0.0.1"] 述。 end def application do [mod: {KvsApp,[]}, • バージョンやアプリケーションの applications: [:sasl ], registered: [:kvs], メインモジュール、デスクリプ description: 'Hello Server App'] ションを指定するだけで、kvs.app end end は自動でmixがビルドしてくれる。 • mix new <app>で<app>用のセッ トアップをしてくれる。
  • 15.