Category

Inhaltliches

Einen Computergegner asynchron anbinden mit RabbitMQ (Micro Moves, Bauteil 6)

By | Inhaltliches | No Comments

Blog-Serie Micro Moves -- LogoIn der vorherigen Folge der Micro-Moves-Serie haben wir einen Service für die Spielregeln im Schach synchron an einen anderen Service angeflanscht. Mit diesem Bauteil zeigen wir nun asynchrone Kommunikation. Konkret ermöglichen wir es Benutzern unserer Online-Schachplattform FLEXess sich mit einem der ganz Großen im Computer-Schach zu messen.

Um was geht es — ein Überblick

Im Bild sieht es aus, als gäbe es dieses Mal gleich zwei Bauteile. Was die Anzahl Prozesse angeht stimmt das auch. Wir integrieren einen Computer-Spieler (Stockfish) und binden ihn via Messaging asynchron an das games-Modul an. Inhaltlich passiert am Ende aber nur eins: Unsere Benutzer können gegen den Ranglisten-Ersten (CCRL 40/40, Stand Juli 2018) im Computer-Schach antreten. Oder auch der Computer gegen sich selbst, wenn wir einfach nur zugucken wollen.

Überblick FLEXess, Bauteil 6

Computerschach

Ein Jahrhundertraum wie das Fliegen. Eine Machine bauen, die Menschen im Schach bezwingt. Seit Wolfgang von Kempelens berühmten Schachtürken um 1780 haben sich Menschen daran versucht. Das Problem können wir als gelöst ansehen. Claude Shannon hat 1949 mit seinem Aufsatz „Programming a Computer for Playing Chess“ die theoretische Grundlage gelegt. 1996 verlor das erste Mal ein amtierender Schachweltmeister — stellvertretend für die gesamte Menschheit sozusagen — unter Wettkampfbedingungen gegen eine Machine (Deep Blue). Mit dem aktuellen Megatrend Machine Learning hatte das Ganze übrigens nichts zu tun. Deep Blue lernte nicht. Es rechnete einfach brutal schnell.

Erste Schach-Computer für den privaten Gebrauch erschienen bereits 1977 und kosteten noch eine Stange Geld. Der Fortschritt in der Hardware lässt uns heute bequem auf unserem Smartphones gegen eine App verlieren. Der eigentliche (Rechen-)Kern von Schach-Programmen wird dabei als „Engine“ bezeichnet. Engines lassen sich typischerweise in verschiedene Schachoberflächen integrieren. Damit dies einfach möglich ist, und auch damit Engines leicht gegeneinander antreten können, haben sich textbasierte Kommunikationsprotokolle etabliert. Das verbreitetste heute ist UCI (kurz für Universal Chess Interface, Spezifikation siehe hier).Stockfish Logo

Um auch Nutzern von FLEXess die Möglichkeit zu geben, gegen einen ernstzunehmenden Computergegner anzutreten, binden wir in dieser Folge Stockfish an. Das ist eine besonders spielstarke Chess Engine, in C++ implementiert und Open Source. Stockfish ist auf allen Ranglisten vorne dabei (Spitzenreiter zum Beispiel in der CCRL 40/40, Computer Chess Rating Lists, Stand Juli 2018). Das Programm unterstützt das UCI-Protokoll und lässt sich dadurch einfach integrieren.

Stockfish auf der Kommandozeile

Wenn Ihr Stockfish für Eurer Notebook herunterladet findet Ihr auf der entsprechenden Seite auch ein einfaches UI, mit dem sich die Engine interaktiv passabel bedienen lässt. Uns interessiert allerdings die Kommandozeile. Startet Ihr die Stockfish Engine in einem Terminal könnt Ihr Befehle eingeben und die Denkarbeit des Programmes beobachten, inkl. des  „besten Zuges“ nach Ende einer Analyse. Die folgende Abbildung zeigt in einem Terminal, dass Stockfish ein Schäfermatt auch auf der Kommandozeile souverän nach Hause spielt.

Stockfish in der Kommandozeile

Die von mir eingegebenen Befehle waren isready, position, go und quit. Mit position wird die aktuelle Spielsituation gesetzt. Die Eingabe ist in FEN möglich (mit position fen …, Details zu dieser Notation findet Ihr in der zugehörigen Randnotiz). Oder wie oben im Bild durch Angabe der bisherigen Züge ausgehend vom Start (mit position startpos moves e2e4 e7e5 d1h5 …). Die verwendete Züge ermöglichen weiß am Zug ein sogenanntes Schäfermatt. Die schicke Abbildung unten zeigt das Brett nach diesen 6 (Halb-)zügen. Es ist übrigens mit dem chess-diagrams-Modul als Folge 2 dieser Serie generiert.

Spielsituation vor Schäfermatt

Der UCI-Befehl go lässt Stockfish losrechen. Mit dem depth-Parameter habe ich die Suchtiefe beschränkt, damit die Ausgaben nicht das Terminal zutexten und wir die vorherigen Eingaben noch im Screenshot sehen. Mit bestmove gibt Stockfish seinen Zug aus. Dame auf h5 schlägt auf f7 und setzt Matt. Besser geht es nicht.

Mit quit können wir die Session beenden. Wir wollen Stockfish nicht weiter unterfordern.

Stockfish anbinden

Roboter mit SteckerBei der Integration der Schach Engine habe ich mich für Python entschieden, um ein bisschen Gluecode zu schreiben. Ihr findet diesen im Modul computer-player auf GitHub. Die betreffende Datei heißt stockfish.py und umfasst ca. zwei Dutzend Zeilen Quelltext. Die Python-Funktion calculate_move startet Stockfish als Subprozess. Via stdin gibt sie die Befehle (position, go …) an die Engine und holt sich das Ergebnis über dessen stdout ab.

Das Ganze funktioniert nur, wenn stockfish installiert ist. Ein kleiner Integrations-Test mit tox überprüft die Anbindung der Engine, indem er Stockfish u.a. genau das Schäfermatt serviert. Unser Image für Docker basiert auf Ubuntu. Das Dockerfile installiert Stockfish mit apt-get. Damit das Programm aktiv wird, wenn wir ihm Züge vorlegen, verbinden wir es via Messaging.

Die Messaging-Renaissance

Messaging ist eine etablierte Technologie, um Programme miteinander sprechen zu lassen, die dies von Natur aus nicht können. Entsprechende Middleware schaut auf eine lange Geschichte zurück. WebSphere MQ von IBM etwa erblickte als MQSeries bereits Anfang der 90er das Licht der Welt. Der spätere SOA-Hype befeuerte den Einsatz derartiger EAI-Lösungen. Neben kommerziellen Produkten von Firmen wie Oracle oder TIBCO gibt es auch Open Source-Lösungen, etwa Apache ActiveMQ.

RabbitMQ LogoMessaging-Lösungen kennzeichnen sich durch lose Kopplung aus. Die Kommunikationspartner können in sehr verschiedenen Technologien implementiert sein und auf unterschiedlichsten Plattformen laufen. Der Nachrichtenaustausch erfolgt asynchron und oftmals indirekt. Die Partnerprogramme brauchen sich weder kennen, noch gleichzeitig laufen. Das macht Messaging Lösungen für zeitgenössische Architekturstile wie Microservices, die lose Kopplung anstreben, sehr interessant.

Auch wenn die Konzepte die alten sind sehen wir vermehrt neuere, leichtgewichtige Messaging-Löungen auf dem Vormarsch. Ganz vorne dabei ist RabbitMQ. Die in Erlang entwickelte Open Source-Lösung implementiert eine Reihe von Standards, darunter AMQP (Advanced Message Queuing Protocol).

Für unsere Anbindung haben wir ein vorgefertigtes Docker-Image von RabbitMQ genutzt, und in der Konfigurationsdatei für docker-compose als Service vereinbart. Die gewählte Image-Variante beinhaltet die webbasierte Management-Konsole. Dadurch lässt sich RabbitMQ mit seinen Verbinden, Nachrichtenaustauschen und Queues prima von außen beobachten. Die folgende Abbildung zeigt bereits unser FLEXess in Aktion. Also beim Senden und Empfangen von Schachstellungen und Spielzügen als Nachrichten.

RabbitMQ Management Console

games und computer-player tauschen Nachrichten aus

Für die Anbindung zwischen games und computer-player (siehe auch Überblicksbild oben) nutzen wir zwei sogenannte „Direct Exchanges“ in RabbitMQ. Die Middleware bietet verschiedene Austauschmuster an — das direkte ist das einfachste. Die folgende Abbildung zeigt das Zusammenspiel:

Ablauf Messaging

Zwei Queues nehmen Nachrichten im JSON-Format auf. Im Fall eines Zuges, den Stockfish ausführen soll, legt games zunächst eine Nachricht mit der Spielsituation (Schachstellung in FEN) in der Message-Queue positions ab. Ein Computer-Spieler „lauscht“ auf dieser Queue (Implementierung der Funktionen in Python in der Datei stockfish_listener.py). Tatsächlich könnte es mehrere computer-player-Container  geben. Im Falle einer Nachricht liest computer-player die Position aus und nutzt die UCI-Integration wie oben beschrieben zur Ermittlung des besten Zuges aus Stockfishs Sicht. Dieser Zug geht über die Queue moves an games zurück. Über die Game-ID, die jede Nachricht als ein Attribut enthält, kann das games-Modul den Zug dem Spiel zuordnen und ausführen.

Die Aussteuerung von Spielsituationen in die Warteschlange positions erfolgt in games über den Spielernamen „stockfish“. So kann ein Spieler gegen den Computer spielen, indem er einfach eine Partie gegen „stockfish“ startet. Die folgende Abbildung zeigt mich (weiß) im Spiel gegen den Computer-Gegner. Im Moment sieht es noch ausgeglichen aus.

Spiel gegen den Computer-Gegner im Browser

Übrigens lassen sich über das UI des games-Subsyststems auch Partien starten, in denen Stockfish gegen sich selbst spielt. Einfach für beide Spielernamen „stockfish“ im Formular unter „Create a new game“ eintragen. Dann geht das los. Wählt Ihr „Play Game!“ könnt Ihr die „beiden“ Kontrahenten beobachten. Die folgende animierte Abbildung zeigt den Ausschnitt einer solchen Partie in Endlosschleife.

Stockfish gegen Stockfish

Weitere Informationen. Und wie es weiter geht.

Enterprise Integration Patterns: Designing, Building, and Deploying Messaging SolutionsRabbitMQ stellt exzellente Tutorials für alle denkbaren Programmiersprachen und verschiedene Kommunikationsmuster bereit. Ich habe mich für Python daran orientiert. Für die Integration in das games-Modul mit Spring Boot ließ ich mich von einem entsprechenden Beitrag inspirieren: „Getting Started: Messaging with RabbitMQ“.

Die fundierte Quelle für Messaging in Buchform ist meiner Meinung nach immer noch der Klassiker Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions von Gregor Hohpe.

Die Entwicklung von Programmen, die Schach spielen können, geht weiter. Auch heute noch treten Schachprogramme im Wettbewerb gegeneinander an. Mittlerweile mischt die künstliche Intelligenz tatkräftig mit. 2017 schlug Google’s AI-Lösung AlphaZero das traditionelle Spitzenprogramm Stockfish. Es benötigte 4 Stunden, um Schach zu lernen. Und verlor im Anschluss von 100 Partien keine einzige (Bericht hier).

Wie geht es hier mit FLEXess weiter eigentlich? Die Benutzer brauchten sich bisher noch nicht an der Plattform anmelden. Eine Zeichenkette für den Spielernamen einzugeben, genügt. Und jeder kann bei jedem mitspielen. Zeit, dass wir uns diesem offensichtlichen Mangel widmen!

Ach ja: Fragen und Anregungen sind natürlich jederzeit gerne willkommen. Per Kommentar hier im Blog oder gerne auch per Mail direkt an mich …

Zur Blog-Serie Micro Moves

 

 

 

 

Spielregeln für Schach und für robuste Aufrufe mit Hystrix (Micro Moves, Bauteil 5)

