フレームワークなしでElixirにJSON APIエンドポイントを実装する方法は?
翻訳者から:
この記事では、Hello、World!と見なすことができる非常に単純なWebアプリケーションの例を示します。 Elixirで最もシンプルなAPIを作成します。
サンプルコードは、ライブラリの現在のバージョンに合わせて若干変更されています。
変更が加えられた完全なサンプルコードはGitHubで見ることができます。

新しい言語の課題
多くの開発者がRubyの世界からElixirに来ています。 これは、利用可能なライブラリとフレームワークの数の点で非常に成熟した環境です。 エリクサーでは、そのような成熟度では十分でない場合があります。 サードパーティのサービスが必要な場合、適切な検索の結果は次のようになります。
- よくサポートされている公式ライブラリがあります(非常にまれです)。
- 公式ですが、時代遅れまたはバグのあるライブラリがあります(それが起こることもあります)。
- コミュニティの誰かが(時々)開発した、よく管理されたライブラリがあります。
- コミュニティの誰かによって開発されたが、サポートされなくなったライブラリがあります(非常に一般的なケース)。
- いくつかのライブラリがあり、それぞれが自分のニーズに合わせて誰かによって作成されており、必要な機能がありません(最も一般的なオプション)。
- 上記のすべてのベストを組み合わせた独自のライブラリがあります...(あまりにも頻繁に見つかります)。
ElixirのシンプルなJSON API

びっくりするかもしれませんが、 Rubyは常にレールに乗っているわけではありません ( Ruby on Rails、覚えていますか?-翻訳者注 )。 また、出席するためにウェブとのコミュニケーションも常に必要とされるわけではありません。 この特定のケースでは、ウェブについて話しましょう。
単一のRESTfulエンドポイントの実装に関しては、通常多くのオプションがあります。
これらは私が個人的に使用したツールの例です。 私の同僚はシナトラのユーザーに満足しています。 彼らはなんとか花見を試すことができました。 現在の気分によっても、自分に合ったオプションを選択できます。
しかし、エリクサーに切り替えたとき、選択肢が限られていたことがわかりました。 いくつかの代替の「フレームワーク」(明白な理由のためにここでは言及しません)がありますが、それらを使用することはほとんど不可能です!
私は、これまでインターネットで言及されたすべての図書館を整理するのに一日中費やしました。 Slackボットとして行動して、単純なHTTP2サーバーをHerokuにデプロイしようとしましたが、その日のうちに降伏しました。 文字通り、私が見つけたオプションはどれも基本的な要件を実装できませんでした。
常に解決策ではない-フェニックス
フェニックスは、私のお気に入りのWebフレームワークです。それは単に冗長な場合があるだけです。 私はそれを使いたくありませんでした。1つのエンドポイントのためだけにフレームワーク全体をプロジェクトに引き込みました。 そして、それが非常に単純であることは重要ではありません。
既に述べたように、見つかったすべてのライブラリがニーズに適していないか(基本的なルーティングとJSONのサポートが必要でした)、Herokuに簡単かつ迅速にデプロイするには十分ではなかったため、既製のライブラリも使用できませんでした。 一歩下がったと思いました。

