Eine Einführung in Elixir-Anwendungen

In meinen vorherigen Artikeln haben wir verschiedene Elixir-Begriffe besprochen und eine Menge Code geschrieben. Was wir jedoch nicht besprochen haben, ist, wie Sie Ihren Code so strukturieren und organisieren, dass er leicht zu verwalten und zu veröffentlichen ist. 

Anwendungen sind für Erlang und Elixir sehr häufig und werden zum Erstellen wiederverwendbarer Komponenten verwendet, die sich als eigenständige Einheiten verhalten. Eine Anwendung verfügt möglicherweise über eine eigene Überwachungsstruktur und -konfiguration und kann sich auf andere Anwendungen verlassen, die entweder lokal oder auf einem Remote-Server verfügbar sind. Alles in allem ist das Arbeiten mit Anwendungen nicht so komplex, und die Leute, die beispielsweise aus der Welt von Ruby stammen, werden viele bekannte Konzepte finden.

In diesem Artikel erfahren Sie, was Anwendungen sind, wie sie erstellt werden können, wie Abhängigkeiten angegeben und installiert werden und wie Umgebungswerte bereitgestellt werden. Am Ende des Artikels werden wir ein wenig üben und einen webbasierten Rechner erstellen. 

Ich werde Elixir 1.5 in diesem Artikel verwenden (es wurde vor einigen Monaten veröffentlicht), aber alle erläuterten Konzepte sollten auch für Version 1.4 gelten.

Anwendungen?

Einige mögen argumentieren, dass der Begriff "Anwendung" nicht sehr passend ist, da er in Erlang und Elixir tatsächlich eine Komponente oder einen Code mit einer Reihe von Abhängigkeiten bedeutet. Die Anwendung selbst kann als Abhängigkeit verwendet werden - in Ruby-Welt würden wir sie "Juwel" nennen..

Alles in allem sind Anwendungen in Elixir sehr verbreitet und ermöglichen Ihnen die Erstellung wiederverwendbarer Komponenten und bieten zudem ein einfaches Abhängigkeitsmanagement. Sie bestehen aus einem oder mehreren Modulen mit null oder mehr Abhängigkeiten und werden von der Anwendungsressourcendatei beschrieben. Diese Datei enthält Informationen zum Namen, zur Version, zu den Modulen, zu den Abhängigkeiten der Anwendung und zu anderen Informationen. Sie können die Ressourcendatei manuell erstellen, dies ist jedoch mit dem Mix-Tool, das eine korrekte Ordnerstruktur vorbereitet, viel einfacher. 

Schauen wir uns also an, wie wir eine neue Elixir-Anwendung erstellen können!

Neue Bewerbung

Um eine neue Anwendung zu erstellen, müssen Sie nur den folgenden Befehl ausführen:

mischen Sie neuen app_name

Wir können auch die --sup Flag, um einen leeren Supervisor für uns zu erstellen. Lassen Sie uns eine neue Anwendung erstellen Probe diesen Weg:

neue Probe mischen --sup

Dieser Befehl erstellt eine Probe Verzeichnis für Sie mit einer Handvoll Dateien und Ordnern. Lass mich dich schnell durch sie führen:

  • Konfig Ordner enthält eine einzige Datei config.exs das, wie Sie sich vorstellen können, bietet Konfiguration für die Anwendung. Anfangs gibt es einige nützliche Kommentare, aber keine Konfiguration. Beachten Sie übrigens, dass die in dieser Datei bereitgestellte Konfiguration nur auf die Anwendung selbst beschränkt ist. Wenn Sie die Anwendung als Abhängigkeit laden, wird es angezeigt config.exs wird effektiv ignoriert.
  • lib ist der primäre Ordner der Anwendung, der a enthält sample.ex Datei und a Probe Ordner mit einem application.ex Datei. application.ex Definiert ein Callback-Modul mit einem Start / 2 Funktion, die einen leeren Supervisor erstellt.
  • Prüfung ist der Ordner, der automatisierte Tests für die Anwendung enthält. In diesem Artikel werden wir keine automatisierten Tests diskutieren.
  • mix.exs ist die Datei, die alle notwendigen Informationen zur Anwendung enthält. Hier gibt es mehrere Funktionen. In der Projekt Bei dieser Funktion geben Sie den Namen der App (als Atom), die Version und die Umgebung an. Das Anwendung Die Funktion enthält Informationen zu den Anwendungsmodul-Callback- und Laufzeitabhängigkeiten. In unserem Fall, Sample.Application wird als Callback des Anwendungsmoduls (das als Haupteinstiegspunkt behandelt werden kann) festgelegt, und es muss a definiert werden Start / 2 Funktion. Wie bereits erwähnt, wurde diese Funktion bereits von der mischen Werkzeug. Zuletzt die deps Funktion listet Abhängigkeiten der Bauzeit auf.