By | Inhaltliches | No Comments

Blog-Serie Micro Moves -- LogoMittlerweile (seit dem dritten Teil dieser Serie) könnt Ihr auf unserer Online-Schachplattform FLEXess Schach spielen. Allerdings ist die Überprüfung der Züge bzgl. der Spielregeln rudimentär. Solange die Figur die richtige Farbe hat, kann sie ziehen, wohin sie will. Im Extremfall auch quer übers Brett direkt auf den gegnerischen König. Weiterhin merken wir nicht, wenn eine Partie zu Ende ist. Wir führen in diesem Teil einen neuen Service ein, der die Spielregeln anbietet. Und nutzen ihn aus dem games-Modul, um diese Mängel zu beheben.

Um was geht es — ein Überblick

Die komplizierten Spielregeln führen zu einem Quelltext-mäßig umfangreichen Bauteil rules. Wir implementieren es in JavaScript. Das Modul stellt seine Funktionalität per HTTP bereit. In Anlehnung an den letzten Teil der Serie machen wir sie im Reverse Proxy bekannt und bauen ein Docker-Image dazu, das wir in Docker Compose integrieren. Die rote (5) in der folgenden Abbildung markiert den Standpunkt von rules im Gesamtbild („Sie befinden sich hier.“).

Erster Verwender des Moduls ist das Partien-Subsystem games, das überprüfen möchte, ob z.B. aus play eingehende Züge regelkonform sind. Und wissen will, ob eine Partie bereits beendet ist (Stichworte: Schachmatt, Patt). Hierzu greift es auf rules zu; der Zugriff erfolgt synchron. Wir sichern ihn mit Netflix Hystrix ab (Stichwort Resilience).

Aber der Reihe nach …

Spielregeln im Schach — und warum ein eigenes Modul?

Die Schachregeln sind verglichen mit anderen Spielen wie Dame oder Mühle vergleichsweise umfangreich. Es gibt sechs Figurenarten, die unterschiedlich ziehen. Der Bauer ist besonders kompliziert. Er schlägt anders als er zieht, darf am Anfang zwei Felder vor, mitunter en Passant schlagen und verwandelt sich auf der gegnerischen Grundlinie in eine andere Figur (engl. Promotion, für die englischen Schachbegriffe siehe diese Randnotiz). Darüber hinaus gibt es noch die Rochade, Schachmatt und Patt, die Regel, dass man nach seinem Zug nicht im Schach stehen darf … usw.

SchachfigurenEine vollständige Implementierung der Regeln ist aufwändig, aber nicht wirklich schwierig. Als nützlich erweist sich dabei eine Funktion, die für eine beliebige Position (im Wesentlichen die Platzierung der Figuren auf dem Brett, gegeben etwa in Forsyth-Edwards-Notation, siehe Randnotiz dazu) die Liste aller erlaubten Züge des am Zug befindlichen Spielers ermittelt. Mit dem Ergebnis könnt Ihr etwa prüfen, ob ein gegebener Zug gültig ist (er muss in der Liste auftauchen). Ist die Zugliste leer ist das Spiel beendet. Ob der König am Zug angegriffen ist macht dann den Unterschied aus zwischen Matt (verloren) und Patt (unentschieden aka Remis).

Die Spielregeln lassen sich prima automatisiert testen. Insbesondere die Funktion welche die gültigen Züge ermitteln. Auch hier leistet die Forsyth-Edwards-Notation gute Dienste, da man mit ihr in Unit-Tests sehr einfach die Eingabe als Zeichenkette repräsentieren kann. Hier ein Code-Fragment wie es in etwa in unseren Unit-Tests auftaucht

fen = "8/8/7R/3k4/8/3P4/7B/7K b - - 0 1"
expectedMoves = ["d5d4", "d5c5"]
pos = new Position(fen)
moves = ChessRules.getAllValidMoves(pos)
assert.equal(moves.length, expectedMoves.length)
...

In unserem Überblicksbild erkennt Ihr mögliche Verwender für Schachregeln. Neben dem Partien-Service games etwa die beiden Clients play (in Vue.js aus Folge 3) und später noch den Mobile Client. Diese Clients würden durch Nutzung der Spielregeln die Benutzbarkeit erhöhen, in dem sie etwa beim Auswählen einer Figur die Felder markieren, wo die Figur hinziehen darf. Und sie könnten einen eingegebenen Spielerzug prüfen, bevor sie ihn Richtung games schicken, und so den Server entlasten und unnötigen Netzwerkzugriffe (bei fehlerhaften Zügen) vermeiden.

Alternatives Schach: Neue Regeln für das Spiel der KönigeWenn jeder Client die Spielregeln selbst implementieren würde, ständen diesen Vorteilen der Nachteil des mehrfachen Aufwands gegenüber und die Gefahr von Inkonsistenzen. Wenn wir die Spielregeln später ergänzen wollen, um auch so schöne Varianten wie Atom-Schach oder Zombie-Schach zu unterstützen, müssten wir an verschiedenen Stellen Änderungen vornehmen. Schöne Anregungen für alternative Schachregeln finden sich übrigens im Buch rechts …

Die Spielregeln zentral zu entwicklen und als Bibliothek zur Verfügung zu stellen (vgl. Shared Kernel in DDD) wäre eine Option. Als problematisch könnten dabei unterschiedliche Programmiersprachen herausstellen. Eine weitere Möglichkeit wäre die Spielregeln im games-Modul zu integrieren. Ich habe mich dagegen entschieden, da dieses Modul eh schon recht groß ist, und ich es ungern neu deployen möchte, um neue Spielregeln zu unterstützen.

Die Wahl fiel daher auf einen eigenen Service rules, der die Spielregeln über eine HTTP-Schnittstelle bereitstellt. Im ersten Wurf nur Standard-Schach allerdings, kein Zombie-Schach.

Implementierung in JavaScript

Die für Micro Moves erstellte Implementierung der Spielregeln in JavaScript (Quelltext auf GitHub) benutzt folgendes Domänenmodell (folgende Abbildung). Die Felder des Brettes werden als Zahlen von 0..63 repräsentiert. Die zentrale Klasse Position spiegelt den Zustand einer Partie wider und orientiert sich an FEN. Objekte sind unveränderlich, die Methode performMove liefert eine neue Position mit der geänderten Spielsituation zurück.

Alle oben dargestellten Elemente finden sich in der Quelltextdatei domain.js. Die folgende Tabelle gibt einen Überblick über alle Dateien der Implementierung.

Datei Wesentliche Elemente Beschreibung
domain.js Colour, Move, Position Domänenmodell für die Schachelemente
geometry.js BoardGeometry Geometrie des Schachbretts, inkl. Bewegungen gerade, schräg …
rules.js ChessRules gültige Züge, angegriffene Felder, Schachmatt …

Für die Spielregeln liegen im Unterverzeichnis tests des Moduls eben solche. Realisiert sind sie mit dem Test-Framework Mocha, siehe etwa „Simple Node.js tests with assert and mocha“. Ihr führt sie einfach mit npm test aus, der folgende Screenshot zeigt einen Teil der Ausgabe:

"npm test" in rules (Ausschnitt)

Die folgende Tabelle skizziert die Operationen der Klasse ChessRules jeweils anhand Eingabe und Ergebnis.

Funktion Eingabe Ergebnis
getAllValidMoves Position Liste der aus der Postion heraus möglichen Züge für den aktiven Spieler
isSquareAttackedByColour Position, Feld, Farbe boolean, ob das Feld in der Postion von einer Figur der betreffenden Farbe angegriffen ist
isCheckmate Position boolean, ob die gegebene Position ein Schachmatt für den aktiven Spieler ist.
isStalemate Position boolean, ob die betreffende Position ein Patt ist.

Der Spielregeln-Service

Logo expressDer eigentliche Service für die Spielregeln ist mit Node.js und dem Framework Express realisiert. Express bezeichnet sich selbst als schnelles, offenes, unkompliziertes Web-Framework für Node.js. Im ersten Wurf unterstützt der Service nur zwei Funktionen. Über die URL /allValidMoves könnt Ihr zu einer Position (Request-Parameter fen) die Menge der möglichen Züge ermitteln (entspricht der Funktion getAllValidMoves aus den Regeln oben). Weiterhin ist es mit /validateMove möglich eine Position (Request-Parameter fen) und einen Zug (Request-Parameter move) anzugeben. Zurückgeliefert wird ein JSON-Dokument, das einen Boolean-Wert enthält, ob der Zug gültig ist. Weiterhin im Falle eines gültigen Zuges die neue Position nach Ausführung (in FEN), und ob diese Position ein Schachmatt ist oder ein Patt. Hier ein Beispiel-Resultat für einen Aufruf:

{
  "fen":"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
  "move":"e2e4",
  "valid":true,
  "resultingFen":"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
  "checkmateAfterMove":false,
  "stalemateAfterMove":false
}

Der Quelltext für den Service findet sich in der Datei server-main.js. Start mit npm start. Der Service enthält eine index.html als statische Testseite. Dort sind einige Aufrufe für die Funktionen verlinkt, damit Ihr sie direkt ausprobieren könnt, ohne selbst Aufrufe mit fen und move als URL-Parameter zu basteln. Die folgende Abbildung zeigt einen Screenshot der Seite, inkl. JSON-Ausgabe bei Aufruf eines Test-Links.

Testseite rules, inkl. Aufruf mit JSON-Ausgabe.

Das rules-Modul enthält darüber hinaus ein Dockerfile (vgl. Folge 4) und ist in die Docker Compose-Konfiguration von FLEXess integriert. Der Artikel „Dockerizing a Node.js web app“ erklärt kurz und knackig wie Ihr Docker Images für Node.js-Anwendungen baut. Ich habe mich daran orientiert.

Synchrone vs. asynchrone Kommunikation

Mit der neuen Funktionalität wäre das games-Modul nun in der Lage, eingehende Züge bzgl. der Spielregeln zu überprüfen. Weiterhin könnte es die neue Spielsituation aus dem Zug ermitteln, und ein Spielende durch Matt oder Patt erkennen.

Immer wieder Thema im Zusammenhang mit vertikalen Architekturstilen ist die Kommunikation zwischen Teilen, in unserem Fall also zwischen games und rules. Oftmals etabliert bereits die Makroarchitektur Regeln oder Prinzipien. Ist Kommunikation zwischen Vertikalen überhaupt erlaubt? Falls ja: synchron, asynchron oder je nach Fall beides. Hinzu kommen Entscheidungen zu Protokollen und Technologien.

All diese Optionen haben Vor- und Nachteile, die je nach Fall mehr oder weniger ins Gewicht fallen (sonst würde da nicht so breit diskutiert). In FLEXess legen wir hier keinen allgemeinen Regeln fest, sondern zeigen in verschiedenen Folgen unterschiedlichen Ansätze. Und diskutieren diese.

In unserem Fall wirkt die Wahl einer synchronen Kommunikation natürlich: Bei games geht ein Zug zu einer Partie ein. Das games-Modul prüft mit einem Aufruf gegen rules, ob er regelkonform ist, und wartet auf das Ergebnis (synchron). Je nach Ausgang der Überprüfung wird der Zug ausgeführt und persistiert, oder abgelehnt. in jedem Fall erhält der Aufrufer ein Ergebnis (auf das er solange wartet).

Motivation für die synchrone Kommunikation ist hier, dass games ohne die Überprüfung in rules den Zug nicht annehmen oder ablehnen kann. Gleichzeitig warten auf der Client-Seite zwei Spieler auf diese Entscheidung. Und bei einem liefe in einem echten Spiel die Schachuhr für seine Bedenkzeit weiter. Wir brauchen das Ergebnis jetzt.

Sequenz-Diagramm zur synchronen Kommunikation zwischen games und rules

Größter Nachteil der synchronen Kommunikation, und der Grund warum in Microservices-Architekturen asynchrone Kommunikation (also das Versenden nach Nachrichten ohne Warten auf Reaktion) bevorzugt sind, ist die höhere Kopplung. Das nicht zur Verfügung stehen von rules führt dazu, dass games keine Züge annehmen kann. Wir kommen darauf noch zurück!

