Lernen Sie Phoenix kennen: Ein Rails-ähnliches Framework für moderne Web-Apps auf Elixir
Veröffentlicht: 2022-03-11Das Phoenix-Framework erfreut sich rasant wachsender Beliebtheit und bietet die Produktivität von Frameworks wie Ruby on Rails, während es gleichzeitig eines der schnellsten verfügbaren Frameworks ist. Es bricht mit dem Mythos, dass man Leistung opfern muss, um die Produktivität zu steigern.
Was genau ist Phoenix?
Phoenix ist ein Web-Framework, das mit der Programmiersprache Elixir erstellt wurde. Elixir, das auf der Erlang-VM aufbaut, wird für den Aufbau von fehlertoleranten, verteilten Systemen mit niedriger Latenz verwendet, die zunehmend notwendige Eigenschaften moderner Webanwendungen sind. Mehr über Elixir erfährst du in diesem Blog-Beitrag oder im offiziellen Leitfaden.
Wenn Sie Ruby on Rails-Entwickler sind, sollten Sie sich wegen der Leistungssteigerungen, die es verspricht, unbedingt für Phoenix interessieren. Entwickler anderer Frameworks können ebenfalls mitverfolgen, wie Phoenix an die Webentwicklung herangeht.
In diesem Artikel werden wir einige der Dinge in Phoenix erfahren, die Sie beachten sollten, wenn Sie aus der Welt von Ruby on Rails kommen.
Im Gegensatz zu Ruby ist Elixir eine funktionale Programmiersprache, was wahrscheinlich der größte Unterschied ist, mit dem Sie sich auseinandersetzen müssen. Es gibt jedoch einige wesentliche Unterschiede zwischen diesen beiden Plattformen, die jeder kennen sollte, der Phoenix lernt, um seine Produktivität zu maximieren.
Namenskonventionen sind einfacher.
Dies ist ein kleines, aber es ist leicht, es zu vermasseln, wenn Sie von Ruby on Rails kommen.
In Phoenix ist es üblich, alles im Singular zu schreiben. Sie hätten also einen „UserController“ anstelle eines „UsersController“, wie Sie es in Ruby on Rails tun würden. Dies gilt überall, außer bei der Benennung von Datenbanktabellen, wo die Konvention darin besteht, die Tabelle im Plural zu benennen.
Dies scheint keine große Sache zu sein, nachdem ich gelernt habe, wann und wo die Pluralform in Ruby on Rails verwendet wird, aber es war anfangs etwas verwirrend, als ich lernte, Ruby on Rails zu verwenden, und ich bin sicher, dass es viele verwirrt hat andere auch. Phoenix gibt Ihnen eine Sorge weniger.
Das Routing ist einfacher zu verwalten.
Phoenix und Ruby on Rails sind sich beim Routing sehr ähnlich. Der Hauptunterschied besteht darin, wie Sie die Verarbeitung einer Anfrage steuern können.
In Ruby on Rails (und anderen Rack-Anwendungen) erfolgt dies über Middleware, während dies in Phoenix mit sogenannten „Plugs“ erfolgt.
Plugs sind das, was Sie verwenden, um eine Verbindung zu verarbeiten.
Beispielsweise protokolliert die Middleware Rails::Rack::Logger
eine Anfrage in Rails, was in Phoenix mit dem Plug.Logger
-Plug geschehen würde. Grundsätzlich kann alles, was Sie in Rack-Middleware tun können, mit Plugs ausgeführt werden.
Hier ist ein Beispiel für einen Router in Phoenix:
defmodule HelloWorld.Router do use HelloWorld.Web, :router pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers end pipeline :api do plug :accepts, ["json"] end scope "/", HelloWorld do pipe_through :browser get "/", HelloController, :index end scope "/api", HelloWorld do pipe_through :api end end
Schauen wir uns zunächst die Pipelines an.
Dies sind Gruppen von Steckern, durch die die Anfrage läuft. Betrachten Sie es als einen Middleware-Stack. Diese können beispielsweise verwendet werden, um zu überprüfen, ob die Anforderung HTML erwartet, die Sitzung abzurufen und sicherzustellen, dass die Anforderung sicher ist. Dies geschieht alles, bevor es den Controller erreicht.
Da Sie mehrere Pipelines angeben können, können Sie auswählen, welche Plugs für bestimmte Routen erforderlich sind. In unserem Router haben wir beispielsweise eine andere Pipeline für Seiten- (HTML) und API-Anfragen (JSON).
Es ist nicht immer sinnvoll, genau dieselbe Pipeline für verschiedene Arten von Anfragen zu verwenden. Phoenix gibt uns diese Flexibilität.
Ansichten und Vorlagen sind zwei verschiedene Dinge.
Ansichten in Phoenix sind nicht dasselbe wie Ansichten in Ruby on Rails.
Ansichten in Phoenix sind für das Rendern von Vorlagen und das Bereitstellen von Funktionen zuständig, die die Verwendung von Rohdaten für die Vorlagen vereinfachen. Eine Ansicht in Phoenix ähnelt am ehesten einem Helfer in Ruby on Rails, mit dem Zusatz, dass das Template gerendert wird.
Lassen Sie uns ein Beispiel schreiben, das „Hello, World!“ anzeigt. im Browser.
Rubin auf Schienen:
app/controllers/hello_controller.rb:
class HelloController < ApplicationController def index end end
app/views/hello/index.html.erb:
<h1>Hello, World!</h1>
Phönix:
web/controllers/hello_controller.ex:
defmodule HelloWorld.HelloController do use HelloWorld.Web, :controller def index(conn, _params) do render conn, "index.html" end end
web/views/hello_view.ex:
defmodule HelloWorld.HelloView do use HelloWorld.Web, :view end
web/templates/hello/index.html.eex:
<h1>Hello, World!</h1>
Diese Beispiele zeigen beide „Hello, World!“ im Browser, weisen jedoch einige wesentliche Unterschiede auf.
Zunächst müssen Sie in Phoenix anders als in Ruby on Rails explizit das Template angeben, das Sie rendern möchten.
Als nächstes mussten wir eine Ansicht in Phoenix einbinden, die zwischen dem Controller und dem Template steht.
Jetzt fragen Sie sich vielleicht, warum wir eine Aussicht brauchen, wenn sie leer ist? Der Schlüssel hier ist, dass Phoenix unter der Haube die Vorlage in eine Funktion kompiliert, die ungefähr dem folgenden Code entspricht:
defmodule HelloWorld.HelloView do use HelloWorld.Web, :view def render("index.html", _assigns) do raw("<h1>Hello, World!</h1>") end end
Sie können die Vorlage löschen und die neue Renderfunktion verwenden, und Sie erhalten dasselbe Ergebnis. Dies ist nützlich, wenn Sie nur Text oder sogar JSON zurückgeben möchten.
Modelle sind genau das: Datenmodelle.
In Phoenix behandeln Modelle hauptsächlich die Datenvalidierung, ihr Schema, ihre Beziehungen zu anderen Modellen und wie sie dargestellt werden.
Das Angeben des Schemas im Modell mag zunächst seltsam klingen, aber es ermöglicht Ihnen, auf einfache Weise „virtuelle“ Felder zu erstellen, bei denen es sich um Felder handelt, die nicht in der Datenbank gespeichert werden. Schauen wir uns ein Beispiel an:
defmodule HelloPhoenix.User do use HelloPhoenix.Web, :model schema "users" do field :name, :string field :email, :string field :password, :string, virtual: true field :password_hash, :string end end
Hier definieren wir vier Felder in der „users“-Tabelle: name, email, password und password_hash.
Hier ist nicht viel interessant, außer dem Feld „Passwort“, das auf „virtuell“ gesetzt ist.
Dadurch können wir dieses Feld setzen und abrufen, ohne die Änderungen in der Datenbank zu speichern. Es ist nützlich, weil wir Logik hätten, um dieses Passwort in einen Hash umzuwandeln, den wir im Feld „password_hash“ speichern und dann in der Datenbank speichern würden.
Sie müssen noch eine Migration erstellen; Das Schema im Modell wird benötigt, da die Felder nicht wie in Ruby on Rails automatisch in das Modell geladen werden.
Der Unterschied zwischen Phoenix und Ruby on Rails besteht darin, dass das Modell nicht die Persistenz der Datenbank übernimmt. Dies wird von einem Modul namens „Repo“ gehandhabt, das mit den Datenbankinformationen konfiguriert wird, wie im folgenden Beispiel gezeigt:
config :my_app, Repo, adapter: Ecto.Adapters.Postgres, database: "ecto_simple", username: "postgres", password: "postgres", hostname: "localhost"
Dieser Code ist in den umgebungsspezifischen Konfigurationsdateien wie config/dev.exs
oder config/test.exs
. Dies ermöglicht es uns, Repo dann zu verwenden, um Datenbankoperationen wie Erstellen und Aktualisieren durchzuführen.

