Ist das Phoenix Framework die Killer-App von Elixir?

18.04.2022Raffael Schneider
Cloud Elixir Realtime Distributed Systems concurrent Otp Erlang Beam functional-paradigm actor-model b-nova techup Stay Tuned

Das Jahr 2022 steht für mich persönlich ganz im Zeichen von Distributed Systems, zu Deutsch verteilte Systeme. Aus diesem Grund befasse ich mich in einer neuen TechUp-Serie mit verteilten Systemen im Allgemeinen, wie auch deren Konzipierung mit Elixir und dessen Ökosystem. Zuerst legen wir den Fokus auf Elixir, um Grundkonzepte von verteilten Systemen praktisch mit einer Programmiersprache dingfest zu machen. Danach werden wir uns theoretische Konzepte sowie weiterführende Themen rund um verteilte Systeme anschauen. Dies wird in die folgenden zwei TechUp-Serien aufgeteilt:

  • Elixir Series
  • Distributed System Series

Dies ist Teil 3 der Elixir-Series. Falls du nicht schon von dort kommst, empfehle ich dir unbedingt erst Teil 2 zu lesen. Für die, die erst mit diesem TechUp in das Thema Elixir und Distributed Systems einsteigen, hier ein kleiner Abriss über Elixir:

Elixir ist eine relativ neue Programmiersprache aus dem Jahr 2014, welche neben dem funktionalen Paradigma auch die “Open Telecom Platform” von Erlang als Runtime-Target im Einsatz hat. Die OTP ist eine perfekte Runtime für verteilte Systeme und stellt alle nötigen Komponenten bereit, um ein solches System zu programmieren. Heutzutage versuchen wir, ähnliche Konzepte durch Container-Orchestrierung mit Kubernetes zu bewerkstelligen.

Im heutigen TechUp geht es um das Phoenix Framework. Ein Web-Framework, welches gerne als die “Killer-App” von Elixir bezeichnet wird. Lass uns gemeinsam Phoenix kennenlernen und versuchen, dessen Eigenschaften besser zu verstehen. 😄

Das Phoenix Framework

Programmiersprachen zeichnen sich nicht selten durch ihr Ökosystem, ebenso wie ihre Fülle an Libraries und Frameworks aus. Bei Elixir ist das nicht anders, hier wird mit dem Phoenix Framework ein grosses Geschütz in Sachen Web-Frameworks aufgefahren. Phoenix ist nämlich nicht nur das Hauptframework von Elixir schlechthin, sondern liefert eine Vielzahl von Features, die die Web-Applikationsentwicklung mit Phoenix stark vereinfachen und zeitgleich eine robustere Architektur ermöglichen.

Phoenix ist ein MVC-Web-Framework, welches komplett auf der BEAM, der Virtual Machine im Herzen von OTP läuft. Hinter der Software steht offiziell zwar keine Firma aber es sind vor allem Mitarbeiter und Leute rund um die Consulting-Firma DockYard, die die Entwicklung des Frameworks vorantreiben. Die aktuelle Release-Version des Frameworks liegt – zur Zeit der Verfassung dieses Artikels – in der Version 1.6.0 vor, welche Ende August 2021 veröffentlicht wurde.

MVC steht für Model-View-Controller und ist ein Software Design Pattern, welches jedem b-nova-Mitarbeiter ein Begriff sein sollte…

Asynchron und Real-Time durch das bewährte PubSub-Modell

Phoenix nutzt das Prozessbasierte Actor Model, wobei die getrennten Einheiten Channels genannt werden. Ein Channel ist auch Message-basiert und kommuniziert über ein Topic mit dem PubSub-Server, welcher Phoenix mit ausliefert. Ein PubSub-Instanz synchronisiert sich automatisch über Instanzen auf anderen Nodes. Zwischen einem Channel und einem potenziellen Client wie typischerweise einem Web-Browser, auf welchem gerade eine Session läuft, wird über ein WebSocket eine asynchrone Verbindung hergestellt. Die asynchrone Kommunikation wird dadurch gewährleistet.

Noch ein Wort zu WebSockets

Ein WebSocket ist genau wie HTTP ein Kommunikationsprotokoll über TCP. Da es aber kein HTTP ist, unterliegt es einem anderen Standard, nämlich dem RFC 6455. Dabei ist erwähnenswert, dass ein Vollduplex (zu Deutsch auch Gegenbetrieb) zwischen beiden Klienten ermöglicht wird. Genauer gesagt heisst das, dass zeitgleich Daten ausgetauscht werden können und nicht etwa wie beim Request/Response-Modell von HTTP eine neue Connection aufgebaut werden muss.