Anbindung an das games-Modul

Im games-Modul kümmert sich die Klasse RulesClient (im Packet org.flexess.games.rulesclient, Quelltexte siehe GitHub) um die Anbindung an rules. Sie fungiert als Gateway (vgl. Pattern-Buch von Martin Fowler) und bietet zunächst nur die Methode validateMove (analog zur URL im Service) an.

Die Klasse kapselt den HTTP-Aufruf gegen rules und das Überführen der JSON-Anwort in ein geeignetes Java-Objekt (Klasse ValidateMoveResult). Als HTTP-Client nutze ich Java-Bordmittel aus java.net. Auf eine Service Registry verzichten wir wie hier — das rules-Modul (und ggf. auch mehrere Exemplare davon) ist durch Docker Compose im Docker-internen Netzwerk mit dem Hostnamen „rules“ erreichbar.

Der RulesClient landet per Dependency Injection im GamesService, der ihn im Rahmen der Operation createAndPerformMove heranzieht, um den eingehen Zug auf Regelkonformität zu überprüfen. Ungültige Züge quittiert er (wie zuvor) mit einer Exception, die der REST-Service als Fehlermeldung zum Client (z.B. die Vue.js SPA play) sendet, siehe Screenshot:

Illegaler Zug, dargestellt in play-SPA

Bei Problemen im RulesClient (z.B. rules-Service nicht verfügbar) kommt es zu IOException u.ä., die innerhalb des RulesClient behandelt dem Service als RuntimeException weitergereicht werden könnten. Sonderlich robust ist das nicht.

Resilience mit Netflix Hystrix

Der synchrone Ansatz birgt die Gefahr, dass ein Ausfall des Moduls rules die Arbeit des zentralen Moduls games massiv behindert. Auch wenn rules für die Bearbeitung von Anfragen lange braucht, zieht das games in Mitleidenschaft. Bei vielen parallelen Anfragen könnte der Thread-Pool von games für HTTP-Anfragen leerlaufen. games antwortet dann nicht nur (auch) langsam, sondern gar nicht mehr (Connection refused) — selbst bei Anfragen, welche die Spielregeln gar nicht betreffen!

Release It!: Design and Deploy Production-Ready SoftwareArchitekturmuster rund um Resilience (dt. „Unverwüstlichkeit“) zielen auf robuste, fehlertolerante Systeme ab. Hier reisst ein einzelnes Teilsystem nicht gleich die ganze Anwendung runter. In Microservices-Anwendungen, in denen synchrone Kommunikation zwischen Services lt. Makro-Architektur zulässig ist, ein wichtiger Punkt. Der Self-contained Systems-Ansatz (kurz SCS, siehe Charakteristiken) hingegen propagiert asynchrone Kommunikation zwischen SCSen, wo immer möglich.

Dort, wo synchrone Kommunikation zwischen Services in der Makroarchitektur zulässig ist (Paradebeispiel: Netflix) hat sich das Circuit Breaker-Pattern etabliert. Es orientiert sich an der Metapher des Schutzschalters in Stromkreisen. Im Falle von Überlast fliegt dort die Sicherung raus und verhindert dass etwa Brände durch überhitzte Leitungen entstehen. Circuit Breaker schützt den Zugriff auf externe Systeme, z.B. einen anderen Service. Antwortet dieser nicht oder langsam, wird der Schaltkreis unterbrochen. Der Service erhält keine Anfragen, bis sich die Situation beruhigt hat (was mit einzelnen Aufrufen ab und an getestet wird).

Martin Fowler beschreibt das Muster in einem Blog-Beitrag — wie für Muster üblich ist die kanonische Quelle allerdings ein Buch: der resilience-Klassiker „Release It!“ von Michael Nygard (Cover rechts). Die zweite Ausgabe ist in 2018 frisch erschienen.

Logo HystrixAls konkrete Implementierung im Java-Umfeld stellt Netflix Hystrix als Bibliothek bereit. Es bezeichnet sich selbst als Latenz- und Fehlertoleranzlösung für verteilte Systeme. Für andere Programmiersprachen gibt es ähnliche Lösungen. Hystrix lässt sich über die Integration von Spring Cloud leicht in unser bestehendes games-Modul aufnehmen.

Um eine Operation (hier den Aufruf des rules-Service aus games) mit Hystrix abzusichern gibt es zwei Optionen. Entweder Ihr schreibt eine Command-Klasse gemäß der Hystrix-API, oder Ihr nutzt eine spezielle Annotation @HystrixCommand und „markiert“ damit eine Methode. Spring wickelt dann gemäß AOP einen Proxy und umhüllt die Ausführung. Letzteres ist die einfachere Variante, Ihr findet den Quelltext dazu im games-Modul in den Klasse RulesClient. Dekoriert ist dort die Methode validateMove.

Falls rules nun nicht zur Verfügung steht oder langsam antwortet öffnet sich der Kreis, und rules wird nicht von weiteren Anfragen von games belästigt. Mit dem Hystrix Monitor lässt sich das Verhalten von außen beobachten (bei geeigneter Konfiguration — das Dashboard muss an den Stream kommen …). Im Folgenden Screenshot seht Ihr unsere anmontierte Methode einmal mit geschlossenem Stromkreis (links), einmal mit unterbrochenem.

Hystrix Dashboard

Egal ob Ihr ein Command schreibt oder die Annotation nutzt: Ihr könnt einen Fallback implementieren bzw. angeben, der bei einem unterbrochenen Stromkreises greift. In der Regel kommt hier ein Default-Verhalten zum Einsatz, das weniger Schmerzen bereitet als ein Fehler. Beispielsweise könnten alte Werte aus einem Cache zurückgeliefert werden, wenn ein entferter Service gerade nicht zur Verfügung steht, und das fachlich akzeptabel ist. Klassisches Beispiel hier sind Wetterdaten, wo der Aufrufer ggf. auch gut mit älteren leben kann.

Im Falle unserer Schachregeln habe ich auf eine Alternative verzichtet. Der Aufrufer erhält die Nachricht, dass der Zug nicht überprüft werden konnte. Der folgende Screenshot zeigt die Situation in der Vue.js-SPA play.

Meldung: Rules service not available.

Denkbar wäre auch im Fall der Nichtverfügbarkeit der Spielregeln den gegnerischen Spieler entscheiden zu lassen, ob der vorgelegte Zug regelkonform ist oder nicht. Im echten Schachspiel ist es ja auch so (man weist den Gegner auf einen fehlerhaften Zug hin). Die Idee gefällt mir. Auf eine Implementierung habe ich trotzdem verzichtet.  

Weitere Informationen. Und wie es weiter geht.

Understanding ECMAScript6: The Definitive Guide for JavaScript Developers (Cover)JavaScript hat durch Technologien wie Node.js oder Single Page Applikations deutlich an Relevanz gewonnen, auch im Unternehmensumfeld. Es geht nicht mehr darum klassischen HTML-Anwendungen mit ein bisschen Feenstaub in Form von browser-seitigem Scripting ein wenig mehr Interaktivität einzuhauchen. Stattdessen entstehen größere Softwarelösungen in dieser Sprache mit einem ganz eigenen, reichen Öko-System. Das Javascript von 1995 hat mit dem von heute nicht mehr viel zu tun. Hingegen waren meine JavaScript-Kenntnisse lange durch diese alten Erinnerungen geprägt. Ein Buch, das mir geholfen hat wieder Anschluss an das „zeitgenössische“ JavaScript zu finden ist „Understanding ECMAScript6: The Definitive Guide for JavaScript Developers“ von Nicholas Zakas (Cover siehe rechts).

Nachdem in dieser Folge die synchrone Kommunikation Thema war, zeigen wir als Alternative dazu später noch einen asynchronen Fall mir Messaging. Eine weiterer offener Punkt ist Security. Im Moment könnte jeder Benutzer an jedem Brett ziehen (er müsste nur auf die Partie klicken).  In der Realität würden die tatsächlichem Spieler einem auf die Finger hauen. Später übernimmt das unsere Plattform.

Ach ja: Fragen und Anregungen sind natürlich jederzeit gerne Willkommen. Per Kommentar hier im Blog oder gerne auch per Mail direkt an mich …

Zur Blog-Serie Micro Moves

Services bündeln mit nginx als Reverse Proxy und Docker (Micro Moves, Bauteil 4)

By | Inhaltliches | No Comments

Blog-Serie Micro Moves -- LogoMit den bisherigen Beiträgen der Micro Moves Blog-Serie ist ein Konglomerat von Bauteilen für unsere Online-Schach-Plattform FLEXess entstanden. Services laufen in separaten Prozessen und lauschen auf bestimmten Netzwerkadressen per HTTP. Besonders spürt das die Single Page Application (SPA) play aus Teil 3, die auf die REST-API aus Teil 1 und den Diagramm-Service auf Teil 2 zugreift. Dazu benötigt sie aktuell deren Hostnamen und Ports.

In dieser Folge kommt keine neue Funktionalität hinzu. Stattdessen räumen wir ein wenig auf, vereinheitlichen die Art, wie wir Services betreiben und auffindbar machen, und lassen FLEXess für einen Browser-Client wie eine Anwendung aussehen. Mit ein wenig Makro-Architektur schaffen wir Voraussetzungen sowohl für weitere Services in den nächsten Folgen, als auch für querschnittliche Themen wie beispielsweise Security.

Um was geht es — ein Überblick

Die Hauptrolle in dieser Folge spielt nginx. Ein Webserver, den wir als Reverse Proxy konfigurieren, und hinter dem wir alle Services (bestehende und zukünftige) verbergen. Die rote (4) markiert dessen Standpunkt im Gesamtbild („Sie befinden sich hier.“).

Überblick Folge 4

Weiterhin nutzen wir diese Folge, um die bestehenden Services in Docker-Images zu verwandeln. Anschließend können wir sie trotz der unterschiedlichen Laufzeitumgebungen (aktuell: Java/Tomcat, Python/gunicorn, JavaScript/node.js) im Deployment einheitlich behandeln. Auch unser neues Bauteil, den Reverse Proxy, betreiben wir später im Container.

Zunächst lassen wir das Ganze einfach mit Docker Compose auf einem einzelnen Host laufen. Interessantere Verteilungsoptionen (z.B. in der Cloud) heben wir uns für später auf.

Dockerfiles für die bisherigen Services

Docker LogoBisher laufen die Module games, chess-diagrams und play in separaten Prozessen und stellen ihre Funktionalität per HTTP auf unterschiedlichen Ports zur Verfügung. Im einfachsten Fall alle lokal auf localhost, standardmäßig (d.h. falls nicht von Euch verändert) auf den Ports 8080 (games via Apache Tomcat), 8000 (chess-diagrams via gunicorn) und 7000 (play via Node.js). Die von play ausgelieferte SPA greift übers Netzwerk nicht nur auf ihre eigenen Ressourcen (JavaScript-Quelltexte) zu, sondern auch auf die anderen beiden Services (um mit den Partien zu interagieren und das Schachbrett zu visualisieren).