しかし、実際には、フェニックス自体は何かに基づいて構築されていますよね?
プラグとカウボーイが救いに来ます
Rubyで真にミニマルなサーバーを作成する必要がある場合は、Ruby Webサーバー用のモジュラーインターフェイスであるrack
使用するだけです。
幸いなことに、Elixirでも同様の機能が利用できます。 この場合、次の要素を使用します。
- cowboy -Erlang / OTP用の小型で高速なHTTPサーバー。完全なHTTPスタックとルーティングを実装し、レイテンシとメモリ使用量を最小限に抑えるよう最適化されています。
- プラグ -Erlang VMで実行されているさまざまなWebサーバー用のアダプターのセット。 各アダプターは、その背後にあるWebサーバーへの直接インターフェースを提供します。
- poisonは、ElixirでJSONを処理するためのライブラリです。
実装
エンドポイント(エンドポイント)、ルーター(ルーター)、JSONパーサー(JSONハンドラー)などのコンポーネントを実装したい。 次に、結果をHerokuにデプロイし、着信リクエストを処理できるようにします。 これをどのように達成できるかを見てみましょう。
アプリ
Elixirプロジェクトにスーパーバイザーが含まれていることを確認してください。 これを行うには、次のようなプロジェクトを作成します。
mix new minimal_server --sup
mix.exsに以下が含まれていることを確認してください。
def application do [ extra_applications: [:logger], mod: {MinimalServer.Application, []} ] end
ファイル lib/minimal_server/application.ex
を作成します 。
defmodule MinimalServer.Application do use Application def start(_type, _args), do: Supervisor.start_link(children(), opts()) defp children do [] end defp opts do [ strategy: :one_for_one, name: MinimalServer.Supervisor ] end end
図書館
mix.exs
次のライブラリを指定する必要があります。
defp deps do [ {:poison, "~> 4.0"}, {:plug, "~> 1.7"}, {:cowboy, "~> 2.5"}, {:plug_cowboy, "~> 2.0"} ] end
次に、依存関係をダウンロードしてコンパイルします。
mix do deps.get, deps.compile, compile
エンドポイント
これで、サーバーへのエントリポイントを作成する準備がすべて整いました。 次の内容のlib/minimal_server/endpoint.ex
ファイルを作成しましょう。
defmodule MinimalServer.Endpoint do use Plug.Router plug(:match) plug(Plug.Parsers, parsers: [:json], pass: ["application/json"], json_decoder: Poison ) plug(:dispatch) match _ do send_resp(conn, 404, "Requested page not found!") end end
Plug
モジュールには、使用するパスとHTTPメソッドに応じて着信要求をリダイレクトするPlug.Router
が含まれています。 リクエストを受け取ると、ルーターはmodule :match
を呼び出し、 match/2
関数で表されます。これは、対応するルートを見つける役割を果たし、その後、module :dispatch
にリダイレクトし、対応するコードを実行します。
APIをJSONに準拠させるため、 Plug.Parsers
を実装する必要があります。 指定された:json_decoder
を使用してapplication/json
リクエストを処理するため、リクエスト本文の分析に使用します。
その結果、すべてのリクエストに一致し、HTTP not found(404)コードで応答する「任意のリクエスト」という一時的なルートを作成しました。
ルーター
ルーターの実装は、アプリケーション作成の最後のステップです。 これは、作成したパイプライン全体の最後の要素です。Webブラウザーからの要求の受信から始まり、応答の形成で終わります。
ルーターは、クライアントからの着信要求を処理し、必要な形式でメッセージを送り返します( ファイルlib/minimal_server/router.ex
特定のコードを追加します-翻訳者のメモ ):
defmodule MinimalServer.Router do use Plug.Router plug(:match) plug(:dispatch) get "/" do conn |> put_resp_content_type("application/json") |> send_resp(200, Poison.encode!(message())) end defp message do %{ response_type: "in_channel", text: "Hello from BOT :)" } end end
上記のRouter
モジュールでは、 GET
メソッドによって送信され、 /
ルートに沿って送信された場合にのみ、要求が処理されます。 Routerモジュールは、 application/json
およびbodyを含むContent-Type
ヘッダーで応答しapplication/json
。
{ "response_type": "in_channel", "text": "Hello from BOT :)" }
すべてをまとめる
ここで、 Endpoint
モジュールを変更してリクエストをルーターに転送し、 Application
を変更してEndpoint
モジュール自体を起動します。
最初の方法は、 MinimalServer.Endpoint
[ match _ do ... end
MinimalServer.Endpoint
前に追加することで実行できます。 翻訳者 ]文字列
forward("/bot", to: MinimalServer.Router)
これにより、 /bot
へのすべてのリクエストがRouter
モジュールにルーティングされ、処理されます。
2番目の関数は、 child_spec/1
関数とchild_spec/1
関数をendpoint.ex
ファイルに追加することで実装できます。
defmodule MinimalServer.Endpoint do # ... def child_spec(opts) do %{ id: __MODULE__, start: {__MODULE__, :start_link, [opts]} } end def start_link(_opts), do: Plug.Cowboy.http(__MODULE__, []) end
children/0
関数によって返されるリストにMinimalServer.Endpoint
を追加するapplication.ex
により、 MinimalServer.Endpoint
を変更できるようにMinimalServer.Endpoint
ました。
defmodule MinimalServer.Application do # ... defp children do [ MinimalServer.Endpoint ] end end
サーバーを起動するには、次のようにします。
mix run --no-halt
最後に、アドレスhttp:// localhost:4000 / botにアクセスして、メッセージを確認できます:)
展開

