Wie implementiere ich einen JSON-API-Endpunkt auf Elixir ohne Frameworks?
Vom Übersetzer:
Der Artikel enthält ein Beispiel für eine sehr einfache Webanwendung, die als Hello, World! beim Erstellen der einfachsten API auf Elixir.
Der Beispielcode wurde geringfügig geändert, um den aktuellen Versionen der Bibliotheken zu entsprechen.
Der vollständige Beispielcode mit den Änderungen ist auf GitHub zu sehen .

Neue sprachliche Herausforderungen
Viele Entwickler kommen aus der Welt von Ruby zu Elixir . Dies ist eine sehr ausgereifte Umgebung in Bezug auf die Anzahl der verfügbaren Bibliotheken und Frameworks. Und eine solche Reife reicht mir in Elixir manchmal nicht aus. Wenn ich einen Dienst eines Drittanbieters benötige, kann das Ergebnis einer geeigneten Suche wie folgt aussehen:
- es gibt eine offizielle, gut unterstützte Bibliothek (sehr selten);
- Es gibt eine offizielle, aber veraltete oder fehlerhafte Bibliothek (manchmal passiert es).
- Es gibt eine gut unterstützte Bibliothek, die von jemandem aus der Community entwickelt wurde (manchmal von Zeit zu Zeit).
- Es gibt eine Bibliothek, die von jemandem aus der Community entwickelt, aber nicht mehr unterstützt wird (ein sehr häufiger Fall).
- Es gibt mehrere Bibliotheken, von denen jede von jemandem für seine eigenen Bedürfnisse geschrieben wurde, und es fehlen die erforderlichen Funktionen (die beliebteste Option).
- Es gibt meine eigene Bibliothek, die das Beste aus den oben genannten kombiniert ... (zu oft gefunden).
Einfache JSON-API unter Elixir

Sie werden vielleicht überrascht sein, aber Ruby ist nicht immer auf den Schienen ( Ruby on Rails, erinnerst du dich? - Anmerkung des Übersetzers ). Die Kommunikation mit dem Internet ist ebenfalls nicht immer erforderlich. Lassen Sie uns in diesem speziellen Fall über das Web sprechen.
Bei der Implementierung eines einzelnen RESTful-Endpunkts gibt es normalerweise viele Optionen:
Dies sind Beispiele für Tools, die ich persönlich verwendet habe. Meine Kollegen sind zufriedene Sinatra-Benutzer. Sie haben es geschafft, Hanami zu versuchen. Ich kann jede Option wählen, die zu mir passt, auch abhängig von meiner aktuellen Stimmung.
Aber als ich zu Elixir wechselte, stellte sich heraus, dass die Auswahl begrenzt war. Obwohl es mehrere alternative „Frameworks“ gibt (deren Namen ich aus offensichtlichen Gründen hier nicht erwähnen werde), ist es fast unmöglich, sie zu verwenden!
Ich habe den ganzen Tag damit verbracht, jede Bibliothek zu sortieren, die jemals im Internet erwähnt wurde. Als Slack-Bot habe ich versucht, einen einfachen HTTP2-Server für Heroku bereitzustellen , habe mich aber am Ende des Tages ergeben. Im wahrsten Sinne des Wortes war keine der Optionen, die ich gefunden habe, in der Lage, die grundlegenden Anforderungen umzusetzen.
Nicht immer eine Lösung - Phoenix
Phoenix ist mein Lieblingswebframework, nur dass es manchmal überflüssig ist. Ich wollte es nicht verwenden und das gesamte Framework nur für einen Endpunkt in das Projekt einbeziehen. und es spielt keine Rolle, dass es sehr einfach ist.
Ich konnte auch keine vorgefertigten Bibliotheken verwenden, da, wie ich bereits sagte, alle gefundenen Bibliotheken entweder nicht für meine Anforderungen geeignet waren (grundlegendes Routing und JSON-Unterstützung waren erforderlich) oder für eine einfache und schnelle Bereitstellung in Heroku nicht bequem genug waren. Mach einen Schritt zurück, dachte ich.