Für diese Folge habe ich die Module zur Vereinheitlichung jeweils mit einem Dockerfile ausgestattet, um Images dafür zu bauen. Docker führt eine Virtualisierung auf Betriebssystemebene durch, die auch als Containerisierung (engl. containerization) bezeichnet wird. Dieser Spickzettel gibt einen guten Überblick über die wichtigsten Docker-Befehle — Alternativen findet Ihr etwa bei ZeroTurnaround (Cheat Sheet Docker) und DZone (Refcard #221). Weitere Details zu Docker auf deren Homepage.

Die Dockerfiles liegen jeweils direkt im entsprechenden Modulverzeichnis, also zum Beispiel micro-moves/modules/chess-diagrams/Dockerfile für chess-diagrams (Quelltexte hier). Ein Docker-Image bauen und einen entsprechenden Container starten könnt Ihr zum Beispiel für das Modul chess-diagrams per Kommandozeile so:

$ cd micro-moves/modules/chess-diagrams
$ docker build . -t chess-diagrams
$ docker run -p 8000:8000 -t chess-diagrams &

Die folgende Tabelle beinhaltet die verwendeten Basis-Images und Run-Commands innerhalb der Dockerfiles von FLEXess.

Modul Basis-Image („FROM name:tag“) Kommando („CMD“)
games openjdk:8 java -jar games.jar
chess-diagrams python:3.6 gunicorn chess_diagrams:app
play node:8 npm start

Beim games-Modul müsst Ihr vorher die Java-Anwendung mit Gradle bauen (siehe Folge 1). Das Dockerfile kopiert nämlich lediglich das resultierende jar-File (games.jar) mit allen Abhängigkeiten („Uberjar“, frei nach Nietzsche) in das Image. Auf den Build in einem Container habe ich verzichtet.

Verknüpfung mit einem Reverse Proxy

Logo nginxViel gewonnen haben wir noch nicht. Die Prozesse laufen jetzt in Docker-Containern, aber die play-SPA beispielsweise muss immer noch die Adressen/Ports von games und chess-diagrams kennen.

Um die interne Struktur von FLEXess (inkl. weiterer Services in der Zukunft) zu verbergen setzen wir einen sogenannten Reverse Proxy ein.

Bei einem „klassischen“ (Web-)Proxy nehmen Clients beispielsweise innerhalb eines Unternehmensnetzwerkes über diesen Kontakt mit der Außenwelt (dem Internet) auf. Die Motivation in Unternehmen ist oftmals das Filtern von Inhalten (kein Facebook bei der Arbeit). Bei einem Reverse Proxy hingegen nehmen Clients von außen über diesen Kontakt mit in unserem Netz liegenden Servern auf. Der direkte Aufruf von außen wird unterbunden, alles Anfragen gehen über den Reverse Proxy.

Directory Layout reverse-proxyAuf diese Weise kann ein Client alle Module von FLEXess über eine Adresse (Hostname und Port) ansprechen. Das Routing erfolgt mit Hilfe der URL. Als Software nutzen wir hierzu nginx. Hierbei handelt es sich um einen sehr rasanten Open Source Webserver, der sich seit einigen Jahren als Alternative zum Apache HTTP-Server etabliert. Er kann als Reverse Proxy betrieben werden, die Dokumentation dazu findet Ihr hier: „NGINX Reverse Proxy“.

Die Konfigurationsdatei nginx.conf für unsere Anwendung liegt in einem eigenen Modulverzeichnis reverse-proxy gemeinsam mit dem passenden Dockerfile (Struktur siehe Abbildung). nginx übernimmt nun auch die Aufgabe. die „Homepage“ von FLEXess auszuliefern. Zuvor hatte games eine provisorische Homepage. Die statischen Inhalte dazu liegen im reverse-proxy-Modul in einem Unterverzeichnis static. Die passende Konfiguration steht ebenfalls in nginx.conf. Die folgende Tabelle zeigt die Weiterleitung an die bisherigen Bauteile der Serie (wie in nginx.conf definiert).

Anfragemuster (location) Weiterleitung (proxy_pass) Beschreibung
/games-api/ http://games:8080/api/ REST API, Folge 1
/chess-diagrams/ http://chess-diagrams:8000/ Service für Diagramme, Folge 2
/games/ http://games:8080/ Spring Web MVC Oberfläche, Folge 3
/games-websocket/ http://games:8080/games-websocket/ WebSocket-Kommunikation, Folge 3
/play/ http://play:7000/ Auslieferung der SPA, Folge 3

Die Konfiguration der Weiterleitung für WebSocket ist dabei etwas spezieller, der Beitrag „NGINX as a WebSocket Proxy“ beschreibt, wie Ihr das in nginx anstellt. Dass die Weiterleitung mit den Hostnamen (wie z.B. play in http://play:7000/) funktioniert liegt am Docker-internen Netzwerk. Dazu jetzt …

Das Ganze fliegen lassen mit Docker Compose

Docker Compose LogoUm die mittlerweile immerhin vier Container unserer FLEXess-Applikation komfortabel bauen, starten und stoppen zu können bedienen wir uns Docker Compose. Hierbei handelt es sich um ein Werkzeug, um Anwendungen einfach zu betreiben, die aus mehreren Containern bestehen.

Einen guten Einstieg und Überblick über das Werkzeug gibt dessen Dokumentation („Overview of Docker Compose“). Gegenüber „ausgewachsenen“ Orchestrierungswerkzeugen wie Kubernetes hat es Einschränkungen, in unserer Blog-Serie hier zählt aber erst einmal „einfach“.

Directory Layout micro-movesDie Konfigurationsdatei im YAML-Format, die alle Bestandteile der Applikation aufzählt, liegt als docker-compose.yml auf einer Ebene mit den Modulen. Docker Compose nennt die Anwendung standardmäßig wie das Verzeichnis, also „modules“. Dies lässt sich per Kommandozeilenparameter -p (für project name) überschreiben. Um diesen nicht jedesmal angeben zu müssen legen wir eine weitere Konfigurationsdatei .env mit dem Projektnamen „flexess“ im Verzeichnis ab, die docker-compose bei jedem Aufruf ausließt.

Ein Start der kompletten FLEXess-Anwendung (Stand bis jetzt) vom Auschecken bis zum Nutzen läuft per Kommandozeile wie folgt ab.

$ git clone https://github.com/embarced/micro-moves.git
$ cd micro-moves/modules/games/
$ ./gradlew assemble
$ cd ..
$ docker-compose build
$ docker-compose up &

Installiert sein muss dafür ein JDK (zum Bauen von games) und Docker. Unter Ubuntu Linux etwa die folgenden Pakete mit apt-get: openjdk-8-jdk-headless dockerdocker-compose. Der verwendete Benutzer muss Mitglied der Unix-Gruppe docker sein, siehe „Post-installation steps for Linux“.

Das reverse-proxy-Modul ist in der Konfiguration das einzige, das einen Port veröffentlicht (bei uns 9000 für den HTTP-Transport von nginx, konfiguriert in docker-compose.yml). Die Anwendung ist daher im Anschluss über http://hostname:9000 im Browser aufrufbar. Die übrigen Services stehen nur innerhalb des Anwendungseigenen Netzes zur Verfügung (zu Netzwerken in Docker Compose siehe „Networking in Compose“). nginx wohnt selber in dem Netz und findet sie über ihre Service-Namen (z.B. chess-diagrams).

Die play-SPA kann die anderen Services (chess-diagrams, games) nun ansprechen, ohne deren Hostnamen und Port zu kennen. Sie verwendet einfach ihre eigenen Werte, die sich leicht per JavaScript im Browser ermitteln lassen. Es müssen dann nur noch die URLs erweitert werden, damit nginx das mit dem Routing hinbekommt (siehe Tabelle oben),

Docker Compose ermöglicht auch eine horizontale Skalierung „out-of-the-box“. Beispielsweise bringt der Befehl

$ docker-compose up --scale chess-diagrams=3 &

drei Exemplare des entsprechenden Services ins Spiel, die unser Reverse Proxy auch automatisch alle nutzt (Load Balancing). Mit dem games-Service funktioniert das leider nicht so gut, da er (noch) zustandsbehaftet ist.

Docker-Container nach Skalierung, angezeigt mit ps

Später werden Services auch innerhalb der FLEXess-Applikation andere Services direkt nutzen (nicht nur die play-SPA andere). Das Auffinden von Services innerhalb des Docker-eigenen Netzwerkes erspart uns hier den Einsatz einer separaten Service Registry wie beispielsweise Netflix Eureka oder HasiCorp Consul. Chris Richardson beschreibt dieses Lösungsmuster in seinem Das Microservices Patterns Buch  genauer, siehe auch seine Musterbeschreibung online.

Neben den Kommandozeilentools gibt es auch einige graphische Werkzeuge zum Beobachten von und Interagieren mit Docker-Containern. Ganz hübsch finde ich Kitematic, das Teil der Docker Toolbox ist. Die folgende Abbildung zeigt die Oberfläche mit FLEXess in Betrieb.

Kitematic in Aktion

Aktuelle Beschränkungen. Und ein Ausblick, wie es weitergeht

Wir haben mit diesem Beitrag und Bauteil einen gehörigen Satz gemacht. Weitere Services unserer FLEXess-Applikation lassen sich nun leicht integrieren und im Reverse Proxy bekannt machen.

Das ist auch nötig, denn es gibt funktional und qualitativ noch Luft nach oben, z.B.:

  • Spieler können sich nicht auf der Plattform registrieren, anmelden und Partien spielen. Das passiert im Moment anonym.
  • Eingehende Züge bleiben praktisch ungeprüft. Es ist daher leicht möglich, regelwidrige Züge zu machen.
  • Das games-Modul überprüft ebenfalls nicht, ob eine Partie zu Ende ist. Hierzu wäre die Stellung auf dem Brett zu analysieren (Schachmatt, Patt).
  • Das aktuelle Setup lässt die Applikation mit Docker Compose lediglich auf einem einzelnen Host laufen.

Weitere Folgen dieser Blog-Serie adressieren (u.a.) die oben genannten Mängel. Das führt zu weiteren Services (etwa für die Spielerverwaltung und die Schachregeln) aber auch querschnittlichen Aspekten (allen voran Security).

Bleibt also (so hoffe ich) spannend.

Ach ja: Fragen und Anregungen sind natürlich jederzeit gerne Willkommen. Per Kommentar hier im Blog oder gerne auch per Mail direkt an mich …

Zur Blog-Serie Micro Moves

 

 

Figuren ziehen mit Single Page Application in Vue.js (Micro Moves, Bauteil 3)

By | Inhaltliches | No Comments

Blog-Serie Micro Moves -- LogoMit den ersten beiden Bauteilen der Micro Moves Blog-Serie sind zwei Services unserer Online-Schachplattform entstanden. Der games-Service für die laufenden Partien bietet eine REST-Schnittstelle (Folge 1), der chess-diagrams-Service liefert auf HTTP-Anfrage eine PNG-Bild mit der Situation auf dem Schachbrett (Folge 2).

In dieser Folge machen wir nun erste Schritte Richtung „echtes“ Frontend und kombinieren die beiden. Ziel ist es im Web-Browser eine neue Partie zu eröffnen und Züge zu machen. Ein zweiter Spieler kann sie sehen und seinerseits ziehen. Ihr könnt dann zu zweit gegeneinander Schach spielen immerhin schon. Schönes Iterationsziel also.

Um was geht es — ein Überblick

Diese Folge liefert sowohl ein Beispiel für ein klassisches Request-Response-basiertes Frontend, als auch eine Single Page Application (kurz SPA). Das klassische Frontend bauen wir im games-Service ein (im Bauplan unten die (1)). Der SPA spendieren wir ein neues Modul play; sie nutzt die beiden Services. Die rote (3) markiert deren Standpunkt der SPA im Gesamtbild („Sie befinden sich hier.“).

Überblick FLEXess

In dieser Folge kommen Spring Web MVC mit Thymeleaf als Template-Mechanismus zum Einsatz und das JavaScript-Framework Vue.js für die SPA. Wenn ein Spieler einen Zug macht, muss sein Gegner diesen sehen. Für diese Kommunikation vom Server zu den Clients nutzen wie WebSocket.

Aber der Reihe nach …

Die UI-Frage

Im Zusammenhang mit Microservices finde ich die Frage, wie man mit dem User Interface umgeht, besonders spannend. Martin Fowler und James Lewis sprechen in ihrer „Definition“ des Architekturstils von einer Anwendung, die aus unabhängigen Prozessen besteht. D.h. für den Benutzer sollte es sich wie ein UI anfühlen, obwohl die Lösung aus technologisch unterschiedlich realisierten Teilen besteht. Wie unser FLEXess hier auch. Wie stellt man das an?

In diesem Zusammenhang gibt es reichlich zu entscheiden. Rich Client vs. Web, im Falle Mobiler Endgeräte Native vs. Hybrid etc. Konzentrieren wir uns auf den häufigen Fall einer Webapplikation! Auch hier gibt es unterschiedliche Spielarten (Request/Response vs. SPA sehen wir später noch, und auch hier gibt es Hybrid-Ansätze).

Im Zuge vertikaler Architekturstile entscheidet unabhängig davon eine Frage: Trägt eine einzelne Vertikale (z.B. ein Microservice) sein UI mit sich, oder sind die Vertikalen UI-los. Die folgende Abbildung illustriert die beiden Extreme (auch hier gibt es Lösungen dazwischen).

UI-Optionen

Konkret kann das so aussehen, dass in Modell 1 links im Bild jeder Service neben der Business-Logik (BL) ein Webfrontend in sich trägt. Die Integration findet im Browser etwa über Links statt. Im Modell 2 bieten die Services die Business-Logik z.B. über eine REST-Schnittstelle an, Eine übergeordnete UI (etwa native Apps oder eine SPA) nutzt diese.

Im ersten Modell haben Teams den kompletten Technologie-Stack inkl. Frontend in der Hand und können z.B. die UI-Technologie zu einem gewissen Grad frei wählen. Dafür ist es bei diesem Ansatz fordernd, dem Anwender eine Anwendung aus einem Guss zu präsentieren. Modell 2 ermöglicht tendenziell eine bessere User Experience, aber unter Verzicht einer unabhängigen Entwicklung der einzelnen UI-Teile und echt Cross-funktionaler Teams (für das übergeordnete UI ist oft ein separates Team zuständig).

Der Widerspruch User Experience vs. unabhängige Entwicklung lässt sich abschwächen, etwas durch eine Integration im Build.

Web-Applikation mit Spring Web MVC und Thymeleaf

Ein technologisch „klassischer“ Ansatz gemäß Modell 1 (Vertikale mit UI Huckepack) erzeugt die Oberfläche serverseitig mit einem geeigneten Framework, und liefert an den Browser HTML-Seiten. Vielleicht angehübscht mit CSS und ein bisschen JavaScript für dynamische Aspekte, damit es nicht ganz so 90er rüberkommt. Eine Interaktion mit dem Nutzer läuft über einen Request/Response-Zyklus ab. Der Browser sendet ausgelöst durch einen Link oder das Absenden eines Formulars eine Anfrage, der Server antwortet mit dem neuen Seiteninhalt.

Zur Illustration dieses Ansatzes erweitern wir hierzu unser games-Modul für die Schachpartien entsprechend (Quelltext hier). Mit Spring Web MVC (Model View Controller) ist das flink gemacht. Insbesondere da wir in Folge 1 die Geschäftslogik getrennt von der REST-API gehalten haben. Die Klassen für die Weboberfläche findet Ihr im Paket org.flexess.games.web, das analog zur REST-API auf die Services zugreift:

Pakete im games-Modul (Diagramm: Teamscale)

Annotationen an den Controller-Klassen legen das Mapping von URLs zu Verhalten fest. Das Generieren der Seiten übernimmt Thymeleaf (Verwendung im Build-File Gradle angeben). Thymeleaf versteht sich als  moderne serverseitige Java-Template-Engine.

Thymeleaf LogoFür mehr Details: Der Beitrag „Getting Started: Serving Web Content with Spring MVC“ nutzt ebenfalls Thymeleaf und zeigt die Integration in Spring Boot. Etwas mehr zum Funktionsumfang der Template-Engine selbst zeigt Eugen Paraschiv in seinem Artikel „Introduction to Using Thymeleaf in Spring“.

Die Templates für unser games-Modul liegen im Verzeichnis src/main/resources/templates. Sie sind mit Bootstrap angehübscht. Die Startseite erreicht Ihr aktuell mit http://localhost:8080, wenn Ihr den Service wie in Folge 1 baut und startet. Weitere Seiten sind von dort verlinkt (Screenshots siehe Abbildung unten).

Später steuert games einen Beitrag zur Gesamtoberfläche von FLEXess gemäß UI-Modell 1 bei. Das diskutieren wir, wenn ein zweites Bauteil mit eigenem UI hinzu kommt, und die beiden sich aus einem Guss präsentieren wollen.

Screenshots Web Application

Grundsätzlich ließe sich mit dem Ansatz die ganze Oberfläche bauen. Die größte Herausforderung liegt in der Interaktion während einer Partie. Jeder Schachzug müsste mit einem Request zum Server gelangen, und der Gegner (anderer Client, anderer Browser) ihn auch sehen, um seinerseits mit einem Zug zu antworten.

Die Dynamikanforderungen an das UI nehmen wir zum Anlass, um als Alternative zur Request/Response-basierten Oberfläche eine Single Page Application (kurz SPA) zu zeigen. Ihr könnt dieses Teil als Blaupause für ein UI nach Modell 2 sehen.

Single Page Application mit Vue.js

Bei einer SPA liefert der Server eine einzelne (HTML-)Seite an den Client. Jede weitere Interaktion läuft mit Hilfe von JavaScript im Browser ab. Mit dem Server kommuniziert eine SPA typischerweise via REST. Passenderweise hält das games-Modul eine solche Schnittstelle bereit (vergleiche Folge 1, die Rolle von curl und Postman dort übernimmt jetzt die SPA).Vue.js Logo

Vue.js ist ein JavaScript-Framework zur Entwicklung eben solcher SPAs. Vergleichbare Lösungen (und Mitbewerber) wären React oder Angular.js. Die Vue.js-Homepage bietet einen guten Einstieg in die Entwicklung mit dem Framework.

Für unseren Fall hier haben wir lediglich das Spielen einer Partie als SPA umgesetzt. Aus der klassischen Web-Oberfläche gelangt man über einen Link zu ihr (siehe Screenshot oben, grüner Button „Play Game!“). Die zu spielende Partie wird via ID als Request-Parameter übergeben und von der play-SPA ausgelesen.

Die Quelltexte zur SPA findet Ihr hier. Sie sind überschaubar: Lediglich eine HTML-Seite (SPA halt ;-)) und dort eingebettet drei JavaScript-Dateien. Die folgende Tabelle gibt einen Überblick:

Datei Beschreibung
index.html Trägt die initiale HTML-Seite mit Layout, Navigation etc. und verweist auf die JavaScript-Dateien
src/main.js Die eigentliche Vue.js-Anwendung mit Daten und Verhalten
src/games.js Funktionen für die REST-Calls gegen die API des games-Moduld (z.B. Ziehen) sowie für die WebSocket-Kommunikation mit games.
src/chess-diagrams.js Funktionen für die Einbindung der Schach-Diagramme des chess-diagrams-Moduls.
package.json Informationen für npm. (Siehe unten)

Neben diese Dateien sind noch einige Fremdbibliotheken erforderlich. Vor allem Vue.js natürlich, aber auch Bootstrap für das Layout und Bibliotheken für WebSocket (siehe unten). Ich habe es mir hier einfach gemacht, und die Abhängigkeiten als Links auf ein CDN (Content Delivery Network) eingepflegt. Somit lädt der Browser sie einfach aus dem Internet. Alternativ wäre auch ein Build-Prozess mit geeigneten Tools denkbar. Das hebe ich mir für einen späteren Beitrag auf, wenn wir mehr mit JavaScript machen.

Die Anwendung starten und verwenden

Theoretisch könnte man die Anwendung lokal im Web-Browser öffnen. Sehr realistisch wäre das aber nicht — die Spieler müssen ja irgendwie an den Client kommen. Besser wäre da schon eine Verteilung über das games-Modul, das ja bereits Seiten per HTTP ausliefert (siehe oben).

Ich habe mich für einen separaten Serverprozess für play entschieden, um die Sachen voneinander entkoppelt zu halten (Angelehnt an Modell 2). Eine recht schlanke Lösung ist ein HTTP-Server, der sich via Node.js betreiben lässt (von Node.js werden wir später in dieser Serie noch mehr hören).

„Installation“ und Betrieb können bei installiertem npm (Node Package Manager, liegt Node.js bei) so erfolgen:

$ git clone https://github.com/embarced/micro-moves.git
Cloning into 'micro-moves'...
$ cd micro-moves/modules/play/
$ npm install
$ npm start

> play@0.1.0 start /Users/stefanz/Documents/GitHub/micro-moves/modules/play
> httpserver -p 7000

lo0: 127.0.0.1
utun0: null
vboxnet1: 192.168.99.1
en5: 192.168.2.124
server started: http://0.0.0.0:7000
$

 

Der verwendete Port 7000 ist in package.json hinterlegt. Die Spring Web MVC-Applikation oben ist schon vorbereitet. Deren „Play Game!“-Link verweist genau auf diese Adresse mit der ID der Partie als Parameter. Die SPA stellt sich dann so im Browser dar:

SPA play in Aktion

Die SPA setzt nach laden der index.html einen REST-Call gegen das games-Subsystem ab. Sie holt sich Partie-Informationen zur ID, die sie zum Beispiel in der Titelzeile (Namen der Spieler) anzeigt. Hingucker ist sicherlich die Darstellung des Brettes. Unter der Haube ein Bild aus dem chess-diagrams-Service (HTML-Tag img mit geeigneten src-Attribut. Die URL wird mit dem FEN-Wert aus den Spielinformationen als Parameter versorgt). Damit das Ganze funktioniert muss neben play (lokal, Port 7000) und dem games-Services (lokal, Port 8080) auch der chess-diagrams-Service (lokal, Port 5000, vgl. Folge 2) laufen.

Die Eingabe des Zuges erfolgt entweder als Text im Input-Feld (z.B. „e2e4“ für e2 nach e4), oder durch klicken in die Brettdarstellung. Eine Funktion aus chess-diagrams.js ermittelt die Koordinaten (z.B. „h4“) und fügt sie ins Input-Feld ein. Keep it simple.

Aktuell überprüft die Anwendung noch nicht, ob der Browser-Benutzer überhaupt an der Partie beteiligt ist. Den Themen Authentifizierung und Autorisierung widmen wir einen späteren Beitrag. Folgerichtig kann ein Benutzer jetzt einfach auf dem Brett gegen sich selbst spielen.

Der Screenshot oben ist in einer neuen Partie nach einem weißen und einem schwarzen Zug entstanden, gefolgt von einem weiteren Versuch den schwarzen Springer auf f6 zu bewegen. Allzu viel prüft der games-Service aktuell nicht (später übernimmt das der Spielregeln-Service). Zu Testzwecken checkt er zumindest, ob die Farbe der Figur passt. Daher die Warnmeldung im Screenshot.

Kommunikation mit Web Socket

Wie läuft die Kommunikation nun genau ab? Nach Eingabe des Zuges und Abschicken des Formulars  (Button „Send Move“) setzt die SPA einen REST-Call (POST Request) gegen den games-Service ab (Funktion sendMove() in games.js). Der landet dann in der Methode createMove() des GameRestController von games. Im Falle eines Fehlers (wie oben, Figur falscher Farbe) liefert diese einen passenden HTTP-Fehlercode zurück.

Playing ChessWenn der Zug akzeptabel ist speichert games ihn, führt ihn aus (ermittelt also die neue Spielsituation) und aktualisiert die Game-Entität (speziell: wie sieht das Brett aus, wer ist am Zug …?). Das erfolgt in der Methode createAndPerformMove() aus der Klasse GameService in games.

Die SPA könnte sich jetzt den neuen Zustand der Partie via GET-Request gegen games holen — sie „weiß“ ja, dass sich die Spielsituation durch Ausführung des Zuges geändert hat. Das ist allerdings nur die halbe Miete. Denn es gibt ja in der Regel noch einen zweiten Spieler mit einem anderen Browser, der ebenfalls über die Änderung informiert werden muss, um dann seinerseits einen Zug zu machen. Die Spieler ziehen ja abwechselnd.

Die SPA verbindet sich daher stattdessen per WebSocket mit dem games-Server, und lauscht auf Ereignisse. Der andere Spieler tut es ihm mit seinem Browser gleich. Im Falle einer neuen Spielsituation sendet games eine Nachricht an ein Topic, welche die neue Spielsituation enthält. Wenn beide Spieler mit ihren SPA dieses Topic abonnieren kriegen sie es mit. Das Topic lautet bei uns /topic/game_XY, wobei XY die ID der Partie ist.

Die Rolle des WebSocket-Servers übernimmt die Spring Boot-Applikation games. Die Konfiguration steckt in der Klasse org.flexess.games.web.WebSocketConfig. Auf Client- (also SPA-)Seite sind die betreffenden JavaScript-Funktionen in games.js implementiert (webSocketConnect() etc.). Die folgende Abbildung illustriert das Zusammenspiel zwischen zwei SPAs (Weißer und Schwarzer Spieler) und dem games-Service als Sequenzdiagramm. Weiß und Schwarz spielen jeweils einen Zug.

Sequenzdiagramm (plantUML)

Bei der Umsetzung habe ich mich am Beitrag „Using WebSocket to build an interactive web application“ orientiert, der ein „Hello World“ mit Spring Boot und WebSocket beschreibt.

Weitere Informationen — und wie es weiter geht …

Das Zusammenspiel der drei Einzelteile play, games und chess-diagrams ist im Moment noch „etwas“ fragil. Als Showcase mag es taugen, aber alle Teile laufen auf localhost und die Adressen und Ports sind in der Vue.js-Applikation als Konstanten fest verdrahtet (zu Beginn von games.js und chess-diagrams.js).

The Majesty of Vue.js 2Idealerweise sollte die SPA nicht alle unterschiedlichen Services persönlich kennen. Und bei horizontaler Skalierung sollten auch mehrere Exemplare der Services laufen können — unsichtbar für den Client. Derartige Themen adressieren wir mit dem nächsten Bauteil. Dann ziehen wir einen Reverse-Proxy ein.

Wenn Ihr mehr zu Vue.js erfahren wollt möchte Ich Euch noch das schöne Buch „The Majesty of Vue.js 2“ ans Herz legen (erschienen bei Leanpub, Cover rechts). Es führt mit vielen kleinen Beispielen lebendig in alle Aspekte ein, die wir hier verwendet haben (z.B. Computed Values zur Aktualisierung der Schachbrett-Graphik nach einem Zug).

Ach ja: Fragen und Anregungen sind natürlich jederzeit gerne Willkommen. Per Kommentar hier im Blog oder gerne auch per Mail direkt an mich …

Zur Blog-Serie Micro Moves

Service für Schach-Diagramme mit Python und Flask (Micro Moves, Bauteil 2)

By | Inhaltliches | No Comments

Blog-Serie Micro Moves -- LogoIn der ersten Folge der Micro Moves Blog-Serie haben wir einen Service auf Basis von Java und Spring Boot an den Start gebracht. Inhaltlich ging es um die laufenden Partien unserer Schach-Online-Plattform FLEXess.

In dieser Folge gibt es einen zweiten Service in Python. Ist eine andere Programmiersprache was tolles? Was spricht dagegen, den Service auch in Java zu bauen?

Um vielen einen Blick über den Tellerrand zu ermöglichen, zeige ich in dieser Blog-Serie unterschiedliche Technologien (und auch Programmiersprachen). Vielfalt ist Teil des Konzeptes. Aber …

Ist polyglott per se schick?

Monolithische Anwendungen stehen in vielen Unternehmen am Pranger. Der Grund dafür ist oft, dass es (zu) lange dauert neue Anforderungen umzusetzen, und/oder in Produktion zu bringen. Release-Zyklen von 3 Monaten und länger sind beispielsweise im Finanzbereich keine Seltenheit.

Mit der Zerlegung in kleine, unabhängige Teile (im Extremfall Microservices, moderater bei Self-contaned Systems) öffnet sich Spielraum, in den Teilen Dinge unterschiedlich zu handhaben. Mitunter wird dies sogar als eine besondere Stärke herausgestellt, für die Teile technologische Entscheidungen passgenau auf das Problem treffen zu können. Ein häufiges Beispiel hier ist die Persistenz. Wenn Services in unterschiedlichen Prozessen laufen, können sie auch leicht in unterschiedlichen Programmiersprachen entwickelt sein. Aber sie müssen nicht. Es sollte Nutzen stiften (in echten Vorhaben ist Vielfalt hier selten ein Wert an sich).

Was macht der Service?

Für verschiedene spätere Folgen benötigen wir Visualisierungen des Schachbretts. Die Forsyth-Edwards-Notation (kurz FEN), wie die die Entität Game aus der letzten Folge benutzt, ist wenig tauglich, um die Spielsituation zu erfassen. Ziel sind Graphiken wie diese:

Schachbrett mit Startaufstellung, generiert mit unserem Python Service

Der Service nimmt eine Spielsituation in FEN entgegen und liefert eine graphische Repräsentation wie oben zurück. Die Funktionalität steht per HTTP bereit, für den ersten Wurf unterstützt er

  • Eingabe in FEN als Request-Parameter
  • Response: Diagramm in fixer Größe (288 x 288 px, Figuren jeweils 32 x 32) und einem Format (PNG).

Es ist noch einiges an Einstellmöglichkeiten (etwa über Parameter) denkbar: andere Größen, Graphikformate, Figurensymbole, Farben … Wir fügen sie hinzu, wenn wir sie im Laufe der Serie brauchen. Die rote (2) markiert Euren Standpunkt im Gesamtbild („Sie befinden sich hier.“).

Lageplan FLEXess, Bauteil 2

Python und Flask

Die Programmiersprache Python (WikipediaHomepage), obgleich recht alt,  erfreut sich gerade aktuell großer Beliebtheit. Der TIOBE-Index (umstritten) führt Python im April 2018 auf Platz 4, RedMonk (umstritten) auf Platz 3. Unbestritten: Trends wie Big Data und Machine Learning erhöhen die Aufmerksamkeit für die Sprache.

Flask Web Development, 2nd EditionPython ist u.a. objektorientiert und in der Regel interpretiert. Ein häufiger Einsatzzweck sind daher Skripte. Ähnlich wie in Java oder .Net liefert Python eine Standardbibliothek mit. Darüber hinaus gibt es noch viel, viel mehr aus der Community. Da wir die Diagramme per HTTP ausliefern wollen brauchen wir eine Web-Applikation. Hier ist Flask recht beliebt und eben genau so ein Framework von Dritten.

Flask bezeichnet sich selbst als Micro-Framework für Web-Applikationen. Es beinhaltet einen Development HTTP-Server und verarbeitet dynamische Anfragen RESTful. Das „Micro“ in Micro-Framework heißt, dass Flask versucht seinen Core einfach aber erweiterbar zu halten. Anders als beispielsweise Django trifft Flask kaum Entscheidungen, etwa welche Datenbanktechnologie zu verwenden wäre. Mehr zu Flask im schönen Buch von Miguel Grinberg (Cover rechts), die zweite Auflage ist gerade frisch raus (März 2018).

Die graphische Arbeit erledigt bei uns Pillow, ein Fork der Python Imaging Library (PIL).

Die Umsetzung

Dank Flask und Pillow ist nicht all zu viel zu tun. Der überschaubare Python-Quelltext des chess-diagrams-Moduls liegt auf GitHub. Ich habe ihn auf zwei Dateien aufgeteilt.

Datei Beschreibung
chess_diagrams.py Enthält die Flask-Applikation mit zwei HTTP-Routen (Test-Seite in HTML und Diagram-Generierung). Abhängigkeiten zu Flask und draw.py
draw.py Enthält Funktionen zur Erzeugung der Diagramme.  Abhängigkeit zu Pillow.

Darüber hinaus liegen die Figurenbilder als PNG im Ordner images, und ein Font für die Brettkoordinaten (a..h, 1..8) im Ordner fonts. Um den Service leicht ausprobieren zu können, ohne FEN in der URL tippen (und enkodieren) zu müssen, liegt eine Test-Seite als HTML im Ordner templates.

Für die Funktion zum Erzeugen einer Diagramm-Graphik ausgehend von einem Web-Request sieht der Call-Graph (generiert mit Doxygen) so aus.

Call Graph (Doxygen)

Die folgende Graphik zeigt die Wirkung der einzelnen draw_xy-Funktionen aus draw.py zum Zeichnen eines Diagramms.

Schritte zum Diagramm in draw.py

Den Service bauen, starten und nutzen

Python ist eine interpretierte Sprache. Übersetzt werden muss der Quelltext daher nicht. Lediglich ein Python-Interpreter muss vorliegen. Getestet habe ich mit den Versionen 2.7 und 3.6. Abhängigkeiten lassen sich mit der Python-eigenen Paketverwaltung pip installieren (pip = pip installs packages). Die Datei requirements.txt zählt  die Pakete auf.

$ git clone https://github.com/embarced/micro-moves.git 
Cloning into 'micro-moves'... 
$ cd micro-moves/modules/chess-diagrams/
$ cat requirements.txt 
Flask
Pillow
$ 

Nach Installation der Pakete ließe sich das Ganze direkt starten. Zuvor lassen wir aber noch Unit- und Integrationtests laufen. Als Testwerkzeug verwende ich pytest. Die Ausführung der Tests in verschiedenen Umgebung (Python 2 und 3) überlasse ich tox. tox ist insbesondere im Umfeld von Continuous Integration beliebt, etwa beim Einbinden in einen CI-Server wie Jenkins. Einfach per pip installieren und tox aufrufen — die Datei tox.ini legt u.a. fest, für welche Versionen die Tests (Dateien mit dem Präfix test_) laufen sollen …

$ pip install tox
$ tox
... 

Test laufen lassen mit tox

Neben Unit-Tests für draw.py sind auch Integrationstests inkl. Flask-Webapplikation enthalten.  Wie Ihr Flask-Anwendungen testet beschreibt diese Seite.

Den Service im Development Web-Server von Flask starten, um ihn z.B. mit einem Browser zu testen, geht am einfachsten so:

$ pip install -r requirements.txt
$ python chess_diagrams.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
... 

Der Service lauscht nun auf localhost, und Ihr könnt ihn direkt mit einem Browser ansprechen über http://127.0.0.1:5000/. Die Testseite enthält ein paar Links um Diagramme zu erzeugen, die dann als PNG zurückgeliefert werden.

Testseite und einzelnes Diagramm im Browser

Der Service im Betrieb

Den von Flask mitlieferten HTTP-Server solltet Ihr nur zu Entwicklungszwecken einsetzen. Das Projekt rät selbst von einem Einsatz in Produktion ab und nennt auf seiner Webseite zahlreiche Alternativen, um Flask-Anwendungen in einem Web-Server zu betreiben. Hierzu zählen Public Cloud-Umgebungen wie Amazon (via AWS Elastic Beanstalk) oder Google App Engine eben so wie selbst betriebene HTTP-Server.
Green Unicorn Logo
Als ein Beispiel zu letzteren zeige ich hier gunicorn (für Green Unicorn, Logo rechts) laut „Selbstauskunft“ ein ziemlich schneller Python WSGI HTTP Server für UNIX. Die Installation kann ebenfalls über pip erfolgen. Die folgenden Zeilen Zeichen den Start unseres Service in gunicorn mit 4 Workern.

$ pip install gunicorn
$ gunicorn -w 4 chess_diagrams:app
[2018-05-01 14:38:20 +0200] [14928] [INFO] Starting gunicorn 19.7.1
[2018-05-01 14:38:20 +0200] [14928] [INFO] Listening at: http://127.0.0.1:8000 (14928)
[2018-05-01 14:38:20 +0200] [14928] [INFO] Using worker: sync
[2018-05-01 14:38:20 +0200] [14931] [INFO] Booting worker with pid: 14931
[2018-05-01 14:38:20 +0200] [14932] [INFO] Booting worker with pid: 14932
[2018-05-01 14:38:20 +0200] [14933] [INFO] Booting worker with pid: 14933
[2018-05-01 14:38:20 +0200] [14934] [INFO] Booting worker with pid: 14934
$

Faktoren und Prinzipien für Microservices und die Cloud

Für Anwendungen die einem aktuellen Architekturstil genügen und die sich vielleicht auch in „der Cloud“ wohlfühlen gelten einige Eigenschaften als zentral. Sehr verdichtet finden sich diese in Form von Listen aus Faktoren oder Prinzipien versammelt und diskutiert, von denen die älteste und bekannteste die Twelve-Factor-App ist. Eine etwas neuere Meinung findest sich E-Book „Beyond the Twelve-Factor-App“ von Kevin Hoffman. Weniger Microservices-lastig sind die ISA-Principles (ISA = Independent Systems Architecture) — sie haben (auch) Self-contained Systems im Sinn und kommen aus deren Umfeld.

Hält man den chess-diagrams Service gegen die Faktoren, so fällt insbesondere auf, dass er im Gegensatz zum games-Service rasant startet. Schnelles Starten ist entscheidend, wenn man auf hohe Last dynamisch mit weiteren Exemplaren reagieren möchte.

Der games-Service liegt bei mir bei etwa 10 Sekunden für den Start. Fairerweise ist ein in Spring Boot realisierter Service schneller oben als ein Java EE Application Server inklusive Service. Hauptkritikpunkt für in Java realisierte Services z.B. im Docker/Kubernetes-Umfeld ist allerdings nicht die Startzeit, sondern der Ressourcenhunger, insbesondere in Bezug auf Speicher.

Weitere Informationen — und wie es weiter geht …

Wenn man von Java kommt wie ich ist Entwickeln in Python wie eine Reise in ein fremdes Land. Die Sprache ist ausgereift und natürlich gibt es alles, was Ihr in Java zum Programmieren nutzt, in Python irgendwie auch. Nur wie? Zum Beispiel: Wie testet Ihr Eure Sachen? Wie bindet Ihr Fremdbibliotheken ein? Welche IDE benutzt Ihr? Einige Sachen waren sehr ungewohnt für mich. Zum Beispiel die parallele Existenz von Python 2 und 3.

The Hitchhiker's Guide to Python (Cover) Ein Buch, das mir hier sehr geholfen hat, ist „The Hitchhiker’s Guide to Python“ von Kenneth Reitz (Cover rechts). Es ist dicht gepackt mit Best Practices, Konventionen und gebräuchlichen Werkzeugen aus dem Python-Universum. Und es ist hier frei online verfügbar (!).

Im weiteren Verlauf gibt es noch ein weiteres Bauteil in Python übrigens. Das für die Spieler. In der nächsten Folge verheiraten wir aber erst einmal die Teile aus den ersten beiden Folgen (games und chess-diagrams) und setzen eine Single Page Application (SPA) als Oberfläche drauf. Ziel: Im Browser gegeneinander spielen können …

Ach ja: Fragen und Anregungen sind natürlich jederzeit gerne Willkommen. Per Kommentar hier im Blog oder gerne auch per Mail direkt an mich …

Zur Blog-Serie Micro Moves

Ein erster Service mit Java und Spring Boot (Micro Moves, Bauteil 1)

By | Inhaltliches | No Comments

Blog-Serie Micro Moves -- LogoNachdem 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?

Unser erstes Bauteil: Der games-Service

Bauteil 1Mit 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:

  • OPEN: Die Partie ist offen, ein Spieler steht fest, ein weiterer kann beitreten
  • RUNNING: Die Partie läuft und die zwei Spieler machen abwechselt ihre Züge
  • ENDED: Die Partie ist beendet.

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

  • Fachlich: Die Überprüfung der Schachregeln bei Zügen — das übernimmt später ein eigener Service. In dieser Fassung werden nur wenige Dinge geprüft, um zu sehen, wie der Service auf fehlerhafte Züge reagiert
  • Technisch: Ein User Interface — der Service bietet zunächst lediglich eine REST-Schnittstelle
  • Technisch: Das Thema Security — diesem Aspekt widmen wir einen eigenen Beitrag

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.“).

