Vapor 3

Vapor ist ein Swift-Framework zur Programmierung von Server-Anwendungen. Zwar ist es möglich, damit z.B. auch einen eigenen Webserver zu gestalten, in der Praxis werden mit Vapor aber überwiegend REST-APIs implementiert.
Derartige APIs können testweise unter macOS laufen; für den Praxisbetrieb kommt dann zumeist ein eigener Ubuntu-Server oder eine entsprechende Cloud-Instanz zum Einsatz.

Als ich im August 2017 für meinem Buch Swift 4 ein Kapitel über Vapor verfasste, dachte ich, in ein paar Wochen würde die aktualisierte Version Vapor 3 fertig werden, die dann (vollständig) Swift-4-kompatibel wäre. Da habe ich mich gleich in zweierlei Hinsicht getäuscht: Zum einen dauerte die Fertigstellung von Vapor 3 bis Mai 2018. Zum anderen war es kein kleines Update, sondern weitestgehend eine Neuimplementierung dieses Frameworks.

Neu in Vapor 3

Bis auf ein paar Grundkonzepte ist in Vapor 3 kein Stein auf dem anderen geblieben; auch nahezu alle Bibliotheken wurden erneuert oder umfassend verändert:

  • Asynchron: Vapor 3 arbeitet nun vollständig asynchron und greift dabei auf Futures zurück (Doku: Async, Async Overview).

  • Codable: Anstelle der Vapor-2-eigenen JSON-Klasse greift Vapor 3 umfassend auf das in Swift 4 eingeführte Codable-Protokoll zurück. Das gibt mehr Typsicherheit in eigenen Datenmodellen. (Doku: Content)

  • Projektkonfiguration: In Vapor 2 wurden einige Aspekte der Konfiguration in JSON-Dateien ausgedrückt (z.B. Config/server.json). Diese Dateien werden in Vapor 3 nicht mehr unterstützt. Stattdessen erfolgt die Konfiguration durch Swift-Code, der Service-Strukturen verändert (Doku: Services-Struktur, Using Services).

  • Bibliotheken: Vapor 3 verwendet überwiegend dieselben Bibliotheken wie Vapor 2, also z.B. Crypto, Fluent, HTTP, MySQL, PostgreSQL, SQLite und natürlich Vapor. Allerdings wurden alle Bibliotheken grundlegend modernisiert und z.T. umfassend verändert. Die HTTP-Bibliothek greift jetzt auf das von Apple entwickelte NIO-Framework zurück (Doku: Vapor Docs, Swift NIO Intro, Swift NIO auf GitHub). Einige Bibliotheken wurden ganz entfernt, weil sie aufgrund von neuen Konzepten nicht mehr benötigt werden. Das betrifft unter anderem Droplet (-> NIO), Config (-> Service) und JSON (-> Codable).

Die Portierung vorhandener Vapor-Projekte auf Version 3 ist deswegen weitgehend unmöglich. Die Vapor-Dokumentation empfiehlt, ein neues Projekt in Vapor 3 zu starten und vorhandenen Code schrittweise und manuell zu migrieren und an die neuen Vapor-Konzepte anzupassen.

In seiner Anwendung weitgehend unverändert geblieben ist immerhin das Kommando vapor (die »Toolbox«, Doku), mit dem Vapor-Projekte eingerichtet und verwaltet werden. Intern greift das vapor-Kommando stark auf den Swift Package Manager zurück.

Installation unter macOS

Voraussetzung für die Verwendung von Vapor unter macOS ist einerseits natürlich Xcode (ich habe für diesen Test mit Xcode 9.3 / Swift 4.1 gearbeitet), andererseits der Paketmanager Homebrew. Sie installieren Vapor wie folgt im Terminal:

brew install vapor

Mit vapor version (wieder im Terminal) können Sie überprüfen, ob alles geklappt hat:

vapor version
  Vapor Toolbox: 3.1.7

Hello World!

Um Vapor kennenzulernen, richten Sie ein Hello-World-Projekt ein. Das Kommando vapor new unterstützt Sie dabei, greift aber auf git zurück. Wenn Sie git bisher nicht bzw. nur aus Xcode heraus verwendet haben, müssen Sie deswegen zuerst eine minimale git-Konfiguration durchführen und Ihren Namen und Ihre E-Mail-Adresse speichern:

git config --global user.email "you@example.com"
git config --global user.name "Your Name"

vapor new <Name> richtet ein neues Projekt ein:

cd <in ein Verzeichnis Ihrer Wahl>
vapor new Hello --verbose

Der so initialisierte Muster-Code besteht aus etlichen Dateien. Um das Projekt zu kompilieren und auszuführen, wechseln Sie in das Projektverzeichnis und rufen das Kommando vapor build auf. Das dauert beim ersten Mal circa eine Minute, weil erst jetzt die für das Projekt erforderlichen externen Bibliotheken heruntergeladen und kompiliert werden. Weitere Build-Vorgänge verlaufen dann wesentlich schneller. Bei meinen Tests scheiterte der erste Durchlauf von vapor build manchmal mit einem Fehler. Beim zweiten Versuch klappte es dann.