WebSockets kommen bei Applikationen zur Verwendung, wo ein zeitnaher, hochaktueller Datenstream, sprich Real-Time Web notwendig ist. Dies ist beispielsweise bei Trading-Plattformen relevant, wo Preisfluktuationen in Echtzeit angezeigt werden müssen. Natürlich werden WebSockets bei Chat-Plattformen, Streaming oder typischerweise auch bei Gaming-Servern verwendet. WebSocket wird als Standard von allen gebräuchlichen Browsern unterstützt.

Machen wir ein kurzes Beispiel mit einem Client-Request und einer Server-Response. Der Client-Request könnte wie folgt aussehen:

1
2
3
4
5
6
7
8
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

Die dazugehörige Server-Response würde einen Handshake für das Upgrade auf das Websocket-Protokoll an den Client zurücksenden, wonach die Websocket-basierten Duplex-Verbindungen entstehen.

1
2
3
4
5
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

Der folgende Datenaustausch würde dann ab sofort, genau wie bei HTTP, auf Layer 7 erfolgen und TCP im Layer 4 vorraussetzen. Websocket ist somit eine interessante Alterantive zum klassischen HTTP, die es erlaubt, in Realzeit Duplex-Verbindungen aufzubauen, welche so lange als offen gelten, bis diese von einem der beiden Enden abgebrochen wird.

Frontend und Backend in einem dank einzigartiger LiveView

Wenn wir uns die Kommunikation über das WebSocket etwas genauer anschauen, so sehen wir, dass über Events gegen das Backend auch das HTML im Frontend aktualisiert wird. Dieser Prozess von HTML-Diffs gegen Events heisst bei Phoenix LiveView und ist ein hauseigenes Feature, welches gerade in einer dezentralisierten Struktur über Channels gewisse Vorteile mit sich bringt.

PETAL anstatt von PWA

Das Phoenix Framework und dessen LiveView-Funktionalität hat auch andere Web-Frameworks inspiriert, eine gleichwertige Lösung zu fertigen. Da wären beispielsweise Laravel LiveWire oder auch Rails Hotwire zu nennen. Der Ansatz ist dabei der gleich; Statt klassisches JSON wird HTML zwischen Frontend und Backend genutzt. Dadurch konnten Abstraktionsebenen entfernt werden, welche bis dato als gegeben gesehen wurden.

Neben dem Transportlayer sind auch andere Abstraktionsebenen im Phoenix-Umfeld obsolet, da man nicht mehr nach den bewährten Prinzipien einer konventionellen Single-Page Application gehen muss. Mit Phoenix in Kombination mit LiveView kann man auch interaktive Webapplikationen bauen, was viele Teilbereiche obsolet macht. Nennenswert wären sicher folgende Komponenten:

  • JSON-Serialisierung vom Server zum Client
  • JSON-Serialisierung vom Client zum Server
  • REST-Controller(s)
  • JavaScript routing
  • JavaScript Data-Store-Lösungen
  • GraphQL-Teilkomponenten wie GraphQL-Typesystem, Resolver, Queries oder Mutationen

Mit dem Phoenix Framework lassen sich unterschiedliche Frontend-Lösungen einsetzen. In der Community wird vorzugsweise ein Toolset namens PETAL genutzt. PETAL ist ein Akronym und steht für folgende Software-Komponenten:

  • Phoenix
  • Elixir
  • TailwindCSS
  • Alpine.js
  • LiveView

Neben Elixir, Phoenix und dessen eigene LiveView-Funktionalität, wird gerne TailwindCSS für das Styling und Alpine.js für Frontend-Logik verbaut. Ja, jetzt kann argumentiert werden, dass weiterhin JavaScript im Frontend eingesetzt wird und somit kein Mehrwert erzielt wird. Es ist klar, dass das Phoenix Framework JavaScript nicht gänzlich ersetzen kann, denn gewisse Komponenten wie beispielsweise ein Popup-Menü müssen weiterhin mit JavaScript bewerkstelligt werden. Im Gegenzug fällt jedoch die ganze Logik der Datenmodellierung aus dem Backend auf den Client weg. Die Nutzung von Alpine.js beschränkt sich idealerweise nur auf rein frontendseitige interaktive Elemente.

Alpine.js wurde spezifisch für diesen Anwendungsfall entwickelt und geht auf den Laravel LiveWire-Entwickler Caleb Porzio zurück. Das Tool enthält mit einem eingeschränkten Set von Elementen, nämlich 15 Attributen, 6 Properties und 2 Methoden nur das nötigste.