Aber tatsächlich ist Phoenix selbst auf der Basis von etwas gebaut , nicht wahr?
Plug & Cowboy kommen zur Rettung
Wenn Sie auf Ruby einen wirklich minimalistischen Server erstellen müssen, können Sie einfach rack
- eine modulare Schnittstelle für Ruby-Webserver.
Glücklicherweise ist etwas Ähnliches in Elixir verfügbar. In diesem Fall verwenden wir die folgenden Elemente:
- Cowboy ist ein kleiner und schneller HTTP-Server für Erlang / OTP, der den vollständigen HTTP-Stack und das Routing implementiert und so optimiert ist, dass Latenz und Speichernutzung minimiert werden.
- Plug - eine Reihe von Adaptern für verschiedene Webserver, die in Erlang VM ausgeführt werden; Jeder Adapter bietet eine direkte Schnittstelle zum dahinter liegenden Webserver.
- gift ist eine Bibliothek zur Verarbeitung von JSON auf Elixir.
Implementierung
Ich möchte Komponenten wie Endpoint (Endpoint), Router (Router) und JSON Parser (JSON-Handler) implementieren. Dann möchte ich das Ergebnis auf Heroku bereitstellen und eingehende Anfragen verarbeiten können. Mal sehen, wie dies erreicht werden kann.
App
Stellen Sie sicher, dass Ihr Elixir-Projekt einen Supervisor enthält. Erstellen Sie dazu ein Projekt wie folgt:
mix new minimal_server --sup
Stellen Sie sicher, dass mix.exs Folgendes enthält:
def application do [ extra_applications: [:logger], mod: {MinimalServer.Application, []} ] end
und erstellen Sie die Datei 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
Bibliotheken
Die folgenden Bibliotheken müssen in mix.exs
angegeben mix.exs
:
defp deps do [ {:poison, "~> 4.0"}, {:plug, "~> 1.7"}, {:cowboy, "~> 2.5"}, {:plug_cowboy, "~> 2.0"} ] end
Laden Sie dann die Abhängigkeiten herunter und kompilieren Sie sie:
mix do deps.get, deps.compile, compile
Endpunkt
Jetzt ist alles bereit, um einen Einstiegspunkt für den Server zu erstellen. Erstellen wir eine lib/minimal_server/endpoint.ex
Datei mit folgendem Inhalt:
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
Das Plug
Modul enthält Plug.Router
zum Umleiten eingehender Anforderungen in Abhängigkeit vom verwendeten Pfad und der HTTP-Methode. Nach Erhalt der Anforderung ruft der Router das Modul :match
, dargestellt durch die Funktion match/2
, die für das Auffinden der entsprechenden Route verantwortlich ist, und leitet es dann an das Modul :dispatch
, das den entsprechenden Code ausführt.
Da unsere API JSON-kompatibel sein soll, müssen wir Plug.Parsers
implementieren. Da es application/json
Anforderungen mit dem angegebenen :json_decoder
verarbeitet :json_decoder
, werden wir es verwenden, um den Anforderungshauptteil zu analysieren.
Als Ergebnis haben wir eine temporäre Route "jede Anforderung" erstellt, die allen Anforderungen entspricht und mit nicht gefundenem HTTP-Code (404) antwortet.
Router
Die Implementierung eines Routers ist der letzte Schritt bei der Erstellung unserer Anwendung. Dies ist das letzte Element der gesamten Pipeline, die wir erstellt haben: Beginnen Sie mit dem Empfang einer Anfrage von einem Webbrowser und enden Sie mit der Bildung einer Antwort.
Der Router verarbeitet die eingehende Anforderung vom Client und sendet eine Nachricht im gewünschten Format zurück ( fügen Sie den obigen Code zur Datei lib/minimal_server/router.ex
- Anmerkung des Übersetzers hinzu ):
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
Im obigen Router
Modul wird die Anforderung nur verarbeitet, wenn sie von der GET
Methode gesendet und entlang der /
route gesendet wird. Das Router-Modul antwortet mit einem Content-Type
Header, der application/json
und body enthält:
{ "response_type": "in_channel", "text": "Hello from BOT :)" }
Alles zusammenfügen
Jetzt ist es an der Zeit, das Endpoint
Modul zu ändern, um Anforderungen an den Router weiterzuleiten und die Application
zu ändern, dass das Endpoint
Modul selbst gestartet wird.
Das erste kann durch Hinzufügen zu MinimalServer.Endpoint
[ vor der match _ do ... end
rule - ca. Übersetzer ] Zeichenfolge
forward("/bot", to: MinimalServer.Router)
Dadurch wird sichergestellt, dass alle Anforderungen an /bot
an das Router
Modul weitergeleitet und von diesem verarbeitet werden.
Die zweite kann implementiert werden, indem die Funktionen child_spec/1
und start_link/1
Datei child_spec/1
start_link/1
werden:
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
Jetzt können Sie application.ex
ändern, indem Sie MinimalServer.Endpoint
zu der von der Funktion children/0
Liste hinzufügen.
defmodule MinimalServer.Application do # ... defp children do [ MinimalServer.Endpoint ] end end
Um den Server zu starten, gehen Sie einfach wie folgt vor:
mix run --no-halt
Schließlich können Sie die Adresse http: // localhost: 4000 / bot besuchen und unsere Nachricht sehen :)
Bereitstellung