cd Hello
vapor build --verbose
    ...
    Building Project [Done]

Sie starten das Programm mit vapor run. Die Server-App läuft nun, bis Sie diese mit Strg+C beenden. Während sie läuft, können Sie mit ihr in einem Webbrowser über den Port 8080 kommunizieren. Die Adresse http://localhost:8080/hello führt in die Hello-World-Zeile.

»Hello World«-Seite eines Vapor-Projekts im Webbrowser

Projekt in Xcode bearbeiten

Grundsätzlich können Sie die Dateien eines Vapor-Projekts natürlich mit jedem beliebigen Editor bearbeiten. Mehr Komfort bietet aber Xcode. Erzeugen Sie also mit vapor xcode die erforderlichen Projektdateien und öffnen Sie dann das neue Projekt:

vapor xcode
  Generating Xcode Project [Done]
  Select the `Run` scheme to run.
  Open Xcode project?
  y/n> y

In Xcode müssen Sie nun das Schema Run/My Mac auswählen und können das Projekt dann direkt in Xcode kompilieren und ausführen.

Vapor-Projekt in Xcode bearbeiten

Damit das Projekt den Aufruf von http://localhost:8080/hello mit ‚Hello, world!‘ beantwortet, ist der folgende, standardmäßig im Projekt enthaltene Code verantwortlich:

// Datei Sources/App/routes.swift
import Vapor
public func routes(_ router: Router) throws {
    // Basic "Hello, world!" example
    router.get("hello") { req in
        return "Hello, world!"
    }

    // weiterer Code für Todo-Controller ...
}

Todo-API

Der Hello-World-App enthält mehr Funktionen, als auf den ersten Blick erkenntlich sind: Ein extrem simpler Todo-Controller ist in der Lage, über eine REST-API Todo-Einträge zu speichern und wieder abzurufen.

Um diese Funktionen auszuprobieren, verwenden Sie am besten ein REST-Tool wie Postman oder RESTed.

Test der REST-API mit »Postman«

Der dafür erforderliche Code umfasst gerade einmal 40 Zeilen und verteilt sich auf die folgenden Dateien:

  • Sources/App/routes.swift
  • Sources/App/Controllers/TodoController.swift
  • Sources/App/Models/Todo.swift

Der Code kann der Ausgangspunkt für eigene Experimente mit Vapor sein. Beachten Sie, dass die Todo-Einträge nur im Arbeitsspeicher. Sobald die Ausführung der Server-App endet, gehen alle Einträge wieder verloren. Natürlich können Sie die Daten auch dauerhaft in einer Datenbank speichern — aber dann steigt die Komplexität des Codes gleich erheblich an.

Eigener Code

Zum Kennenlernen von Vapor bietet es sich an, das vorgegebene Hello-World-Projekt ein wenig zu erweitern. Wenn Sie beispielsweise möchten, dass die Server-App auf den GET-Request http://localhost:8080/now mit der aktuellen Uhrzeit antwortet, ergänzen Sie die Datei routes.swift wie folgt:

// Datei Sources/App/routes.swift
public func routes(_ router: Router) throws {
  router.get("now") { req -> String in
    let now = Date()
    let formatter = DateFormatter()
    formatter.dateFormat = "d.M.yyyy H:mm"
    let nowfmt = formatter.string(from: now)
    return nowfmt
  }
  ...
}

Wenn Vapor auf einen Request mit einer JSON-Struktur antworten soll, richten Sie eine Struktur ein, die von Content abgeleitet ist. (Content ist ein in der Vapor-Bibliothek definiertes Protokoll, welches wiederum vom in Swift 4 eingeführte Codable-Protokoll abgeleitet ist und daher JSON unterstützt.)

// Datei Sources/App/routes.swift
public func routes(_ router: Router) throws {
  router.get("jsondata") { req -> MyData in
    return MyData(id: 42, txt: "bla")
  }
  ...
}

struct MyData: Content {
  var id: Int
  var txt: String
}

Binding/Port ändern

Die Vapor-App verwendet standardmäßig den Port 8080 und die IP-Adresse 127.0.0.1 (localhost). Ein davon abweichendes Binding können Sie in configure.swift einstellen:

// in der Datei HelloVapor/Sources/App/configure.swift
public func configure(_ config: inout Config, 
                      _ env: inout Environment, 
                      _ services: inout Services) throws 
{
  // vorhandenen Code belassen
  ...

  // Bind-Adresse und Port einstellen
  let serverConfigure = 
    NIOServerConfig.default(hostname: "0.0.0.0", port: 8081)
  services.register(serverConfigure)
}

