Elixir製Webアプリケーションフレームワーク – Phoenixの構造
PhoenixはRuby on Railsの影響を受けたMVCのフレームワークで、開発スタイルやディレクトリの構造が非常に似ています。
過去の記事では、環境構築とCRUDアプリケーションを作るところまでを説明しました。
高生産性、高信頼性、高速のElixir製Webアプリケーションフレームワーク、Phoenixを始める
ElixirとPhoenixでCRUDなWebアプリケーションを作る
今回は、アプリケーションの作成ではなくPhoenixの構造やリクエスト処理について調べてみます。
Request Pipeline
Phoenixは他のWebアプリケーションフレームワークと同様にModel-View-Controllerの構造をしています。
Modelはデータにアクセスし、Viewはデータを表示すること、Controllerはリクエストを受け、変換してModelに届ける役割を持ちます。
一方でリクエストを受け付けてからレスポンスを返すまでの処理の流れをプログラム上で明示的に記述します(RailsではRackに移譲することにより暗黙的に実現しています)。
クライアントから送られたリクエストに対して、Phoenixはパイプラインのように一つずつ処理をして加工していきます。
そして、最終的に加工され出来上がったレスポンスをクライアントに返却します。
サーバに届いたリクエストは以下の順に処理されます。
connection |> endpoint |> router |> pipelines |> controller
それぞれの役割は以下です。
- endpointにまず最初にリクエストが届きます
- routerでリクエストを正しいコントローラに振り分けます
- pipelinesでは共通処理をリクエストに対して行います
- controllerは通常のWebアプリケーションと同じ役割です
Plug
このリクエストに対して一つずつ処理を実行する関数をPhoenixのPlugと呼びます。
Plugの定義は「Plug.Conn構造体を入力してPlug.Conn構造体を出力する」です。
説明したrouter
やcontroller
もPhoenixではPlugとして作られています。
つまり、Phoenixで作るWebアプリケーションはPlugのパイプラインで構成されているということです。
ディレクトリ構造
まず、プロジェクト全体のディレクトリ構造を見てみましょう。
hello └── web ├── channels ├── controllers │ ├── page_controller.ex │ └── hello_controller.ex ├── models ├── static ├── templates │ ├── hello │ │ └── world.html.eex │ ├── layout │ │ └── app.html.eex │ ├── page │ │ └── index.html.eex ├── views │ ├── error_view.ex │ ├── layout_view.ex │ ├── page_view.ex │ └── hello_view.ex ├── router.ex └── web.ex └── lib ├── hello.ex ├── hello ├── endpoint.ex └── config ├── config.exs ├── dev.exs ├── prod.exs ├── prod.secret.exs ├── test.exs └── test...
configディレクトリ
config.exs
はアプリケーション全体にかかわる設定をするファイルです。
use Mix.Config # Configures the endpoint config :hello, Hello.Endpoint, url: [host: "localhost"], root: Path.dirname(__DIR__), secret_key_base: "0W6...vk", render_errors: [accepts: ~w(html json)], pubsub: [name: Hello.PubSub, adapter: Phoenix.PubSub.PG2]
config
関数はPhoenixアプリケーションの初期設定を行っています。ここでエンドポイントも設定しています。
それではendpoint.ex
を見てみましょう、このファイルはlibディレクトリに置かれています。
libディレクトリ
endpoint
endpoint.ex
の中身は以下のようになっています
lib/endpoint.ex
defmodule Hello.Endpoint do use Phoenix.Endpoint, otp_app: :hello plug Plug.Static, ... plug Plug.RequestId plug Plug.Logger plug Plug.Parsers, ... plug Plug.MethodOverride plug Plug.Head plug Plug.Session, ... plug Hello.Router end
リクエストに対して処理をしていくPlugが設定されています。
ここに書いてあるPlugを上から順に処理していきます。アプリケーションに共通の処理を足したい場合は、共通処理を書いたPlugをここに設定すれば良いだけです。非常にわかりやすいですね。
それ以外のファイル
endpoint.ex以外にlibディレクトリにはsupervision treeにより起動するプロセス、長く起動し続けるプロセスを記述したファイルを置きます。
webディレクトリ
もう一度webディレクトリの中身を見てみましょう。
hello └── web ├── channels ├── controllers │ ├── page_controller.ex │ └── hello_controller.ex ├── models ├── static ├── templates │ ├── hello │ │ └── world.html.eex │ ├── layout │ │ └── app.html.eex │ ├── page │ │ └── index.html.eex ├── views │ ├── error_view.ex │ ├── layout_view.ex │ ├── page_view.ex │ └── hello_view.ex ├── router.ex └── web.ex
このディレクトリにはおなじみのControllerやModel、Viewのファイルが配置されています。
また、他のFWでは見たことのないchannels
ディレクトリがあります。ここにはPhoenixがサポートするリアルタイム通信をするためのファイルを配置します。
router
続いてrouter.exを見ていきましょう
web/router.ex
defmodule Hello.Router do use Hello.Web, :router # ひとまとまりのpipelineとして定義 pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers plug Hello.Auth, repo: Hello.Repo end pipeline :api do plug :accepts, ["json"] end scope "/" Hello do pipe_through :browser # 上で定義したplugのまとまりをこのルートで処理させる get "/", PageController, :index end end
routerはRailsと同じようにURLと処理するコントローラのルーティングを設定します。
それ以外にここでは、HTMLを返却するアプリケーションとAPIアプリケーションごとにそれぞれ共通して処理するPlug関数を設定しています。
pipe_through :browser
の記述がありますが、これは上で定義したpipeline :browser
の中で設定したplug関数を全て処理するようにしています。
endopoint.ex
とrouter.ex
を見ればリクエストに対してPhoenixが処理することがすべて分かります。
Railsとの違い
ここまではPhoenixアプリケーションのリクエスト処理の仕組みやディレクトリ構造について調べました。
最後にRuby on Railsとの違いについて整理します。
起動処理
プロジェクトのルートディレクトリに配置されているmix.ex
を見ると、起動時にアプリケーションが呼び出しているモジュールが分かります。
mix.ex
def application do [mod: {Hello, []}, applications: [:phoenix, :phoenix_html, :cowboy, :logger, :gettext, :phoenix_ecto, :postgrex]] end
これはPhoenix独自の仕組みではなく、すべてのElixirアプリケーションで共通する仕組みです。
Railsとは違い、Phoenixは一つのElixirアプリケーションとして作られています。
見通しの良さ
起動処理やリクエストに対する共通処理はendpoint.ex
とrouter.ex
を見ればすぐに理解でき、非常に見通しの良い造りになっています。
endpoint, controller, routerなどを全てPlugとして定義し抽象化したことによる成果です。
アセット
これは重要なことではないですが、PhoenixはCoffeeScriptではなく、ES2015です。
まとめ
PhoenixがRailsで成功した素晴らしい仕組みを取り入れつつ、Elixir、関数型の強みを生かした独自の構造をしていることを書いてきました。
個人的にアプリケーションを書いていて印象に残ったことは、Railsに近い生産性を持ちつつ全体の仕組みが非常に理解しやすいことです。
そして、今回は書いていませんがPhoenixにはChannelというリアルタイム通信をサポートする機能があります(Rails5もActionCableでサポートされました)。
今後のWebの成長とともに、成功する可能性の非常に大きいフレームワークです。