Überblick FLEXess inkl. Position des Services games

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.

Die Umsetzung — Spring Boot als Microservices Chassis

Microservices Patterns (MEAP)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 links) 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.

Die Umsetzung — Implementierung in Java

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

Abhängigkeiten zwischen den Paketen (Graphik: TeamScale)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 Service bauen und starten

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

 

Service games startet mit Spring Boot

Den Service nutzen (Abfragen)

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 anlegen (via curl und Postman)

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.

Screenshot Postman

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.

Weitere Informationen — und wie es weiter geht …

API-Design: Praxishandbuch für Java- und Webservice-EntwicklerDer 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. Der 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.

Zur Blog-Serie Micro Moves

 

Micro Moves – Zeitgenössische Softwarearchitektur als Bausatz (Intro)

By | Inhaltliches | No Comments

Logo Micro Moves Vertikale Architekturstile wie Microservices und Self-contained Systems sind in aller Munde. Beispiele für deren Umsetzung mit einer konkreten Technologie, etwa Spring Boot, finden sich reichlich im Netz. Bestimmte Aspekte kommen in der Diskussion meiner Meinung nach allerdings zu kurz — vor allem konzeptionelle. Hierzu zählen etwa die UI-Integration zwischen unterschiedlichen Services und Security-Themen — und ganz generell die Frage nach Makro- und Mikroarchitektur. Was ist für alle Services gleich? Wo profitiert Ihr von speziellen Lösungen? Wie fühlen sich unterschiedliche Technologien an, auch vielleicht im Vergleich zu Sachen die Ihr kennt?