TailwindCSS ist ein Utiliy-first CSS-Framework, mit dem man direkt im HTML die nötigen Klassen und Attribute setzt, welche für das gewünschte Element nötig sind. In Kombination mit dem restlichen Stack ergibt sich ein Gesamtbild, wobei alles im HTML gebaut werden kann. Das ist die perfekte Ausgangslage für ein reines Backend-fokussiertes Web-Framework wie Phoenix.

Ecto

Ecto ist der Object Relational Mapper, kurz ORM, für Elixir und auch der Standard-ORM für das Phoenix Framework.

Ecto unterstützt folgende Datenbanken:

  • Database

    • PostgreSQL
    • MySQL
    • MSSQL
    • SQLite3
    • ETS
  • Ecto Adapter

    • Ecto.Adapters.Postgres
    • Ecto.Adapters.MyXQL
    • Ecto.Adapters.Tds
    • Ecto.Adapters.SQLite3
    • Etso

ETS steht für Erlang Term Storage und ist ein Built-in Speicher für die Erlang Runtime, mit dem sehr grosse Datensätze zur Laufzeit in der Runtime persistiert werden können. Dies ergibt eine optimale Methode, verteilte Daten gleichzeitig zu verwenden.

Installation von Phoenix

Wir können Phoenix in der neusten Version 1.6.6 (zum Zeitpunkt der Verfassung des TechUps) ganz einfach mit Hex installieren lassen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
❯ mix archive.install hex phx_new
Resolving Hex dependencies...
Dependency resolution completed:
New:
  phx_new 1.6.5
* Getting phx_new (Hex package)
All dependencies are up to date
Compiling 11 files (.ex)
Generated phx_new app
Generated archive "phx_new-1.6.5.ez" with MIX_ENV=prod
Are you sure you want to install "phx_new-1.6.5.ez"? [Yn] Y
* creating /Users/rschneider/.mix/archives/phx_new-1.6.5

Phoenix bietet mit phx.new einen Quickstart, mit dem wir ganz einfach ein Phoenix Templateprojekt aufsetzen können. Das dauert beim ersten Aufsetzen bis zu einer Minute, da dort noch gewisse Dependencies gezogen werden. Wir nennen unsere Phoenix-App einfach mal webservice und setzen das Projekt mit mix phx.new webservice wie folgt auf:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
❯ mix phx.new webservice --live --no-ecto
* creating webservice/config/config.exs
...
* creating webservice/priv/static/favicon.ico

Fetch and install dependencies? [Yn] Y
* running mix deps.get
* running mix deps.compile

We are almost there! The following steps are missing:

    $ cd webservice

Start your Phoenix app with:

    $ mix phx.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phx.server

Das --no-ecto-Flag bezeichnet einfach den Einsatz von Phoenix ohne ORM mit Ecto, womit die Notwendigkeit extra eine Datenbank aufsetzen zu müssen, bevor man Phoenix ausprobieren kann, wegfällt. Nun können wir unsere REPL wie gewohnt starten:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
❯ iex -S mix phx.server
Erlang/OTP 24 [erts-12.1.5] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [dtrace]

Compiling 13 files (.ex)
Generated webservice app
[info] Running WebserviceWeb.Endpoint with cowboy 2.9.0 at 127.0.0.1:4000 (http)
[debug] Downloading esbuild from https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.0.tgz
[info] Access WebserviceWeb.Endpoint at http://localhost:4000
Interactive Elixir (1.13.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>

Unter localhost:4000 können wir dann unser Web-Framework aufrufen.

Wie man sieht gibt Phoenix schon eine Startseite vor und zeigt bereits ein sehr hilfreiches Dashboard unter http://localhost:4000/dashboard/home an.

Templates

EEx steht für Embedded Elixir und bezeichnet die hauseigene Template-Engine von Elixir. Die offizielle Dokumentation dazu beschreibt die wichtigsten Eigenschaften der Engine.

EEx ist auch die standardmässige Template-Engine von Phoenix und vergleichbar mit dem Ruby-Äquivalent ERB. Standardmässig leben die Templates in einem lib/module_name/templates/-Verzeichnis und werden nach der zu rendernden View benannt. Beispielsweise könnte ein Template für eine Testseite unter einem Verzeichnis lib/hello_web/templates/page/test.html.eex liegen und die test.html.eex würde so aussehen:

1
2
3
<div class="jumbotron">
  <p><%= handler_info @conn %></p>
</div>

Das Beispiel kommt direkt aus der Phoenix-spezifischen Dokumentation über Templates.

Nächster Schritt: Ein Twitter-Klon als POC

In der nächsten Folge der Elixir Series werden wir einen Twitter-Klon in Elixir schreiben und diese Applikation auf einer für Elixir konzipierten Plattform hosten lassen.

Phoenix Framework Website

Phoenix Dokumentation

Elixir Website

Petal - What it is and why you might like it