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:
|
|
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.
|
|
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.
|
|
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:
|
|
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:
|
|
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:
|
|
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.