In den nächsten Wochen entsteht hier daher FLEXess (für FLEXible chESS Platform), eine technologisch bunte Online-Schach-Plattform. Zug um Zug zum direkt Nachvollziehen (und Nachbauen). Mit Vertikalen realisiert in unterschiedlichen Programmiersprachen (Java, Python, JavaScript, …). Und querschnittlichen Aspekten wie UIs (von Web-Front mit SPA bis zur nativen App). Sowie der Gelegenheit als menschlicher Spieler gegen den aktuellen Ranglistenersten im Computer-Schach anzutreten.

FLEXess — Zug um Zug eine technologisch bunte Online-Schach-Plattform

Am Beispiel entlang diskutiere ich in dieser Serie wichtige Fragestellungen rund um Technologien und Vorgehen. Ich beantworte sie konkret für unsere Applikation und implementiere sie exemplarisch (Quelltexte auf GitHub). Weiter zeigt die Serie Alternativen auf und skizziert mögliche nächste Schritte. Darüber hinaus verweist jede Folge auf weiterführende Literatur und aus unserer Sicht qualitativ hochwertige Quellen im Netz.

Ihr wollt unterschiedlichste Technologien in einer überschaubaren aber gleichzeitig attraktiven Applikation gemeinsam am Werkeln sehen? Ihr wollt Eurer Wissen zu modernen Architekturstilen und Microservices Patterns auf- oder ausbauen? Und Orientierung in typischen Architekturentscheidungen erhalten.