Abhängigkeiten

Es ist sehr wichtig, zwischen den Abhängigkeiten der Laufzeit und der Buildzeit zu unterscheiden. Build-Time-Abhängigkeiten werden vom geladen mischen Tool während der Kompilierung und werden grundsätzlich in Ihre Anwendung kompiliert. 

Sie können beispielsweise von einem Dienst wie GitHub oder von der Hex.pm-Website abgerufen werden, einem externen Paketmanager, der Tausende von Komponenten für Elixir und Erlang speichert. Laufzeitabhängigkeiten werden vor dem Start der Anwendung gestartet. Sie sind bereits zusammengestellt und für uns verfügbar.

Es gibt verschiedene Möglichkeiten, um Build-Time-Abhängigkeiten in a anzugeben mix.exs Datei. Wenn Sie eine Anwendung von der hex.pm-Website verwenden möchten, sagen Sie einfach:

: dependency_name, "~> 0.0.1"

Das erste Argument ist immer ein Atom, das den Namen der Anwendung darstellt. Die zweite ist die Anforderung, eine Version, die Sie verwenden möchten. Sie wird vom Version-Modul analysiert. In diesem Beispiel, ~> bedeutet, dass wir mindestens die Version herunterladen möchten 0.0.1 oder höher aber weniger als 0,1,0. Wenn wir sagen ~> 1,0, es bedeutet, dass wir Version größer oder gleich verwenden möchten 1,0 aber weniger als 2,0. Es gibt auch Betreiber wie ==, >, <, > =, und <= verfügbar.

Es ist auch möglich, eine direkt anzugeben : git oder ein :Pfad Möglichkeit:

: gettext, git: "https://github.com/elixir-lang/gettext.git", Tag: "0,1" : local_dependency, path: "path / to / local_dependency"

Da ist auch ein : github Abkürzung, die es uns erlaubt, nur den Namen des Besitzers und eines Repos anzugeben:

: gettext, github: "elixir-lang / gettext"

Um alle Abhängigkeiten herunterzuladen und zu kompilieren, führen Sie Folgendes aus:

mix deps.get

Dadurch wird ein Hex-Client installiert, falls Sie noch keinen haben, und prüfen Sie, ob eine der Abhängigkeiten aktualisiert werden muss. Zum Beispiel können Sie Poison-eine Lösung angeben, um JSON-als eine Abhängigkeit wie folgt zu analysieren:

 defp deps tun [: poison, "~> 3.1"] ende

Dann renne:

mix deps.get

Sie sehen eine ähnliche Ausgabe:

