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

  • 72
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

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

Elixirとは

logo[1].png

Elixir公式サイトから引用します

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

同じくErlang公式サイトから引用します。

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

同じくPhoenix公式サイトから引用します。

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

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

環境

  • Ubuntu 14.04 Trusty

インストール

Elixir公式サイト及びPhoenix公式サイトを参考にインストールを進めていきます。

  • Erlangリポジトリを追加
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をインストール
apt-get install esl-erlang elixir
  • mixでhexパッケージマネージャとphoenix本体をインストールします

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

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を作成します
mix phoenix.new example
  • 依存パッケージのインストール
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を編集
- http: [port: 4000],
+ http: [port: 80],
  • phoenixサーバ起動
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機能を代替したスケール可能なバージョンを作っていきます。

単体構成

  • プロジェクトの作成
mix phoenix.new redis_pubsub_sample
  • 起動ポートを環境変数から取得するよう設定

config/dev.exs

- http: [port: 4000],
+ http: [port: System.get_env("PORT")],
  • Channelのルーティング設定

web/channels/user_socket.ex

channel "rooms:*", RedisPubsubSample.RoomChannel
  • Channelの作成

web/channels/room_channel.ex

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

<!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

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);
});
  • アプリケーションの起動
PORT=80 mix phoenix.server

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

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

RedisでPubSub機能を代替

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

mix.exs

# 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

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

mix deps.get
  • エンドポイント設定

config/config.exs

# Configures the endpoint
config :redis_pubsub_sample, RedisPubsubSample.Endpoint,
- adapter: Phoenix.PubSub.PG2
+ adapter: Phoenix.PubSub.Redis,
+ host: "192.168.1.100"
  • サーバ起動
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
参考: Elixir - WebSocket and Web Application Framework - Qiita