構成
ほとんどの場合、ローカル環境および操作のために、サーバーの構成は異なります。 したがって、これらのモードごとに個別の設定を入力する必要があります。 まず、 config.exs
をconfig.exs
、 config.exs
を追加します。
config :minimal_server, MinimalServer.Endpoint, port: 4000
この場合、アプリケーションがtest
、 prod
およびdev
モードで起動されると、これらの設定が変更されていなければポート4000を受け取ります。
翻訳者からこの時点で、元のテキストの作成者は、config.exsを変更して、モードごとに異なるオプションを使用する方法について言及するのを忘れていました。 これを行うには、 config/config.exs
最後の行にimport_config "#{Mix.env()}.exs"
;をconfig/config.exs
ます。 結果は次のようになります。
use Mix.Config config :minimal_server, MinimalServer.Endpoint, port: 4000 import_config "#{Mix.env()}.exs"
その後、 config
ディレクトリにprod.exs
、 test.exs
、 dev.exs
各ファイルをprod.exs
にtest.exs
作成します。
use Mix.Config
本番環境では、通常、ポート番号をハードに設定するのではなく、次のようなシステム環境変数に依存します。
config :minimal_server, MinimalServer.Endpoint, port: "PORT" |> System.get_env() |> String.to_integer()
上記のテキストをconfig/prod.exs
最後に追加します-約 翻訳者
その後、固定値がローカルで使用され、運用操作では環境変数の構成が使用されます。
endpoint.ex
でこのスキームを実装してみましょう( start_link / 1関数を置き換える-トランスレーターコメント ):
defmodule MinimalServer.Endpoint do # ... require Logger def start_link(_opts) do with {:ok, [port: port] = config} <- Application.fetch_env(:minimal_server, __MODULE__) do Logger.info("Starting server at http://localhost:#{port}/") Plug.Adapters.Cowboy2.http(__MODULE__, [], config) end end end
ヘロク
Herokuは、複雑なセットアップなしで最も簡単なワンクリック展開を提供します。 プロジェクトをデプロイするには、いくつかの簡単なファイルを準備し、リモートアプリケーションを作成する必要があります 。

Heroku CLIをインストールした後、次のように新しいアプリケーションを作成できます。
$ heroku create minimal-server-habr Creating ⬢ minimal-server-habr... done https://minimal-server-habr.herokuapp.com/ | https://git.heroku.com/minimal-server-habr.git
次に、 Elixir Build Kitをアプリケーションに追加します。
heroku buildpacks:set \ https://github.com/HashNuke/heroku-buildpack-elixir.git
この翻訳の時点で、ElixirとErlangの現在のバージョンは(プラスまたはマイナス)です。
erlang_version=21.1 elixir_version=1.8.1
ビルドキット自体を構成するには、上記の行をelixir_buildpack.config
ファイルに追加します。
最後の手順は、Procfileを作成することです。これも非常に簡単です。
web: mix run --no-halt
翻訳者注:Herokuでのビルド中のエラーを回避するには、アプリケーションで使用される環境変数の値を設定する必要があります。
$ heroku config:set PORT=4000 Setting PORT and restarting ⬢ minimal-server-habr... done, v5 PORT: 4000
新しいファイルをコミットするとすぐに[ git-およそを使用して。 翻訳者 ]、Herokuにアップロードできます。
$ git push heroku master Initializing repository, done. updating 'refs/heads/master' ...
そしてそれだけです! アプリケーションはhttps://minimal-server-habr.herokuapp.comで入手できます 。
まとめ
この時点で、フレームワークを使用せずに3( 4-およそTranslator )ライブラリのみを使用して、最も単純なJSON RESTful APIおよびHTTPサーバーをElixirに実装する方法を既に理解しました。
単純なエンドポイントへのアクセスを提供する必要がある場合、Phoenixやその他のフレームワークに関係なく、Phoenixを毎回使用する必要はまったくありません。
plug
+ cowboy
とPhoenixの間に信頼性が高く、十分にテストされ、サポートされているフレームワークがないのはなぜですか? たぶん、単純なものを実装する必要は本当にないのでしょうか? たぶん、各企業は独自のライブラリを使用していますか? または、誰もがフェニックスまたは提示されたアプローチのいずれかを使用していますか?

リポジトリは、いつものように、私のGitHubで入手できます。