Webservices mit dem Play Framework, Slick und Flyway in Scala
Für nahezu alle Produkte die wir für uns oder unsere Kunden entwickeln benötigen wir auch ein CMS um Basisdaten oder Texte zu verarbeiten. Oft sind diese Werte so spezifisch, dass ein herkömmliches CMS nicht verwendet werden kann. Um schnell auf die Anforderungen reagieren zu können setzen wir für diese Webservices auf das Play2 Framework, dass auf dem akka Framework aufsetzt und uns neben eine schnellen Entwicklung auch eine spätere Skalierung ermöglicht. Gerade im Rahmen von Minimal Viable bzw. Minimal Marketable Products setzen wir häufig REST Schnittstellen ein und switchen erst später zu Methoden wie graphQL oder gRPC zur Übertragung. Obwohl das Konzept der REST Schnittstelle schon mehr als 15 Jahre alt ist, ist es somit aktueller denn je. Mit dem Hintergedanken eine skalierbare, entwicklerfreundliche und moderne API zu programmieren, fällt die Wahl aus unserer Sicht immer auf das Play2-Framework. In Kombination mit Flyway zur Migration der Datenbank, sowie Slick zur Codegenerierung und Datenbank Abstraktion lassen sich auch leicht Datenbankänderungen im Betrieb einpflegen. Durch unser Tooling (GitHub, CircleCI, Kubernetes Cluster bei Google) und die Frameworks (Play2, akka) können wir innerhalb von 10 Minuten Updates für unsere Produkte ausrollen. In diesem Beitrag werden die Basics für ein Play2 Projekt erläutert.
Play2-Framework
Einleitung:
Basierend auf akka-http bietet Play2 eine optimale Basis für eine schnelle und skalierbare API. Durch die Anbindung an eine Postgresql Datenbank ist auch die persistente Speicherung der Daten gegeben. Für die Authentifizierung wird im nachfolgenden Beispiel Google Firebase verwendet. Die Einbindung wird innerhalb des Blogbeitrags genauer erläutert. Ebenso wird die programmierte API mit Swagger Annotations dokumentiert und über eine eigene Schnittstelle über http zugänglich gemacht.
Benötigt wird zum Stand 08.10.2018 für die Version 2.6:
- Mindestens Java SE 1.8
- sbt oder Gradle
Die Beispiele bestehen aus Scala Code, jedoch ist das Framework auch mit Java verwendbar.
Das verwendete Template dieses Beitrags ist auf github veröffentlicht.
Scala Play2 Framework Bootstrap Template
Aufbau:
Der Grundsätzliche Aufbau bei unserem Bootstrap Template teilt das Framework in 3 abstrakte Ebenen:
- API Ebene
- Service Ebene
- Datenbank Ebene
API Ebene:
Hier werden die Endpunkte, beziehungsweise das Routing der API, festgelegt. Die Syntax besteht aus:
HTTP Methode Path/Optional Parameter Controller.Methode(optional: Parameter)
und wird in der “conf/routes” Datei festgelegt. Die Methoden des Controllers werden dadurch auf die Pfade gebunden.
Service Ebene:
Die Methoden, die in der “routes” Datei auf den Pfad gebunden wurden, sind hier festgelegt.
Sie rufen die Methoden des „Data Access Object“s (DAO) auf. Das DAO wird über Dependency Injection mithilfe von Google Guice verfügbar gemacht. Die Annotations werden von Swagger verwendet, um eine Dokumentation der API im OpenAPI Format zu erstellen und sind hier nicht von Relevanz.
Diese Methode (getContact) ist auf den GET /v1/contact/:id Pfad gebunden und gibt ein Objekt aufgrund des Parameters über den Response Body als JSON zurück.
Datenbank Ebene:
Hier werden die Methoden der DAO’s über Slick in Datenbank Queries umgewandelt und ausgeführt. Die Benutzung von Slick erlaubt dieselben Interaktionsmöglichkeiten mit der Datenbank, wie sie regulär nur mit Streams umgesetzt werden können.
Die Methode “lookup” wird von der oben genannten Controller Methode aufgerufen und gibt entweder ein gefundenes Objekt oder einen Fehler zurück. Bei „queryById“ kann man sehen, dass hier auf der „Contacts“ Table aus der Datenbank die Stream Methode „filter“ angewendet werden kann. Dies wird dann von Slick in einen Query umgewandelt.
In dem DAO muss auch die von Slick generierte „Tables“ Datei importiert werden.
Authentication:
Durch einen Filter, der in der „application.conf“ zu der Filter-Liste von Play hinzugefügt wird,
können die Requests bevor sie gerouted werden zum Beispiel auf einen JWT Token überprüft werden. Die Filter geben die Requests nach der Bearbeitung durch sich selbst an den nächsten Filter weiter. Die Filter benötigen nur eine „apply“ Methode, die einen Filter als Parameter entgegennehmen und ein Result zurückgeben.
Der FirebaseJWTValidator kann den Token mit der Nutzerdatenbank eines Firebase Projektes vergleichen und so die Gültigkeit bestätigen. Eine Firebase.json Datei muss in dem „conf“ Ressourcen Ordner vorhanden sein. Diese enthält ein JSON Objekt, das man durch die Erstellung eines Firebase Projekts erhält. Die Validierung funktioniert über ein Singleton Firebase Objekt, das in der „Modules“ Datei wie folgt gebunden wird und so beim Start der Applikation einmalig instanziert wird.
Ebenso wichtig ist ein Stop Hook, um bei einem Hot Reload zu verhinden, dass versucht wird ein zweites Firebase Objekt zu erstellen.
Das Objekt wird in der Module class als Singleton gebunden:
Tests:
Durch die direkte Einbindung von z.B. Scalatest können über Play2 Fake Requests alle Pfade einfach und schnell getestet werden. Integrationstests sind somit kein Problem.
Slick:
Slick ermöglicht es, einfach und leicht mit einer Datenbank, wie mit Datei Streams, zu interagieren. Ebenso kann und sollte mit Slick der Code zu den Datenbanktabellen generiert werden. Die einzige Voraussetzung, die eine Datenbank erfüllen muss, ist, dass ein Treiber für Slick vorhanden sein muss. Dieser wird in der „application.conf“ festgelegt. Slick kann sogar mit mehreren DBs gleichzeitig umgehen. Dies ermöglicht die spätere Aufspaltung der Application in mehrere Microservices. Anders als in der Java Enterprise Welt üblich ist Slick kein ORM („object-relational-mapper“) sondern ein FRM („funtional-relational-mapper“).
Hier wird das von Play Framework isolierte Slick Plugin als eigenes Modul verwendet. Normalerweise verwendet Play standardmäßig das integrierte “play-slick” Plugin. Dies ermöglicht mehr Kontrolle darüber, wann die Code Generierung ausgeführt wird. Ebenso wird ein angepasster Code Generator verwendet, um direkt JSON und DateTime in der Datenbank zu speichern.
Flyway:
Durch Flyway wird die Datenbank versioniert. Somit können automatisch Änderungen in das Produktivsystem über eine CI Pipeline (z.B. mit CircleCI oder Jenkins) übernommen werden, ohne, dass ein Entwickler manuell etwas dazu beitragen muss. Dies funktioniert über herkömmliche *.sql Dateien, die von Flyway in Versionen eingeteilt werden. So werden nur die noch notwendigen Änderungen der SQL Dateien von Flyway auf die Datenbank angewendet.
Swagger:
Über eine z.B. als Docker Container gestartete Swagger UI kann die durch den Pfad /v1/swagger.json erreichbare Swagger Datei dargestellt werden.
Fazit:
Durch die Kombination der einzelnen Module mit Play2 wird eine perfekte Möglichkeit geschaffen, RESTful API zu erstellen. Ich persönlich finde die Arbeit mit dem Play Framework sehr angenehm. Da es das Framework ermöglicht, in kurzer Zeit und mit verhältnismäßig wenig Aufwand, auch große API’s zu erstellen, kann ich die Verwendung jedem empfehlen, der vor der Herausforderung steht, in kurzer Zeit eine komplexe, schnelle, zuverlässige und entwicklerfreundliche Schnittstelle zu programmieren. Folgt man den Grundsätzen des Domain Driven Design und verwendet man von Anfang an mehrere Datenbankendpunkte, lassen sich die Play2 Services hervorragend in mehrere kleine Microservices schneiden.