Elixir + Phoenixでリアルタイムアプリケーションを作ってみるメモ


最近話題の(?)Elixir + Phoenixを現在関わっているプロジェクトの検証・評価目的に使ってみます。
なお自分自身全くの初心者なので初心者向けの内容になる予定です。

### Elixirとは
![logo[1].png](https://qiita-image-store.s3.amazonaws.com/0/47983/ed6e786a-ab12-d3d4-88eb-4d6b2ffee5bf.png)

[Elixir公式サイト](http://elixir-lang.org/)から引用します

>Elixir is a dynamic, functional language designed for building scalable and maintainable applications.
>Elixir leverages the Erlang VM, known for running low-latency, distributed and fault-tolerant systems, while also being successfully used in web development and the embedded software domain.

- **Erlang VM(BEAM)**上で動作する言語
- スケーラブルでメンテナンス性が高い
- 関数型言語
- 最新バージョンは1.2 (2015/01/27現在)
- 最初のリリースは2012年のためまだ若い言語
- 開発者はRailsのコア開発者でもあるJosé Valim
- Erlangのいいとこを取り入れつつmixなどのビルドシステムも装備

なんか慣れない単語がいっぱい出てきました。関数型って時点でそこそこ威圧感ありますがそもそも**Eralng VM**とは何か。おそらくErlangという言語のVirtual Machine（JVMのようなもの？）のことのようですがよくわかってないのでErlangを調べてみましょう。

## Erlangとは

![Erlang_logo[1].png](https://qiita-image-store.s3.amazonaws.com/0/47983/9b15dba9-068c-85ab-61b9-2cd5a0d6bcbc.png)


同じく[Erlang公式サイト](http://www.erlang.org/)から引用します。

>Erlang is a programming language used to build massively scalable soft real-time systems with requirements on high availability. 
>Some of its uses are in telecoms, banking, e-commerce, computer telephony and instant messaging. Erlang's runtime system has built-in support for concurrency, distribution and fault tolerance.


- 可用性の高いリアルタイムシステムを作れる
- スケーラブル
- 電話局や銀行、電子商取引などで使われてる
- インスタントメッセージなどにも使われている
- 並列、分散、耐障害性をランタイムレベルでサポート
- 最新バージョンは18
- 30年近くの歴史を持つ言語
- LINE, WhatsApp, Facebook, dwangoなどで採用実績あり

なんとなく分かりました。Erlangはリアルタイムシステムを作るのに向いててランタイムレベルで中々高度な機能があるみたいです。それじゃ次は**Phoenix**について調べてみます。

## Phoenixとは

![phoenix_large[1].png](https://qiita-image-store.s3.amazonaws.com/0/47983/924c7011-ca74-cae2-1314-dbea4b3f2ff3.png)


同じく[Phoenix公式サイト](http://www.phoenixframework.org/docs/overview)から引用します。

>Phoenix is a web development framework written in Elixir which implements the server-side MVC pattern. 
>Many of its components and concepts will seem familiar to those of us with experience in other web frameworks like Ruby on Rails or Python's Django.
>

- Elixir製のフレームワーク
- MVCパターンで書ける
- RailsやDjangoのようなフレームワークに影響を受けてる

Elixirで書かれているフレームワークらしいです。まとめるとこういう包含関係になっています。

![elixir_phoenix.png](https://qiita-image-store.s3.amazonaws.com/0/47983/04b87bc3-0fd2-72c9-c270-f64df3c0f7a2.png)

依存関係で言うとPhoenix → Elixir → Erlang VMでJavaにおけるJVMとScalaとPlayの関係っぽいですね(?)
Elixir、Phoenix、Erlangについてはなんとなく分かったので次に環境を作ってインストールしていきます。

## 環境

- Ubuntu 14.04 Trusty

## インストール

[Elixir公式サイト](http://elixir-lang.org/install.html)及び[Phoenix公式サイト](http://www.phoenixframework.org/docs/installation)を参考にインストールを進めていきます。

- Erlangリポジトリを追加

```bash
wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb 
dpkg -i erlang-solutions_1.0_all.deb
apt-get update
```

- elangとelixirをインストール

```bash
apt-get install esl-erlang elixir
```

-  mixでhexパッケージマネージャとphoenix本体をインストールします

mixはelixirにおけるビルドツールでコンパイルやテストや依存性管理などのタスクを管理出来るようになります

```bash
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
mix local.hex
mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez
```

- projectを作成します

```bash
mix phoenix.new example
```

- 依存パッケージのインストール

```bash
cd example
npm install
```

node.jsが入って無ければインストール

```
git clone git://github.com/creationix/nvm.git ~/.nvm
echo "source ~/.nvm/nvm/sh" >> ~/.bashrc
source ~/.bashrc
nvm install stable
nvm use stable
nvm alias default stable
```

- デフォルトの起動ポートは4000番だけど変えたい場合は`config/dev.exs`を編集

```diff
- http: [port: 4000],
+ http: [port: 80],
```

- phoenixサーバ起動

```bash
mix phoenix.server
```

- 80番にアクセスしWelcome to Phoenix!と表示されればHello WorldはOK

とりあえずインストールは以上で一通り終わったのでアプリケーションを作っていきます。

## Phoenix の基本

アプリケーションを作る前にPhoenixで必要になる概念をいくつか説明します

### Endpoint

- すべてのリクエストを処理しRouterに渡す役割を持ってる
- リクエストに適用するためのplug(web開発でよくある処理をまとめたmodule。HTTPサーバ)の基本セットがある
- 指定されたRouterにリクエストを振り分ける

### Router

- リクエストをパースし指定されたコントローラー、アクション、必要であればパラメータも渡す
- リソースに対してパスを生成するヘルパーがある
- リクエストを渡すpipelineを定義出来る
- pipelineというのは`plug`をまとめて特定のRouteに適用させたりするためのグループ化の方法

### Controllers

- リクエストを処理するアクションを定義
- データストアとやりとりしたりそれをviewに渡したりリダイレクトしたりする

### Views

- Templateをレンダリングする
- プレゼンテーション層として動作
- ヘルパーを定義してTemplateなどで使うことが出来る

### Templates

- いわゆるテンプレート。Viewを埋め込む。

### Channels

- リアルタイムアプリケーションを作るためのsocketを管理してくれる
- Controllerと似ているが、Controllerはpersistentな双方向通信を扱える

### PubSub

- Channelレイヤーの下にありクライアントにpublishしたtopicをsubscribeさせることが出来る
- サードパーティのPubSub実装を抽象(Redisとか)

## アプリケーションの作成

定番のチャットアプリケーションを作っていきます
まず単体でチャット出来るアプリケーションを作り、後程RedisでPubSub機能を代替したスケール可能なバージョンを作っていきます。

### 単体構成

- プロジェクトの作成

```bash
mix phoenix.new redis_pubsub_sample
```

- 起動ポートを環境変数から取得するよう設定

`config/dev.exs`

```diff
- http: [port: 4000],
+ http: [port: System.get_env("PORT")],
```

- [Channel](http://www.phoenixframework.org/docs/channels)のルーティング設定

`web/channels/user_socket.ex`

```erlang
channel "rooms:*", RedisPubsubSample.RoomChannel
```
- Channelの作成

`web/channels/room_channel.ex`

```erlang
defmodule RedisPubsubSample.RoomChannel do
  use Phoenix.Channel

  def join("rooms:lobby", _auth_msg, socket) do
    {:ok, socket}
  end
  def join("rooms:" <> _private_rood, _auth_msg, socket) do
    {:error, %{reason: "unauthorized"}}
  end

  def handle_in("send_message", %{"message" => message}, socket) do
    broadcast! socket, "receive_message", %{message: message}
    {:noreply, socket}
  end
end

```

- Templateの書き換え

`web/templates/layout/app.html.eex`

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Phoenix PubSub Sample</title>
    <link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
    <style>
      .received-message {
        color: #555;
        font-style: italic;
        padding: 10px 0 5px;
        border-bottom: 1px solid #ddd;
      }
      .received-message:first-child {
        font-size: 2em;
      }
    </style>
  </head>

  <body>
    <div class="container" role="main">
      <form class="form-inline">
        <div class="form-group">
          <input type="text" class="form-control" id="input-send-message">
        </div>
        <button type="submit" class="btn btn-default">Send</button>
      </form>
      <div id="received-messages">
      </div>
    </div>

    <script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
    <script src="<%= static_path(@conn, "/js/app.js") %>"></script>
  </body>
</html>
```

- JSの書き換え

`web/static/js/app.js`

```js
import {Socket} from "phoenix"

var socket = new Socket("/socket");
socket.connect();
var channel = socket.channel("rooms:lobby", {});
channel.join();

$("form").submit(function(e) {
    e.preventDefault();
    channel.push("send_message", {message: $("#input-send-message").val()});
    $("#input-send-message").val("");
});

channel.on("receive_message", function(dt) {
    var div = $("<div></div>", {"class": "received-message"})
        .text(dt.message);
    $("#received-messages").prepend(div);
});
```

- アプリケーションの起動

```bash
PORT=80 mix phoenix.server
```

80番にアクセスするとチャット画面が表示されます。
同じURLを別窓で開きチャットの送受信が出来れば完了です。

続いてRedisをPubSubのアダプターとして使ったスケール可能な構成にしてみます。

## RedisでPubSub機能を代替

- 依存パッケージの導入。

`mix.exs`

```diff
# applicationにphoenix_pubsub_redisを追加
def application do
    [mod: {RedisPubsubSample, []},
     applications: [:phoenix, :phoenix_html, :cowboy, :logger, :gettext,
                    :phoenix_ecto, :postgrex, 
+                   :phoenix_pubsub_redis
                   ]]
  end

# 依存関係にphoenix_pubsub_redisを追加
defp deps do
+ {:phoenix_pubsub_redis, "~> 2.0.0"},
end
```

依存パッケージダウンロード

```bash
mix deps.get
```

- エンドポイント設定

`config/config.exs`

```diff
# Configures the endpoint
config :redis_pubsub_sample, RedisPubsubSample.Endpoint,
- adapter: Phoenix.PubSub.PG2
+ adapter: Phoenix.PubSub.Redis,
+ host: "192.168.1.100"
```

- サーバ起動

```bash
PORT=80 mix phoenix.server
```

同じアプリケーションを別ポートまたは別ホストでアプリケーションを立ち上げてチャットが出来れば確認はOKです。



## まとめ

Elixir言語の概要とそれの土台となるErlang言語、Elixir製のフレームワークのPhoenixの概要を確認し、簡単なリアルタイムアプリケーションを作成しました。
軽く触った印象としてはErlang/Elixir特有の記法に最初は戸惑うもののErlangのロバストで安定したエコシステムを享受しつつ書き慣れたMVCフレームワーク的な使い方が出来るPhoenixは大量のリクエストを捌きたいAPIサーバとしてや安定したリアルタイムウェブアプリケーションの作成に向いてるのかなと思いました。まだElixir/Phoenixどちらも成長中のプロダクトなので今後も要注目ということで。


参考: [Elixir - Phoenix で Redis の Pub/Sub を使って WebSocket をスケールアウトさせる - Qiita](http://qiita.com/mserizawa/items/31e94c25bc177dcbc2c9)
参考: [Elixir - WebSocket and Web Application Framework - Qiita](http://qiita.com/ogomr/items/99dd4bd86384ad5add12)