Na dann los!

Der fachliche Rahmen

FLEXess erlaubt es den Benutzern gegeneinander Schach zu spielen, oder sich mit Computer-Gegnern zu messen. Fachlich zerfällt das ganze in Themen, die wir in unterschiedlichen Teilsystemen implementieren. Für die fachliche Zerlegung größerer Systeme etabliert sich in der Praxis der strategische Teil von Domain-driven Design (DDD). Zentraler Zerlegungsbegriff sind (Sub-)domänen. DDD kategorisiert diese in Core Domains, Supporting Domains und Generic Subdomains  und etabliert Muster der Zusammenarbeit für Teams, welche diese Teile umsetzen.

Ich möchte dem Thema hier gar nicht viel Raum geben — eine grobe Context Map zur Visualisierung der Fachlichkeit soll genügen:

Context Map FLEXess

Subdomäne Begriffe (Auswahl) Aufgabe / Verantwortlichkeit
Laufende Partien Gegner, Partie, Zug Einleiten und verwalten laufender Partien
Spieler Spieler Verwaltung der (menschlichen) Spieler, Registrierung etc.
Spielregeln Position, Züge Überprüfen von Zügen auf Gültigkeit, Test auf Partie-Ende …
Diagramme Bild Generierung von Schachdiagrammen als Graphiken (z.B. als PNG) zur Einbindung in Webseiten, Berichte etc.
Computer-Gegner Chess Engine Anbindung von Computerschachprogrammen

Nick Tune: The Strategic Practices of Domain-Driven DesignStatt der Standardliteratur zu DDD möchte ich Interessierten die schöne Materialsammlung von Nick Tune zum Thema Strategisches DDD ans Herz legen! In dem klasse gestalteten E-Book beschreibt der Autor Techniken wie Business Canvas, Event Storming etc.

In unserer Blogserie soll der Schwerpunkt jedoch auf der Umsetzung liegen.

Unsere Umsetzung: Ein Überblicksbild

Eine allgemein akzeptierte Definition für Microservices gibt es nicht. Und schlußendlich ist es auch egal, ob unsere Umsetzung einen Architekturstil „gültig“ implementiert. Gerade bei Microservices gibt es viel Gestaltungs- und Entscheidungsspielraum.

Martin Fowler und James Lewis beschreiben in Ihrem viel beachteten Blogbeitrag, der gerne als Definitionsersatz für Microservices mißbraucht wird, von einem Ansatz, der eine einzelne Anwendung mit Hilfe kleiner Services umsetzt. Diese laufen in eigenen Prozessen und kommunizieren über leichtgewichtige Mechanismen.

Die folgende Abbildung gibt einen groben Überblick über die Umsetzung von FLEXess, wie sie in den folgenden Wochen hier entstehen soll. Änderungen nicht ausgeschlossen, sondern umgekehrt wahrscheinlich.

Überblick FLEXess

Die folgende Tabelle ordnet die vertikalen Services den fachlichen Subdomänen aus der Context Map oben zu und nennt die wesentlichen verbauten Technologien und den Blogbeitrag, in dem sie erstmalig eingeführt werden …

Service Subdomäne Wesentliche Technologien Blog-Beitrag
chess-diagrams Diagramme Python, Flask, Pillow Folge 2
games Laufende Partien Java, Spring Boot Folge 1
rules Spielregeln
players Spieler

FLEXess unterstützt Web-Browser als UIs, weiterhin bietet es eine native Android-App für einen Teil seiner Funktionalität an (das Spielen von Partien). Als Kommunikation kommen im Wesentlichen HTTP und REST zum Einsatz, asynchrone Kommunikation über Messaging. Ein Reverse Proxy lässt die darunter liegenden Vertikalen (Services), die als eigenständige Prozesse laufen, wie eine Anwendung erscheinen.

Aber der Reihe nach.

Wie geht es weiter?

Wir beginnen in den nächsten Folgen mit der Umsetzung der ersten zwei Services. Anschließend diskutieren wir die UI-Frage — klassische Web-Applikation vs. SPA (Single Page Applikation) etc. — und stellen die zwei Vertikalen in einem Browser zur Verfügung. Den Anfang macht aber das Partien-Subsystem „games“ mit einer rein REST-basierten Schnittstelle …

Ach ja: Fragen und Anregungen sind natürlich jederzeit gerne Willkommen. Per Kommentar direkt hier oder gerne auch per Mail direkt an mich.

Zur Blog-Serie Micro Moves

viennaJS meetup_OZeigermann

Vienna JS Meetup: React and Typescript

By | Inhaltliches, Publikationen | No Comments
„ViennaJS & React Joint Meetup: React and Typescript.“

ViennaJS & React Joint Meetup

Vienna JS Meetup: React and Typescript
Speaker: Oliver Zeigermann and Dave Bruderstein
presented at React Vienna + Vienna JS joint event
July 26, 2017 at 7pm
Sektor5, Siebenbrunnengasse 44, 1050 Vienna

Video recorded by pusher

Oliver and Dave gives a practical demo about using React and Typescript – a brief introduction of what Typescript feels like.
No slides, just code.