Konfig
In einer lokalen Umgebung und für den Betrieb ist der Server meistens anders konfiguriert. Daher müssen wir für jeden dieser Modi separate Einstellungen vornehmen. config.exs
unsere config.exs
indem Sie config.exs
hinzufügen:
config :minimal_server, MinimalServer.Endpoint, port: 4000
In diesem Fall erhält die Anwendung beim Start im test
, prod
und dev
Modus Port 4000, wenn diese Einstellungen nicht geändert werden.
Vom ÜbersetzerZu diesem Zeitpunkt hat der Autor des Originaltextes vergessen zu erwähnen, wie config.exs so geändert werden kann, dass Sie verschiedene Optionen für verschiedene Modi verwenden können. import_config "#{Mix.env()}.exs"
Sie dazu import_config "#{Mix.env()}.exs"
; in der letzten Zeile in config/config.exs
; Das Ergebnis ist so etwas wie:
use Mix.Config config :minimal_server, MinimalServer.Endpoint, port: 4000 import_config "#{Mix.env()}.exs"
Erstellen Sie anschließend die Dateien prod.exs
, test.exs
, dev.exs
im config
indem Sie sie in jede Zeile dev.exs
:
use Mix.Config
In der Produktion möchten wir die Portnummer normalerweise nicht fest einstellen, sondern uns auf eine Systemumgebungsvariable stützen, zum Beispiel:
config :minimal_server, MinimalServer.Endpoint, port: "PORT" |> System.get_env() |> String.to_integer()
Fügen Sie den obigen Text am Ende von config/prod.exs
- ca. Übersetzer
Danach wird lokal ein fester Wert und im betrieblichen Betrieb eine Konfiguration von Umgebungsvariablen verwendet.
Implementieren wir dieses Schema in endpoint.ex ( ersetzen der Funktion start_link / 1 - Übersetzerkommentar ):
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 bietet die einfachste Ein-Klick-Bereitstellung ohne komplizierte Einrichtung. Um unser Projekt bereitzustellen, müssen Sie einige einfache Dateien vorbereiten und eine Remote-Anwendung erstellen .

Nach der Installation von Heroku CLI können Sie eine neue Anwendung wie folgt erstellen:
$ heroku create minimal-server-habr Creating ⬢ minimal-server-habr... done https://minimal-server-habr.herokuapp.com/ | https://git.heroku.com/minimal-server-habr.git
Fügen Sie nun das Elixir Build Kit zu Ihrer Anwendung hinzu:
heroku buildpacks:set \ https://github.com/HashNuke/heroku-buildpack-elixir.git
Zum Zeitpunkt dieser Übersetzung sind die aktuellen Versionen von Elixir und Erlang (plus oder minus):
erlang_version=21.1 elixir_version=1.8.1
Um das Build Kit selbst zu konfigurieren, fügen Sie die obigen Zeilen zur Datei elixir_buildpack.config
.
Der letzte Schritt besteht darin, ein Procfile zu erstellen. Auch dies ist sehr einfach:
web: mix run --no-halt
Anmerkung des Übersetzers: Um einen Fehler beim Erstellen von Heroku zu vermeiden, müssen Sie den Wert der Umgebungsvariablen festlegen, die in der Anwendung verwendet werden:
$ heroku config:set PORT=4000 Setting PORT and restarting ⬢ minimal-server-habr... done, v5 PORT: 4000
Sobald Sie neue Dateien festschreiben [ mit git - ca. Übersetzer ], können Sie sie auf Heroku hochladen:
$ git push heroku master Initializing repository, done. updating 'refs/heads/master' ...
Und das ist alles! Die Anwendung ist unter https://minimal-server-habr.herokuapp.com verfügbar.
Zusammenfassung
Zu diesem Zeitpunkt haben Sie bereits verstanden, wie Sie die einfachste JSON RESTful-API und den einfachsten HTTP-Server in Elixir implementieren, ohne Frameworks zu verwenden, und nur 3 ( ca. 4 - ca. Übersetzer ) Bibliotheken verwenden.
Wenn Sie Zugriff auf einfache Endpunkte gewähren müssen, müssen Sie Phoenix nicht jedes Mal verwenden, egal wie cool es ist, genau wie jedes andere Framework.
Neugierig, warum es zwischen plug
+ cowboy
und Phoenix keine zuverlässigen, gut getesteten und unterstützten Frameworks gibt? Vielleicht besteht keine wirkliche Notwendigkeit, einfache Dinge zu implementieren? Vielleicht nutzt jedes Unternehmen seine eigene Bibliothek? Oder verwendet jeder entweder Phoenix oder den vorgestellten Ansatz?

Das Repository ist wie immer auf meinem GitHub verfügbar.