Die IP-Adresse 0.0.0.0 ist erforderlich, wenn Sie Vapor in einem Docker-Container ausführen und den Port an den Docker-Host weiterleiten möchten.

Deployment

Zu Vapor gibt es eine eigene Cloud-Infrastruktur, die wohl auch zum Geschäftsmodell für das Projekt werden soll. Mit vapor cloud deploy kann ein Projekt unkompliziert in die Vapor-Cloud übertragen werden. In einer Minimalkonfiguration samt Datenbank kostet das aktuell 13 Dollar im Monat (siehe die Preisliste).

Wer über etwas Linux-Knowhow verfügt, kann Vapor und Swift aber auch recht unkompliziert unter Ubuntu (oder in einem auf Ubuntu basierenden Docker-Container) installieren, den Quellcode des eigenen Projekts dort neu kompilieren und schließlich ausführen. Eine kurze Anleitung zur Ubuntu-Installation von Vapor 3 finden Sie hier.

Vapor verwendet standardmäßig HTTP. Die Brücke zum sicheren HTTPS errichten Sie auf einem eigenen Server am besten mit Apache oder Nginx: Eine Proxy-Konfiguration verbindet dann den Port 8080 von Vapor mit Port 443 des Webservers. Gegenwärtig gibt es dazu noch keine auf Vapor 3 aktualisierte Dokumentation. Grundsätzlich hat sich die Vorgehensweise im Vergleich zu Vapor 2 aber kaum geändert, weswegen Vapor 2 Nginx Deployment und Vapor 2 Apache Deployment weiterhin gültig bleiben.

Praxiserfahrungen

Vapor ist auch in Version 3 ein faszinierendes Projekt. Es gibt aber Verbesserungspotential:

  • In vielen Situationen steht in Xcode keine kontextabhängige Hilfe für Vapor-Klassen, -Protokolle etc. zur Verfügung. Auch die automatische Vervollständigung funktioniert häufig nicht. Das macht das Arbeiten unnötig mühsam, wobei schwer zu sagen ist, ob die Schuld bei Apple/Xcode liegt oder bei Vapor.

  • Die Vapor-Dokumentation macht nur auf den ersten Blick einen guten Eindruck. Viele Fragen bleiben offen, praxisnahe Code-Beispiele fehlen. Selbst simple Aufgaben lassen sich nur durch langwieriges Suchen und Probieren lösen.

  • Die Verwendung von Discord als zentrale Diskussionsplattform für das Vapor-Projekt macht das dort gesammelte Wissen von außen schwer zugänglich (z.B. für Google-Suchen). Die unübersichtliche Gestaltung der Discord-Website verschlimmert die Situation weiter.

  • Die radikale Neugestaltung des Frameworks ohne Rücksicht auf Kompatibilität ist zwar im Sinne eines sauberen Neustarts verständlich, wurde aber viel zu spät kommuniziert. Wer auf Vapor 2 gesetzt hat, steht jetzt dumm da (so wie ich …). Das schafft keine Vertrauensbasis für die Zukunft.

Quellen / Links

Blogs und externe Seiten

Ebooks (kostenpflichtig)

Die Dokumentationsdefizite versuchen zwei englischsprachige Ebooks zu beheben:

Das Ebook von Paul Hudson ist aus meiner Sicht enttäuschend: 1/4 des Texts gibt eine Einführung in Swift, die für die Zielgruppe des Buchs irrelevant ist. Danach kreisen viele Beispiele um traditionelle Webserver-Anwendungen. Das Schlagwort ‚REST API‘ kommt im gesamten Buch nicht vor. Paul Hudson erklärt zweifellos viele Vapor-Grundlagen, aber die Beispiele wirken komplett praxisfern.

Ein endgültiges Urteil über das Ebook von Ray Wenderlich fällt schwer, weil das Buch noch nicht fertig ist. (Einige Kapitel fehlen noch. Wenn Sie das Buch jetzt kaufen, bekommen Sie regelmäßig Updates.) Inhaltlich bietet die schon fertigen Teile des Buchs aber ungleich mehr Tiefgang. Allein schon die Kapitelüberschriften zeigen, dass dieses Buch in die richtige Richtung geht:

Das Vapor-Buch von Wenderlich et al in iBooks

Dafür sind formale Aspekte ärgerlich: Die PDF-Version hat weder ein ‚richtiges‘ Inhaltsverzeichnis (das typischerweise in der Seitenleiste des PDF-Viewers angezeigt wird) noch anklickbare Links in der Textform des Inhaltsverzeichnisses. Steinzeitlich! Die EPUB-Version funktioniert in dieser Hinsicht besser, irritiert aber mit halb-leeren Seiten (wegen ungünstigen Seitenumbrüchen) und anderen Layout-Defiziten.

Letztlich zählt der Inhalt, und diesbezüglich bietet das Wenderlich-Buch definitiv mehr.