Ausführen der Abhängigkeitsauflösung… Auflösung der Abhängigkeit abgeschlossen: poison 3.1.0 * poison erhalten (Hex-Paket) Paket überprüfen (https://repo.hex.pm/tarballs/poison-3.1.0.tar) Paket abgerufen

Poison ist jetzt kompiliert und auf Ihrem PC verfügbar. Was mehr ist, a Mix.lock Die Datei wird automatisch erstellt. Diese Datei enthält die genauen Versionen der Abhängigkeiten, die beim Booten der Anwendung verwendet werden sollen. 

Führen Sie den folgenden Befehl aus, um mehr über Abhängigkeiten zu erfahren:

mix help deps

Wieder Verhalten

Anwendungen sind Verhaltensweisen, genau wie GenServer und Supervisors, über die wir in den vorherigen Artikeln gesprochen haben. Wie ich oben bereits erwähnt habe, bieten wir ein Callback-Modul im mix.exs Datei auf folgende Weise:

 def application do [mod: Sample.Application, []] ende

Sample.Application ist der Name des Moduls, während [] kann eine Liste von Argumenten enthalten, die an das übergeben werden sollen Start / 2 Funktion. Das Start / 2 Funktion muss implementiert werden, damit die Anwendung ordnungsgemäß startet.

Das application.ex enthält das Callback-Modul, das folgendermaßen aussieht:

defmodule Sample.Application verwendet Application def start (_type, _args) do children = [] opts = [strategie:: one_for_one, name: Sample.Supervisor] Supervisor.start_link (children, opts) end end

Das Start / 2 Funktion muss entweder zurückkehren : ok, pid (mit optionalem Status als drittes Element) oder : Fehler, Grund.

Erwähnenswert ist auch, dass Anwendungen das Callback-Modul überhaupt nicht wirklich benötigen. Es bedeutet, dass die Anwendung innerhalb der mix.exs Datei kann sehr minimalistisch werden:

def application do [] ende

Solche Anwendungen werden aufgerufen Bibliotheksanwendungen. Sie haben keinen Überwachungsbaum, können aber trotzdem von anderen Anwendungen als Abhängigkeiten verwendet werden. Ein Beispiel für eine Bibliotheksanwendung wäre Poison, das wir im vorherigen Abschnitt als Abhängigkeit angegeben haben.

Anwendung starten

Die einfachste Möglichkeit, Ihre Anwendung zu starten, besteht darin, den folgenden Befehl auszuführen:

iex -S mix

Sie sehen eine Ausgabe, die dieser ähnelt:

2 Dateien kompilieren (.ex) Generierte Beispielanwendung

EIN _bauen Das Verzeichnis wird innerhalb von erstellt Probe Mappe. Es wird enthalten .Strahl Dateien sowie einige andere Dateien und Ordner.

Wenn Sie keine Elixir-Shell starten möchten, können Sie eine andere Option ausführen:

Mischlauf

Das Problem ist jedoch, dass die Anwendung beendet wird, sobald die Start Funktion beendet seine Arbeit. Daher können Sie die --ohne halt Schlüssel, um die Anwendung so lange wie nötig laufen zu lassen:

Mixlauf - no-halt

Dasselbe kann mit der erreicht werden Elixier Befehl:

Elixier-Mix-Mix - no-halt

Beachten Sie jedoch, dass die Anwendung angehalten wird, sobald Sie das Terminal schließen, an dem dieser Befehl ausgeführt wurde. Dies kann vermieden werden, indem Sie Ihre Anwendung in einem getrennten Modus starten: 

Elixier-Mix-Mix --no-halt - Detached

Anwendungsumgebung

In manchen Fällen möchten Sie möglicherweise, dass der Benutzer einer Anwendung einige Parameter einstellt, bevor die App tatsächlich gestartet wird. Dies ist nützlich, wenn der Benutzer beispielsweise steuern kann, welchen Port ein Webserver überwachen soll. Solche Parameter können in der Anwendungsumgebung angegeben werden, bei der es sich um einen einfachen Schlüsselwertspeicher handelt. 

Um einige Parameter zu lesen, verwenden Sie die fetch_env / 2 Funktion, die eine App und einen Schlüssel akzeptiert:

Application.fetch_env (: sample,: some_key) 

Wenn der Schlüssel nicht gefunden werden kann, eine :Error Atom wird zurückgegeben. Es gibt auch eine fetch_env! / 2 Funktion, die stattdessen einen Fehler auslöst und get_env / 3 das kann einen Standardwert liefern.

Um einen Parameter zu speichern, verwenden Sie put_env / 4:

Application.put_env (: sample,: key,: value)

Der vierte Wert enthält Optionen und muss nicht festgelegt werden.

Um einen Schlüssel zu löschen, verwenden Sie die delete_env / 3 Funktion:

Application.delete_env (: sample,: key)

Wie liefern wir einen Wert für die Umgebung, wenn Sie eine App starten? Nun, solche Parameter werden mit der eingestellt --erl Schlüssel auf folgende Weise eingeben:

iex --erl "-sample key value" -S mix

Sie können dann einfach den Wert abrufen:

Application.get_env: sample,: key # =>: value

Was passiert, wenn ein Benutzer beim Starten der Anwendung die Angabe eines Parameters vergisst? Nun, höchstwahrscheinlich müssen wir für solche Fälle einen Standardwert angeben. Es gibt zwei mögliche Orte, an denen Sie dies tun können: in der config.exs oder in der mix.exs Datei.

Die erste Option ist die bevorzugte, weil config.exs ist die Datei, die eigentlich verschiedene Konfigurationsoptionen speichern soll. Wenn Ihre Anwendung viele Umgebungsparameter hat, sollten Sie unbedingt dabei bleiben config.exs:

benutze Mix.Config config: sample, key:: value

Für eine kleinere Anwendung ist es jedoch in Ordnung, Umgebungswerte direkt im Inneren anzugeben mix.exs indem Sie die Anwendungsfunktion anpassen:

 def application do [extra_applications: [: logger], mod: Sample.Application, [], env: [# <==== key: :value ] ] end

Beispiel: Erstellen eines webbasierten CalcServers

Okay, um Anwendungen in Aktion zu sehen, ändern wir das Beispiel, das bereits in meinen GenServer- und Supervisors-Artikeln beschrieben wurde. Dies ist ein einfacher Rechner, mit dem Benutzer verschiedene mathematische Operationen ausführen und das Ergebnis ganz einfach abrufen können. 

Was ich tun möchte, ist, diesen Rechner webbasiert zu machen, damit wir POST-Anfragen senden können, um Berechnungen durchzuführen, und eine GET-Anfrage, um das Ergebnis zu erhalten.

Erstelle eine neue lib / calc_server.ex Datei mit folgendem Inhalt:

defmodule Sample.CalcServer verwendet GenServer. def start_link (initial_value) do GenServer.start_link (__ MODULE__, initial_value, name: __MODULE__) end def init (initial_value), wenn is_number (initial_value) do : ok, initial_value do def: (_) : stop, "Der Wert muss eine ganze Zahl sein!" end def add (number) do GenServer.cast (__ MODULE__, : add, number) end def Ergebnis do GenServer.call (__ MODULE__,: result) ende def handle_call (: result, _, state) do : antworten, state, state end def handle_cast (operation, state) do case operation do : add, number -> : noreply, state + number _ -> : stop, "nicht implementiert", state end end def terminate (_reason, _state) do IO.puts "Server beendet" end end

Wir werden nur Unterstützung für die hinzufügen Operation. Alle anderen mathematischen Operationen können auf dieselbe Weise eingeführt werden. Ich werde sie hier nicht aufführen, um den Code kompakter zu gestalten.

Das CalcServer nutzt GenServer, also bekommen wir child_spec automatisch und kann es von der Rückruffunktion wie folgt starten:

 def start (_type, _args) do children = [Sample.CalcServer, 0] opts = [strategie:: one_for_one, name: Sample.Supervisor] Supervisor.start_link (children, opts) endet

0 Hier ist das erste Ergebnis. Ansonsten muss es eine Zahl sein CalcServer wird sofort beendet.

Nun stellt sich die Frage, wie wir Web-Support hinzufügen. Dazu benötigen wir zwei Abhängigkeiten von Drittanbietern: Plug, der als Abstraktionsbibliothek fungiert, und Cowboy, der als tatsächlicher Webserver fungiert. Natürlich müssen wir diese Abhängigkeiten innerhalb von angeben mix.exs Datei:

 defp deps tun [: cowboy, "~> 1.1", : plug, "~> 1.4"] ende

Nun können wir die Plug-Anwendung unter unserem eigenen Überwachungsbaum starten. Passen Sie die Startfunktion folgendermaßen an:

 def start (_type, _args) do children = [Plug.Adapters.Cowboy.child_spec (: http, Sample.Router, [], [port: Application.fetch_env! (: sample,: port)])), Sample.CalcServer , 0] #… Ende

Hier bieten wir an child_spec und Einstellung Sample.Router um auf Anfragen zu antworten. Dieses Modul wird in Kürze erstellt. Was mir an diesem Setup nicht gefällt, ist, dass die Portnummer hartcodiert ist, was nicht wirklich bequem ist. Ich möchte es vielleicht beim Start der Anwendung optimieren, also speichern wir es stattdessen in der Umgebung:

Plug.Adapters.Cowboy.child_spec (: http, Sample.Router, [], [port: Application.fetch_env! (: Sample,: port)])

Geben Sie jetzt den Standard-Port-Wert in ein config.exs Datei:

config: Beispiel, Port: 8088

Großartig! 

Was ist mit dem Router? Erstelle eine neue lib / router.ex Datei mit folgendem Inhalt:

defmodule Sample.Router verwendet Plug.Router-Plug: Match-Plug: Versandende

Nun müssen wir einige Routen definieren, um die Addition durchzuführen und das Ergebnis abzurufen:

 get "/ result" do conn |> ok (to_string (Sample.CalcServer.result)) ende post "/ add" do fetch_number (conn) |> Sample.CalcServer.add conn |> ok Ende

Wir benutzen erhalten und Post Makros zur Definition der /Ergebnis und /hinzufügen Routen. Diese Makros setzen die conn Objekt für uns. 

OK und Abrufnummer sind private Funktionen, die auf folgende Weise definiert sind:

 defp fetch_number (conn) do Plug.Conn.fetch_query_params (conn) .params ["number"] |> String.to_integer end defp ok (conn, Daten \\ "OK") do send_resp conn, 200, Datenende

fetch_query_params / 2 gibt ein Objekt mit allen Abfrageparametern zurück. Uns interessiert nur die Nummer, die der Benutzer an uns sendet. Alle Parameter sind anfangs Strings, daher müssen wir sie in eine Ganzzahl konvertieren.

send_resp / 3 sendet eine Antwort mit dem bereitgestellten Statuscode und einem Body an den Client. Wir werden hier keine Fehlerprüfung durchführen, daher wird der Code immer so sein 200, Das heißt, alles ist in Ordnung.

Und das ist es! Jetzt können Sie die Anwendung auf eine der oben genannten Arten starten (z. B. durch Eingabe) iex -S mix) und benutze die locken Werkzeug zur Durchführung der Anfragen:

curl http: // localhost: 8088 / result # => 0 curl http: // localhost: 8088 / add? number = 1 -X POST # => OK curl http: // localhost: 8088 / result # => 1

Fazit

In diesem Artikel haben wir Elixir-Anwendungen und deren Zweck beschrieben. Sie haben gelernt, wie Sie Anwendungen erstellen, verschiedene Arten von Informationen bereitstellen und Abhängigkeiten auflisten mix.exs Datei. Sie haben auch erfahren, wie Sie die Konfiguration in der App-Umgebung speichern und wie Sie Ihre Anwendung starten können. Zuletzt haben wir Anwendungen in Aktion gesehen und einen einfachen webbasierten Rechner erstellt.

Vergessen Sie nicht, dass die hex.pm-Website viele Hunderte von Anwendungen von Drittanbietern enthält, die zur Verwendung in Ihren Projekten bereit sind. Stellen Sie also sicher, dass Sie den Katalog durchsuchen und die für Sie passende Lösung auswählen! 

Hoffentlich fanden Sie diesen Artikel nützlich und interessant. Ich danke dir, dass du bei mir bleibst und bis zum nächsten Mal.