React and Typescript. No slides, just code. - pusher session

follow us on Twitter – @embarced

bild_architectur_kata_SToth

Architektur Kata Live – Eine Rückschau

By | Allgemein, Inhaltliches, Publikationen, Vorträge | No Comments

Auf dem Software Architecture Summit 2017 in München habe ich einen halbtägigen Architektur Kata Workshop durchgeführt. Hier ein kurzer Einblick inklusive der erarbeiteten Ergebnisse und Präsentationsfolien. Mehr Informationen zu Architektur Kata gibt es auch in meinem Buch Vorgehensmuster für Softwarearchitektur

„Architektur Kata Live“
Logo Software Architecture Summit
 
Architektur Kata Live
Sprecher: Stefan Toth
Halbtagsworkshop auf dem Software Architecture Summit, München
Dienstag, 14. März 2017, 10 – 13 Uhr
Novotel München Messe, Willy-Brandt-Platz 1, 81829 München Riem

Was ist Ihrer Meinung nach wichtig, um als Sportler Erfolg zu haben? Genetische Veranlagung? Talent? Geld? Glück? Je nach Sportart werden diese Dinge mehr oder weniger Einfluss haben, fest steht jedoch: Alle Sportler trainieren und steigern ihre Leistungsfähigkeit zwischen den Wettkämpfen. Auch Tänzer, Moderatoren oder Musiker bereiten sich professionell auf ihre Aufgaben vor – sie tanzen, machen Sprechübungen, lernen oder proben. In Architektur Kata wird diese Professionalität auf Softwarearchitektur übertragen. Das Konzept der „Kata“ ist dabei Kampfsportarten entliehen, die damit die wiederholte Ausführung von Bewegungsabläufen im Übungsumfeld bezeichnen, um im Ernstfall gerüstet zu sein.
Was wir auch in Projekten und Firmen schon gemacht haben, wurde nun auf die große Teilnehmergruppe am Architecture Summit adaptiert. Über fast 70 Teilnehmer zwängten sich in einen etwas zu kleinen Raum und erhielten die Möglichkeit Systeme zu entwerfen, technisch relevante Fertigkeiten einzusetzen und ihre Ideen kreativ festzuhalten. Wiederholt, abwechslungsreich und im Vergleich mit anderen.

Die ausgewählten Architektur Kata waren „Real Fantasy NFL“ und „Lights, please“. Beide direkt von der ehemaligen Webseite des Erfinders Ted Neward (Teds Kata Seite) und sehr unterschiedlich. Einmal geht es um eine hochskalierbare und äußerst elastisch umzusetzende Webseite für Fantasy Football inkl. Highlight Videos, einmal um Heimautomatisation und die damit zusammenhängenden Herausforderungen für einen Elektronikartikelhersteller.
In Gruppen arbeiteten die Teilnehmer insgesamt etwa 1:15 an ihrer Lösung und an ihrem Problemverständnis. Lösungen wurden gegenseitig vorgestellt und bewertet. Hier die Gruppenergebnisse:

Hier gibt’s noch die Folien der Session zum Download.

Drei Dinge, die mich bei Architekturdiagrammen mit plantUML nerv(t)en

By | Allgemein, Inhaltliches | 8 Comments

plantIch sehe bei Kunden ab und an plantUML im Einsatz. Und noch häufiger fragen mich Teilnehmer in Workshops, was ich von plantUML halte, wenn es um Diagramme in der Dokumentation von Softwarearchitektur geht. Beispielsweise in den betreffenden Abschnitten von arc42 (Konkret: Kontextabgrenzung, Bausteinsicht, Laufzeitsicht …).

Um ehrlich zu sein fand ich plantUML dafür lange Zeit nicht so toll. Aus verschiedenen Gründen. Bei näherem Hinsehen ließ sich einiges davon aber auf Unkenntnis und Vorurteile meinerseits zurückführen. Und einige Unschönheiten lassen sich mit den richtigen Kniffs und Workarounds umgehen. Ein paar kleine Erkenntnisse zeige ich im Rahmen dieses Beitrags. Und beginne so meinen Frieden zu machen mit dem Werkzeug.

Aber zuvor: Was ist dieses plantUML überhaupt (für diejenigen, die es nicht kennen) und warum finde ich es (immernoch) nicht ganz so toll wie manche meiner Kunden?

plantUML

plantUML generiert UML-Diagramme als Graphik aus textuellen Beschreibungen. Die Syntax dazu ist recht einfach gehalten und schnell erlernt. Hier ein Minibeispiel. Aus der Eingabe links generiert plantUML das Diagramm rechts.cd_schach

@startuml

Schachbrett -> Figur 
Figur --> Farbe
Figur --> enum Art

enum Farbe { 
 SCHWARZ 
 WEISS
}

@enduml

 

Neben den besonders gut unterstützen Klassendiagrammen (wie im Beispiel oben, Details zur Syntax siehe hier) kann plantUML auch andere Diagrammarten der UML generieren (Sequenzdiagramme, Anwendungsfalldiagramme …).

plantUML ist Open Source, in Java geschrieben und leicht integrierbar. Entsprechend gibt es zum Beispiel Plugins für verschiedene Textsysteme (z.B. Asciidoctor), Build-Werkzeuge (z.B. Gradle) und Wikis (z.B. XWiki und Confluence). Gerade in diesen Fällen passt das Text-Format von plantUML sehr gut, fühlt es sich doch in der Versionsverwaltung ebenso wohl wie zwischen Wiki-Markup.

In der Regel nimmt der Autor beim Erstellen kaum Einfluss auf das Layout seiner Diagramme. Ich sehe das als Vorteil, kostet das Kästchenschubsen in graphischen Editoren bei Änderungen doch oftmals viel Zeit.

Drei Dinge, die ich an plantUML nicht mag

Trotz der unbestrittenen positiven Merkmale gibt es Dinge, die mir bei plantUML nicht so gut gefallen. Hier drei aus meiner Sicht besonders gewichtige:

  1. Der Technologie-Stack ist fragil
  2. Für Softwarearchitektur relevante Diagramme fehlen
  3. Die generierten Diagramme sehen altbacken aus

Der Technologie-Stack ist fragil

plantUML ist in Java geschrieben, der Download lediglich ein jar-File. Tatsächlich handelt es sich bei bei dem Tool aber eher um ein Frontend (oder wenn man mag einen Präprozessor) für das Werkzeug Graphviz.

binaryGraphviz ist eine leistungsfähige und recht verbreitete Open Source-Lösung zum Visualisieren von Graphen (also Knoten verbunden durch Kanten). Anders als plantUML wird es Betriebssystem-spezifisch als Binary installiert. Es muss lokal vorliegen (konkret: das Graphviz-Executable „dot“). plantUML greift darauf zu, es verwandelt die plantUML-Eingabe in eine Eingabe in der DOT-Sprache und lässt dann Graphviz die ganze Arbeit machen („Zwerge auf den Schultern von Riesen“).

Das Problem dabei: das Zusammenspiel zwischen plantUML und dot führt regelmäßig zu Reibung. Auch eine saubere Graphviz-Installation verweigert schon mal die Arbeit und führt zu Fehlermeldungen, lustigerweise in Form einer Graphik anstelle des erhofften Ergebnisses. Sieht dann zum Beispiel so aus:

Fehler beim Aufruf von not / Graphviz

In der Praxis heißt das etwa für ein Java-Projekt, das seine Architekturdokumentation in den Build integrieren will, dass es höhere Anforderungen an das Build-System stellt. Also an alle, die das Projekt bauen wollen. Java installieren reicht nicht (wie bei Gradle durch den Wrapper eigentlich üblich), es muss auch noch Graphviz da sein und mit plantUML zusammenspielen.  Das berühmte „It Works on My Machine“ war doch eigentlich überwunden (OK, die Pest ist schlussendlich auch noch nicht ausgestorben). Auch entsprechende Beiträge der Plugin-Benutzer in den Foren von XWiki und Confluence singen ein Lied von dem Problem.

Für Softwarearchitektur relevante Diagramme fehlen

plantUML unterstützt verschiedene Diagrammarten der UML, und die wiederum verschiedenen gut. Bei Klassendiagrammen geht mit Abstand am meisten. Gerade diese Diegrammart benötige ich allerdings in einem Architekturüberblick eher selten, bestenfalls für ein grobes Domänenmodell.

struktur_kleinBesonders wichtig sind mir methodisch gesehen die Kontextabgrenzung (Kapitel 3 in arc42, bzw. der Abschnitt „Context“ bei Simon Brown) und die statische Zerlegung (Kapitel 5 „Bausteinsicht“ in arc42). Eine Kontextabgrenzung lässt sich mit plantUML ganz gut visualisieren übrigens. Meine favorisierte Darstellung von Zerlegungshierarchien mit den Ports der Kompositionsstrukturdiagramme unterstützt plantUML leider nicht. Strukturen lassen sich zwar mit gruppierenden Elementen abbilden; Interaktionspunkte auf der „Membran“ (Ports, siehe kleine Skizze links) gehen hingehen nicht.

Die Diagramme sehen altbacken aus

Geschmäcker sind verschiedenen, mir persönlich gefällt die Darstellung von plantUML tatsächlich nicht sonderlich. Die Buchstaben-Icons bei Klassen, Aufzählungen etc. (E, C, … siehe Schach-Beispiel oben)  sind nicht mein Fall, und vor allem:  Die Farben (gelbe Kästen, rote Linien) haben die Anmutung alter Rational-Werkzeuge (kleine Zeitreise, Video). Die Zeit wünsche ich mir nicht zurück. Den Retro-Look der 90er-Jahre komplett machen dann Komponenten im Stil der UML 1. Als Beispiel hier ein rudimentärer Systemkontext (System mit einem Benutzer als Akteur) in plantUML, rechts wieder das resultierende Diagramm (das System als Komponente in UML 1-Notation):

@startumlsc_altbacken
[Webshop] <<system>>
:Kunde: - Webshop
@enduml

Auf Diagramme mit dieser Anmutung stolpere ich regelmäßig in Projekten. Dass mir die nicht übermäßig gefallen ist mein Problem, wenn die Teams es so mögen … Mein Problem könnt Ihr gleichzeitig beheben, bei plantUML läßt sich nämlich so ziemlich jede Stileigenschaft (Fonts, Farben, Strichbreiten …) konfigurieren. Aber wer hat da schon Lust zu? Der Aufwand mit all den Schaltern schreckt erstmal ab.

Die gute Nachricht: Es gibt recht große Schalter — Hebel sozusagen — welche mit wenig Aufwand viel Wirkung zeigen. Für das Beispiel oben reichen zwei dieser wirkmächtigen Hebel, und es sieht für mich deutlich passabler aus (siehe wieder Graphik rechts):

@startumlsc_modern
skinparam monochrome true
skinparam componentStyle uml2

[Webshop] <<system>>
:Kunde: - Webshop
@enduml

Das gefällt mir richtig gut schon. Derartige Skin-Parameter lassen sich übrigens auch in Dateien auslagern, von aussen in den plantUML-Prozessor reinreichen, etc. — So verschmutzen sie auch nicht alle plantUML-Quelltexte.

Fazit

Der Ansatz, aus text-basierten Beschreibungen Graphiken/Diagramme zu generieren, hat viel für sich. In vielen Fällen nutzen Teams ein Wiki oder Text-Systeme wie Asciidoc, um ihre Architekturdokumentation festzuhalten. Die Frage, wo die Diagramme herkommen, stellt sich dort stets, und plantUML ist zumindest eine Option. Mit wenigen Kniffen finde ich die Ausgabe zumindest optisch passabel, eine große Herausforderung bleibt das fragile Zusammenspiel mit Graphviz.

Ich spiele mit dem Gedanken hier ein geschlossenes Fallbeispiel bereitzustellen. Einen Architekturüberblick, in dem die Diagramme mit plantUML generiert sind, und dessen Inhalte sich an den Sichten von arc42 orientieren. Fänden Sie das interessant?