Repo.insert(%User{name: "John Smith", example: "[email protected]"}) do {:ok, user} -> # Insertion was successful {:error, changeset} -> # Insertion failed end
Dies ist ein gängiges Beispiel bei Controllern in Phoenix.
Wir geben einem Benutzer einen Namen und eine E-Mail und Repo versucht, einen neuen Datensatz in der Datenbank zu erstellen. Wir können dann optional den erfolgreichen oder fehlgeschlagenen Versuch behandeln, wie im Beispiel gezeigt.
Um diesen Code besser zu verstehen, müssen Sie den Musterabgleich in Elixir verstehen. Der von der Funktion zurückgegebene Wert ist ein Tupel. Die Funktion gibt ein Tupel aus zwei Werten, einen Status und dann entweder das Modell oder ein Changeset zurück. Ein Changeset ist eine Möglichkeit, Änderungen nachzuverfolgen und ein Modell zu validieren (Changesets werden im nächsten Abschnitt behandelt).
Das erste Tupel, von oben nach unten, das mit dem Muster des Tupels übereinstimmt, das von der Funktion zurückgegeben wird, die versucht hat, den Benutzer in die Datenbank einzufügen, führt seine definierte Funktion aus.
Wir hätten anstelle eines Atoms (was im Grunde ein Symbol in Ruby ist) eine Variable für den Status setzen können, aber dann würden wir sowohl erfolgreiche als auch fehlgeschlagene Versuche abgleichen und für beide Situationen dieselbe Funktion verwenden. Indem wir angeben, welches Atom wir abgleichen möchten, definieren wir eine Funktion speziell für diesen Status.
Ruby on Rails verfügt über die Persistenzfunktionalität, die über ActiveRecord in das Modell integriert ist. Dies verleiht dem Modell mehr Verantwortung und kann das Testen eines Modells dadurch manchmal komplexer machen. In Phoenix wurde dies sinnvoll getrennt und verhindert, dass jedes Modell mit Persistenzlogik aufgebläht wird.
Changesets ermöglichen klare Validierungs- und Transformationsregeln.
In Ruby on Rails kann das Validieren und Transformieren von Daten die Quelle schwer zu findender Fehler sein. Dies liegt daran, dass es nicht sofort offensichtlich ist, wenn Daten durch Callbacks wie „before_create“ und Validierungen transformiert werden.
In Phoenix führen Sie diese Validierungen und Transformationen explizit mithilfe von Änderungssätzen durch. Dies ist eines meiner Lieblingsfeatures in Phoenix.
Werfen wir einen Blick auf ein Changeset, indem wir eines zum vorherigen Modell hinzufügen:
defmodule HelloPhoenix.User do use HelloPhoenix.Web, :model schema "users" do field :name, :string field :email, :string field :password, :string, virtual: true field :password_hash, :string end def changeset(struct, params \\ %{}) do struct |> cast(params, [:name, :email, :password]) |> validate_required([:email, :password]) end end
Hier macht das Changeset zwei Dinge.
Zuerst ruft es die „cast“-Funktion auf, die eine Whitelist zulässiger Felder ist, ähnlich wie „strong_parameters“ in Ruby on Rails, und validiert dann, dass die Felder „email“ und „password“ enthalten sind, wodurch das Feld „name“ erstellt wird Optional. Auf diese Weise können nur die von Ihnen zugelassenen Felder von Benutzern geändert werden.
Das Schöne an diesem Ansatz ist, dass wir nicht auf ein Changeset beschränkt sind. Wir können mehrere Änderungssätze erstellen. Ein Changeset für die Registrierung und eines für die Aktualisierung des Benutzers ist üblich. Vielleicht möchten wir das Passwortfeld nur bei der Registrierung verlangen, aber nicht bei der Aktualisierung des Benutzers.
Vergleichen Sie diesen Ansatz mit dem, was normalerweise in Ruby on Rails gemacht wird, Sie müssten angeben, dass die Validierung nur bei „create“ ausgeführt werden soll. Dies macht es manchmal schwierig, in Rails herauszufinden, was Ihr Code tut, wenn Sie ein komplexes Modell haben.
Das Importieren von Funktionen ist unkompliziert und dennoch flexibel.
Ein Großteil der Funktionalität einer Bibliothek namens „Ecto“ wird in die Modelle importiert. Modelle haben normalerweise diese Linie oben:
use HelloPhoenix.Web, :model
Das Modul „HelloPhoenix.Web“ befindet sich in „web/web.ex“. Im Modul sollte es eine Funktion namens „model“ wie folgt geben:
def model do quote do use Ecto.Schema import Ecto import Ecto.Changeset import Ecto.Query end end
Hier können Sie sehen, welche Module wir von Ecto importieren. Sie können hier beliebige andere Module entfernen oder hinzufügen, die in alle Modelle importiert werden.
Es gibt auch ähnliche Funktionen wie „View“ und „Controller“, die den gleichen Zweck für die Views bzw. Controller erfüllen.
Die Schlüsselwörter quote
und use
können verwirrend erscheinen. In diesem Beispiel können Sie sich ein Zitat so vorstellen, dass dieser Code direkt im Kontext des Moduls ausgeführt wird, das diese Funktion aufruft. Es ist also gleichbedeutend mit dem Schreiben des Codes in Anführungszeichen im Modul.
Mit dem Schlüsselwort use können Sie auch Code im Kontext dessen ausführen, wo er aufgerufen wird. Es erfordert im Wesentlichen das angegebene Modul und ruft dann das __using__
Makro auf dem Modul auf, das es in dem Kontext ausführt, in dem es aufgerufen wurde. Sie können mehr über das Zitieren und die Verwendung im offiziellen Leitfaden lesen.
Dies hilft Ihnen wirklich zu verstehen, wo sich bestimmte Funktionen im Framework befinden, und trägt dazu bei, das Gefühl zu verringern, dass das Framework viel „Magie“ macht.
Parallelität steht im Mittelpunkt.
Nebenläufigkeit ist in Phoenix kostenlos, da sie ein Hauptmerkmal von Elixir ist. Sie erhalten eine Anwendung, die mehrere Prozesse hervorbringen und auf mehreren Kernen ausführen kann, ohne sich Gedanken über Thread-Sicherheit und -Zuverlässigkeit machen zu müssen.
So einfach können Sie einen neuen Prozess in Elixir erzeugen:
spawn fn -> 1 + 2 end
Alles nach dem spawn
und vor dem end
wird in einem neuen Prozess ausgeführt.
Tatsächlich wird jede Anfrage in Phoenix in einem eigenen Prozess behandelt. Elixir nutzt die Leistung der Erlang-VM, um zuverlässige und effiziente Parallelität „kostenlos“ bereitzustellen.
Dies macht Phoenix auch zu einer guten Wahl für die Ausführung von Diensten, die WebSockets verwenden, da WebSockets eine offene Verbindung zwischen dem Client und dem Server aufrechterhalten müssen (was bedeutet, dass Sie Ihre Anwendung so erstellen müssen, dass sie möglicherweise Tausende gleichzeitiger Verbindungen verarbeiten kann).
Diese Anforderungen würden ein Projekt, das auf Ruby on Rails basiert, erheblich komplizieren, aber Phoenix kann diese Anforderungen kostenlos über Elixir erfüllen.
Wenn Sie WebSockets in Ihrer Phoenix-Anwendung verwenden möchten, müssen Sie Kanäle verwenden. Es ist das Äquivalent zu ActionCable
in Ruby on Rails, aber weniger komplex einzurichten, da Sie keinen separaten Server betreiben müssen.
Phoenix macht das Erstellen moderner Web-Apps zum Kinderspiel.
Während wir uns hauptsächlich auf die Unterschiede konzentriert haben, hat Phoenix einige Dinge mit Ruby on Rails gemeinsam.
Phoenix folgt ungefähr dem gleichen MVC-Muster wie Ruby on Rails, also sollte es jetzt, da Sie die Hauptunterschiede kennen, nicht schwierig sein, herauszufinden, welcher Code wohin gehört. Phoenix hat auch ähnliche Generatoren wie Ruby on Rails zum Erstellen von Modellen, Controllern, Migrationen und mehr.
Nachdem Sie Elixir gelernt haben, werden Sie sich langsam der Produktivität von Ruby on Rails annähern, sobald Sie sich mit Phoenix wohler fühlen.
Die wenigen Male, in denen ich mich nicht so produktiv fühle, sind, wenn ich auf ein Problem stoße, das von einem Juwel in Ruby gelöst wurde, und ich keine ähnliche Bibliothek für Elixir finden kann. Glücklicherweise werden diese Lücken langsam von der wachsenden Elixir-Community gefüllt.
Die Unterschiede in Phoenix helfen, viele Probleme zu lösen, die mit der Verwaltung komplexer Ruby on Rails-Projekte verbunden sind. Auch wenn sie nicht alle Probleme lösen, helfen sie Ihnen doch, Sie in die richtige Richtung zu lenken. Die Wahl eines langsamen Frameworks, weil Sie Produktivität wollen, ist keine gültige Entschuldigung mehr, Phoenix bietet Ihnen beides.