Stefan Zörner
25.04.2018
Micro Moves, Bauteil 1. Hier gehts um Games-Service, Partien und Züge als REST-Service.
Nachdem ich in der Intro den fachlichen Rahmen für die Schach-Anwendung FLEXess aufgespannt habe, gibt es in diesem Beitrag den ersten inhaltlichen Service: games. Oder genauer gesagt eine erste Fassung davon – denn der Service wird noch ein paar Verbesserungen erleben über die Zeit (u.a. ein UI).
Die Quelltexte für den Service findet Ihr auf GitHub. Die Bezeichner in Quelltext und API (Klassennamen, Methodennamen etc.) sind Englisch. Das erschwert das Verständnis mitunter. Denn die Englischen Schachbegriffe erschließen sich selbst Schachkennern nicht automatisch. Das gilt bereits für grundlegende Dinge wie Figurennamen. Ich habe daher eine kleine Randnotiz dazu geschrieben: Lady hits farmer … (?) Schachbegriffe auf Englisch. Doch bevor wir in den Quelltext eintauchen … zunächst: Was kann der Service fachlich?
Mit diesem Service können Anwender vorhandene Schach-Partien abfragen, inklusive deren Details (z.B. die gespielten Züge). Sie können neue Partien einleiten und sich dabei die Farbe aussuchen. Sie können offenen Partien als zweiter Spieler beitreten. Und sie können Züge ausführen.
Partien besitzen dabei drei Zustände:
Die hier gezeigte Fassung des Services ist der Startpunkt. Einige Dinge fehlen noch und kommen in weiteren Folgen der Serie hinzu. Hierzu zählen insbesondere
Zunächst geht es darum einen ersten integralen Bestandteil von FLEXess mit überschaubarem Aufwand ans Laufen zu bekommen. Die rote (1) markiert Euren Standpunkt (“Sie befinden sich hier.").
Als eine charakteristische Eigenschaft von Microservices (und auch Self-containes Systems) wird oft die Freiheit in Technologieentscheidungen angeführt, welche die umsetzenden Teams genießen. Hier ist vor allem die Persistenz ein Thema, aber auch die verwendete Programmiersprache. Unterschiedliche Programmiersprachen innerhalb einer Anwendung sind kein Wert an sich, aber Flexibilität an dieser Stelle eröffnet bei verschiedenen Anforderungen ggf. interessante Optionen.
Der erste Service hier ist in Java umgesetzt. Um einen technologischen Mix zu zeigen folgen später noch Services in Python und JavaScript.
Im Java-Umfeld ist der Einsatz von Spring Boot zur Implementierung leichtgewichtiger Services sehr verbreitet. Folgt man der Nomenklatur von Chris Richardson und seinem Buch Microservices Patterns (Cover siehe rechts) handelt es sich hierbei (gemeinsam mit Spring Cloud) um die Umsetzung des “Microservice Chassis”-Musters.
Als solches kümmert es sich um querschnittliche Aspekte. Laut Pattern-Beschreibung besteht der große Vorteil eines Microservice-Chassis darin, dass Ihr schnell und einfach mit der Entwicklung eines Microservices beginnen könnt. Oder wir jetzt hier konkret mit Spring Boot und Java.
Spring für sich alleine genommen ist ein verbreitetes, umfangreiches Java-Framework mit langer Geschichte. Spring Boot behauptet von sich selbst eine vorgefertigte Meinung über das Bauen von produktionsreifen Spring-Anwendungen zu haben. Es favorisiert Konventionen über Konfigurationen und ist so gemacht, dass Ihr so schnell wie möglich in Betrieb gehen können.
Microservices und Self-contained Systems sind Architekturstile mit Vertikalen als prägende Elemente. Eine einzelne Vertikale selbst kann geschichtet sein, so auch unser games-Service (wenn auch nicht strikt, wenn wie das folgende Abhängigkeitsdiagramm zeigt).
Die Java-Klassen sind auf drei Pakete verteilt:
Package org.flexess.games... | Wesentliche Inhalte |
domain | Die Entitäten Game und Move und der Zugriff auf die Persistenz mit Spring Data. |
service | Geschäftslogik in der Klasse GameService. Greift auf die Repositories aus domain zu. |
rest | REST-Schnittstelle für den Service in der Klasse GameRestController. |
Game übernimmt die Rolle eines Aggregates im Sinne von DDD. Objekte der Klasse Move verhalten sich dabei wie Bestellpositionen im kanonischen Aggregate-Beispiel einer Bestellung.
Als Persistenz kommt eine relationale Datenbank zum Einsatz (aktuell die H2), auf die der Service vermöge Spring Data JPA zugreift. Das Schema wird dabei aus den Entitäten generiert – wir wollten ja “schnell”.
Die Klasse GameService beinhaltet die oben beschriebene Funktionalität (Partien abfragen, Züge ausführen etc.) als Methoden. Auf dieser Ebene liegen auch die Transaktionen.
Die REST-Schnittstelle nutzt Spring Web MVC und Data Transfer Objects, um die Entitäten für die JSON-Repräsentation anzupassen ohne an den Domain-Objekten herumzufummeln. Eine sehr hilfreiche Informationsquelle für Anpassungen dieser Art ist Jackson Annotation Examples von Eugen Paraschiv.
Den Quelltexten liegt ein Build-Skript für Gradle bei. Beim Bauen mit dem Gradle-Wrapper muss lediglich Java 8 (oder höher) installiert sein. Das Skript gradlew (bzw. gradlew.bat) lädt die passende Gradle-Version und startet den Build, der die Abhängigkeiten (wie zum Beispiel Spring) herunterlädt.
Das Target bootRun baut den Service, lässt Tests laufen und startet das Ganze lokal auf Port 8080. Hier eine Sequenz zum Klonen aus GitHub, bauen und starten …
$ git clone https://github.com/embarced/micro-moves.git
Cloning into 'micro-moves'...
$ cd micro-moves/modules/games/
$ ./gradlew bootRun
Beim ersten Hochfahren legt der Service eine H2-Datenbank an und ein paar Partien darin ab, damit Ihr ohne größere Umstände direkt abfragen könnt.
Ein guter Einstiegspunkt ist das Auflisten aller Partien mit http://localhost:8080/api/games/, das Ihr im Web-Browser eingeben oder auch in einem Terminal per curl abfragen könnt. Details zu einer einzelnen Partie erhaltet Ihr mit dem Hineinnavigieren mit der gameId in der URL. Hier ein Beispiel mit curl:
$ curl http://localhost:8080/api/games/
[{"gameId":1,"playerWhite":"pinky","playerBlack":"brain","status":"ENDED"},
{"gameId":2,"playerBlack":"peter","status":"OPEN"}]
$ curl http://localhost:8080/api/games/1
{"gameId":1,"playerWhite":"pinky","playerBlack":"brain","status":"ENDED",
"result":"0-1","activeColour":"w",
"fen":"rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 0 3",
"created":"2018-04-24T11:10:55","modified":"2018-04-24T11:10:55"}
$
Die Ausgabe im Terminal ist nicht gut lesbar. Web-Browser können das schöner formatieren (hier im Screenshot Safari).
Wer Kommandozeilenfreund und Ästhet zugleich ist wie ich verwendet einen Pretty Printer wie json_pp:
$ curl http://localhost:8080/api/games/1 | json_pp
{
"gameId" : 1,
"playerWhite" : "pinky",
"playerBlack" : "brain",
"status" : "ENDED",
"result" : "0-1",
"activeColour" : "w",
"fen" : "rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 0 3",
"created" : "2018-04-24T11:10:55",
"modified" : "2018-04-24T11:10:55"
}
$
Eine Partie speichert neben den beteiligten Gegnern auch den aktuellen Zustand auf dem Brett in FEN-Notation ab (zu FEN siehe die Randnotiz Spielsituation als Zeichenkette hier im Blog). Die Abfrage aller Partien bzw. gezielt einer einzelnen Partie erfolgen über GET-Requests, die in den Methoden allGames bzw. gameById der Klasse GameRestController implementiert sind. Es lassen sich auch die Züge einer Partie anzeigen, etwa mit http://localhost:8080/api/games/1/moves/
Eine neue Partie einleiten erfolgt über einen POST-Request (Methode createGame) der auch über curl abgesetzt werden kann. Die verwendeten Kommandozeilenoptionen: -H kurz für –header, -X kurz für –request, -d kurz für –data).
$ curl -H "Content-Type: application/json" -X POST
-d '{"playerWhite": "stefan"}' http://localhost:8080/api/games/
Game #3 (stefan-???) created.
$curl http://localhost:8080/api/games/3 | json_pp
{
"playerWhite" : "stefan",
"fen" : "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
"gameId" : 3,
"status" : "OPEN",
"activeColour" : "w",
"created" : "2018-04-24T12:51:47",
"modified" : "2018-04-24T12:51:47"
}
$
Das ist etwas mühsam und wäre es noch mehr, wenn weitere Angaben in den Daten erforderlich wären. Weitaus bequemer ist der Einsatz des schönen Werkzeuges Postman. Hier lassen sich u.a. auch Anfragen speichern und recyceln. Der Screenshot unten zeigt einen erfolgreichen POST zum Erzeugen einer neuen Partie. Tabea spielt schwarz.
Ich habe hier ein Postman-Projekt mit den vom games-Service aktuell unterstützten Operationen abgelegt. Ihr könnt es leicht in Eure Postman-Installation importieren. Nicht gezeigt habe ich hier beispielsweise das Ausführen von Zügen, das über POST-Requests auf der …/moves/-Resource abgebildet ist.
Der Entwurf einer REST-API folgt Best Practices und Konventionen, die oftmals Projekt-spezifisch sind. Es gibt viele Dertailentscheidungen zu treffen. Etwa zum Aufbau der URLs oder zu Fehlercodes. Zum API-Design hat Kai Spichale ein lesenswertes Buch verfasst (Cover rechts), das auch REST-Schnittstellen behandelt.
Eine Schachpartie mit Postman oder curl zu spielen ist recht mühsam. Service braucht offensichtlich noch ein User Interface. Bevor ich mich diesem Thema annehme gibt es aber in der nächsten Folge zunächst noch einen weiteren Service. Diesen dann in Python.
Ach ja: Fragen und Anregungen sind natürlich jederzeit gerne Willkommen. Per Kommentar hier im Blog oder gerne auch per Mail direkt an mich.