Category

Inhaltliches

MicroMoves Folge 7_

Security: Spieler für alle Services anmelden mit JWT (Micro Moves, Bauteil 7)

By | Inhaltliches | No Comments

Blog-Serie Micro Moves -- LogoMittlerweile haben wir mit den Folgen der Micro-Moves-Serie  schon einiges an Funktionalität zusammen. Benutzer von FLEXess können Partien starten und im Browser gegeneinander und gegen einen Computer-Gegner (seit Folge 6) antreten. Das Thema Sicherheit haben wir bisher allerdings komplett ausgespart. Wir ändern das in dieser Folge.

Um was geht es — ein Überblick

Aktuell können anonyme Benutzer über den games-Service (siehe Blog-Folge Bauteil 1) Partien mit zwei beliebigen Spielernamen (Texteingabe) einleiten. Anschließend können sie über die  play-Oberfläche (siehe Blog-Folge Bauteil 3) Züge absetzen. Mittlerweile prüft der rules-Service (siehe Blogfolge Bauteil 5) zwar, ob der Zug regelkonform ist. Wer da am anderen Ende des Browsers sitzt spielte aber keine Rolle bisher. Jeder Benutzer kann bei jeder Partie mitziehen, nicht nur in seinen eigenen. 

Mit dieser Folge führen wir einen neuen Service ein: players. Er ist in Python implementiert und ermöglicht es Benutzern, sich als Spieler zu registrieren und am System anzumelden. Die Web-Oberfläche hierzu bauen wir mit Flask (analog zu Bauteil 2 chess-diagrams, aber mit HTML-basierter Oberfläche).

Bei erfolgreicher Anmeldung stellt players ein JWT-Token (siehe unten) aus, mit dem sich der Benutzer auch bei anderen Services authentifizieren kann. Hier ist insbesondere games zu nennen, das ab dieser Folge unter anderem sicherstellt, dass nur angemeldete Benutzer, die mit von der Partie sind, tatsächlich ziehen können.

Eine Warnung: Unsere Implementierung ist eine einfache Lösung zu Illustrationszwecken. In echten Microservices-Vorhaben ist der Einsatz von JWT zwar ebenfalls üblich, nicht aber das Selberstricken der Authentifizierung wie hier in players. Ich diskutiere am Ende gängige Alternativen hierzu.

JWT — JSON Web Token

JSON Web Token, kurz JWT (ausgesprochen: „jot“), ist ein Standard für die sichere Übermittlung von Behauptungen (engl. Claims) bei gleichzeitig geringem Platzbedarf (also Ressourcen-Verbrauch). JWT genießt breite Unterstützung durch (Web-)Frameworks und Bibliotheken in allen nur erdenklichen Programmiersprachen. Unter anderem auch in den in Micro Moves bisher verwéndeten: Java, Python und JavaScript.

Ein JWT-Token sieht so aus (die Zeilenumbrüche gehören nicht zum Token):

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
eyJzdWIiOiJwcGFudGhlciIsIm5hbWUiOiJQYXVsIFBhbnRoZXIiLCJyb2xlcyI6InVzZXIifQ.
cM93rF0CUXk5PxH8elR0paJ4PFqtbr0RbcFNFBocsmQ

Auf den ersten Blick also wüst, tatsächlich aber lediglich drei mit Base64-kodierte Bestandteile, mit Punkten voneinander getrennt:

  • Header mit Meta-Informationen, z.B. zum Signierverfahren
  • Payload (Nutzlast)
  • Signatur

Die Seite jwt.io stellt ein schönes Online-Werkzeug zum Token-Kodieren und -dekodieren zur Verfügung. Kopiert man das obige Token in diese Seite (siehe auch Screenshot unten), kann man Header und Payload lesen.

Header: {
"alg": "HS256",
"typ": "JWT"
}

Payload: {
"sub": "ppanther",
"name": "Paul Panther",
"roles": "user"
}
 

Das Token ist nicht verschlüsselt, sondern lediglich mit dem Verfahren HMAC / SHA-256 (kurz HS256, Angabe im Header) digital signiert (der dritte Teil des Tokens). Hierbei handelt es sich um ein symmetrisches Verfahren. Derselbe Schlüssel wird server-seitig zum Signieren benutzt und auch zum Überprüfen der Signatur (ebenfalls server-seitig, ggf. durch andere Module, der Client kriegt den Schlüssel nicht zu sehen). Auch dieses Überprüfen kann der Debugger auf jwt.io zeigen. Einfach den Schlüssel bei „Verify Token“ eingeben. In unserem Beispiel lautete er „secret123“.JWT Debugger bei jwt.ioDie Payload sind die eigentlichen Daten innerhalb des Tokens, deren „Echtheit“ die Signatur sicherstellt. Konkret handelt es sich hier um den Anmeldenamen (sub, kurz für subject), den Namen eines Benutzer und seine Rolle(n). Bei uns gibt es die Rollen user, admin und engine.

Wie stellen wir nun solche Token aus? Wie übermittelt ein Client das Token an andere Services? Und wie überprüfen diese die digitale Signatur? Schauen wir es uns der Reihe nach an! 

Ein Python-Service zum Anmelden von Spielern

Der Spieler ist als Konzept bisher nicht präsent. Spielernamen tauchen in Form von Zeichenketten im games-Subsystem auf, das wars. Unser neuer Service players ist eine simple Verwaltung von Spielern in Python. Der Quelltext von players ist wie immer auf GitHub verfügbar (unter modules/players). Über Webseiten ermöglicht players

  • … alle Spieler aufzulisten.
  • … einen neuen Spieler zu registrieren.
  • … Spieler an- und abzumelden.

Beim letzten Punkt kommt JWT zum Zuge, um die Information, dass ein Spieler sich erfolgreich am System angemeldet hat, inkl. weiterer Claims sicher an andere Teile des Systems (allen voran games) zu tragen.

Technologisch ähnelt players dem Python-Service chess-diagrams aus Folge 2 dieser Blog-Serie. Es kommen ebenfalls Flask als Web-Framework und gunicorn als Web-Server zum Einsatz. Mit Jinja2 kommt eine Template-Engine hinzu und mit TinyDB eine einfache Persistenzlösung. Für das für JWT nötige Kodieren und Dekodieren nutzt players die Python Bibliothek PyJWT. 

players beinhaltet folgende Seiten und Funktionalität.

Seite (URL) Funktionalität
/ Startseite. Listet alle Spieler in einer Tabelle auf.
/register Formularbasierte Registrierung eines neuen Spielers.
/profile_userid Profilseite für den Spieler mit dem Benutzernamen userid.
/login Anmeldung durch Benutzername und Kennwort.
/logoff Abmelden des angemeldeten Benutzers.
/about Über das Modul players.

Für die Seiten sind unter den URLs Funktionen für Flask im Python-File players.py hinterlegt, die auf entsprechende HTTP-Requests reagieren. Sie delegieren für die Darstellung der resultierenden (einfach gehaltenen) Seiten auf mit Jinja2 realisierte HTML-Templates.

Der neue Service lässt sich separat starten (z.B. via Kommando python3 player.py, Tests analog zu chess-diagrams via tox), ich hab ihm auch wieder ein Dockerfile spendiert und ihn in die Konfiguration von docker-compose mit aufgenommen. Die Seiten sind unter dem Pfad /players/ im Reverse-Proxy eingeklinkt, d.h. http://localhost:9000/players/about liefert in der Konfiguration und bei lokalem Start die About-Seite zum Modul.

players macht sich das Speicherns der registrierten Benutzer und ihrer Rollen sehr einfach und nutzt dazu TinyDB. Da Micro Moves in erster Linie Konzepte erläutert reicht das zur Illustration. Zu Alternativen und ernstzunehmenden Lösungsansätzen weiter unten.Startseite playersKonsequenterweise — den Spielzeugcharakter mit TinyDb im Blick — speichert players keine Kennwörter ab. Auch nicht verschlüsselt oder als Hash. Es erhebt nicht einmal Kennwörter. Die Authentifizierung simulieren wir durch Überprüfen, ob der Benutzer als Kennwort überhaupt etwas eingibt. Dieses „Sicherheitskonzept“ ist natürlich nicht viel besser als eine Admin-Party (alle Partygäste haben Admin-Rechte), wie wir sie vor dieser Folge hatten. Aber zur Illustration reicht es uns hier. — Liebe Kinder, falls Ihr das hier seht: Probiert das nicht zuhause an Eurem Produktionssystem aus!! 😉 

Im Falle der erfolgreichen Anmeldung stellt das players-Modul ein JWT-Token aus und sendet es als HTTP-Cookie an den Browser. Da die Micro Moves Module hinter einem gemeinsamen Reverse Proxy stehen, teilen sie sich für den Client von außen gesehen Hostname und Port. Das Cookie wird daher vom Browser bei Aufrufen — und das ist entscheidend — an alle beteiligte Module gesendet, insbesondere auch an games.

Das Erzeugen („Kodieren“) und Auslesen („Dekodieren“) des JWT-Tokens ist in players in web_token.py implementiert. Die Herausforderung dabei ist die digitale Signatur (Signieren beim Ausstellen, Überprüfen beim „Wiederkommen“). Die eigentliche Arbeit übernimmt die Python-Bibliothek PyJWT

Für das hier gewählte Signierverfahren HS256 benötigen wir einen geheimen Schlüssel („Shared Secret“). Wir geben ihn der Einfachheit halber von außen über eine Umgebungsvariable in den Docker-Prozess. Das Ganze lässt sich mit docker-compose recht einfach konfigurieren.  Hier ein Ausschnitt aus der Datei docker-compose.yml


players:

    build: ./players/
    environment:
        – JWT_COOKIE_NAME
        – JWT_SECRET

Die Werte der beiden Variablen sind in der Konfigurationsdatei .env abgelegt. Unter dem Cookie-Namen legt players das JWT-Token im HTTP-Response ab und sendet es an den Browser zurück. Umgekehrt versucht players mit diesem Namen den JWT-Token als Cookie-Wert aus einem HTTP-Request auszulesen.

In Abhängigkeit vom empfangenen Cookie ändert sich der rechte Teil der Navigation. Falls keines da ist, stehen Links zum Registrieren und Anmelden zur Verfügung. Ansonsten ein Dropdown für Benutzerprofil und Abmelden (siehe Abbildung unten). Name und User ID des Benutzers werden dazu aus dem JWT-Token entnommen.der rechte Teil der Navigation

Der Konfiguationsparameter JWT_SECRET ist das „shared secret“ für die digitale Signatur. Wer dieses hat, kann Signaturen aus players überprüfen (siehe z.B. unten im games-Modul). Ein Angreifer könnte damit auch selbst „gültige“ JWT Tokens ausstellen. Der Wert sollte daher nur vertrauenswürdigen Beteiligten zugänglich gemacht werden.

Um dieses Sicherheitsrisiko zu umgehen werden statt HS256 gerne asymmetrische Verfahren mit einem öffentlichen Schlüssel zum Überprüfen (erhalten alle) und einem geheimen Schlüssel zum Signieren (erhält nur der Aussteller) gewählt. Mehr dazu etwa im schönen Beitrag „The Importance of Using Strong Keys in Signing JWTs“.

Autorisierung im Subsystem games (Java)

Das games-Modul interessiert sich sehr für den Benutzer hinter einem Request. Nur ein angemeldeter Benutzer sollte …

  • … neue Partien eröffnen (mit sich selbst als 1. Spieler).
  • … offenen Partien als 2. Spieler beitreten.
  • … Züge in seinen Partien machen.

Für letzteres sollte er zugleich nicht nur Spieler in der zugehörigen Partie sein, sondern auch am Zug.

Gerade das letzte Beispiel zeigt, das die Autorisierung, d.h. die Entscheidung über die Berechtigung, im Modul games gut aufgehoben ist. players kann die Authentifizierung klären, und relativ statische Rollenzugehörigkeiten liefern. Ob der Benutzer irgendwo am Zug ist weiß es nicht.

Anonyme Benutzer lassen wir Partien auflisten und anzeigen, auch das zusehen von Partien anderer Spieler lassen wir zu. Weiterhin ist es angemeldeten Benutzern erlaubt, gegen sich selbst zu spielen. Das aber eher aus didaktischen Gründen — es lässt sich so weiterhin leichter ein wenig herumspielen über das play UI. Administratoren (d.h. Benutzer mit Rolle admin) können den Computer-Spieler Stockfish (vgl. Bauteil 6) gegen sich selbst spielen lassen — „normale“ Benutzer (also nur Rolle user) können nur selbst gegen Stockfish antreten.

Eine neue Partie eröffnenDas Auslesen des JWT-Tokens ist in games als Servlet-Filter realisiert (Klasse JwtCookieFilter im Package org.flexess.games.user). Der Prozess erhält über Umgebungsvariablen den Namen des Cookies und das Shared Secret. Er „fischt“ den Cookie, falls vorhanden, aus dem Request und verifiziert die Signatur des enthaltenen JWT-Tokens mit Hilfe des Shared Secrets. Hier kommt die JWT-Java-Bibliothek von Auth0 zum Einsatz. Im Erfolgsfall baut das Servlet ein User-Objekt mit User ID (z.B. ppanther), Namen (z.B. Paul Panther) und Rollen (z.B. user) und legt es im Request als Attribut ab. 

Die Spring Web MVC-Controller können dann darauf zugreifen, und die oben beschriebenen Überprüfungen durchführen (Beispiel: Ist der Benutzer angemeldet? Ist er am Zug? …). Und sie können Werte automatisch setzen. Wenn zum Beispiel der angemeldete Benutzer einer Partie als 2. Spieler beitritt, kann games dessen User ID in die Partie-Entität eintragen — je nachdem als schwarz oder weiß. Und den Namen im Menu anzeigen und Links aufs Profil gehen auch … 

Informationen auslesen in play (Vue.js)

Für das Spielen im Browser hatten wir in Bauteil 3 eine Single-Page-Application play mit Vue.js gebaut. Hier ist für das Berücksichtigen der neuen Funktionalität vergleichsweise wenig zu tun. Wenn ein Benutzer angemeldet ist, und in die SPA wechselt, sendet der Browser auch von dort unseren Cookie mit dem JWT-Token an die hinter dem Reverse Proxy liegenden Web-Applikationen.

Die Interaktion zwischen SPA und games erfolgt über REST — unser ServletFilter greift auch bei diesen Anfragen, fischt den Cookie aus dem HTTP-Request validiert das enthaltene User-Objekt gegen das Shared Secret und legt es als Attribut im Request-Objekt ab.

Die Überprüfung, ob der Benutzer, der einen Zug sendet, tatsächlich am Zug ist, erfolgt in der Java-Klasse GameRestController (Package org.flexess.games.rest) in games. Falls nein gibt es eine Fehlermeldung ( HTTP 403, Forbidden) zurück, die in play fachlich angezeigt wird, siehe Screenshot.Der angemeldete Benutzer ist nicht am Zug.In der SPA play selbst habe ich auf Sicherheitsabfragen verzichtet. Das hätte zwar die Benutzbarkeit erhöht (Beispiel: Man darf nur Züge absenden, wenn man am Zug ist), allerdings könnte ich dann genau solche Überprüfungen nicht gut demonstrieren.

In play ist somit eigentlich gar nichts zu tun. Insbesondere brauchen wir die digitale Signatur des JWT-Tokens nicht verifizieren. Der Browser sendet es einfach unverändert zurück — games verifiziert es auf dem Server. Sollte ein Angreifer auf dem Client ein anderes Token unterschieben, sollte games das merken, da die Signatur nicht stimmt.

Das einzige, was wir in play tun, ist den Benutzer mit User ID und Namen für das Menu anzuzeigen. Da die SPA die ganze Browser-Seite belegt, müssen wir die Funktionalität doppeln (mehr zu diesem Kompromiss in der nächsten Folge dieser Blog-Serie). Dazu müssen wir das JWT-Token allerdings nur auslesen und dekodieren, nicht verifizieren. Hierzu brauchen wir den Schlüssel nicht. Insbesondere muss der Shared Key nicht in die SPA gelangen — was auch fatal wäre. Wegen des symmetrischen Verfahren wäre das Fälschen eines Anmelde-Tokens damit ein Leichtes.

Das Dekodieren eines JWT Tokens in play ist in JavaScript ziemlich leicht. Anders als beim digitalen Signieren in Python (players) und Überprüfen der Signatur in Java (games) und Python (players) kommen wir hier mit Bordmitteln, also ohne Fremdbibliothek durch. Siehe Funktionen getPayloadFromJWT und getUserFromJWT in der Datei players.js in play.

Einschränkungen und Alternativen

Die Benutzerdaten speichert players aktuell lokal über TinyDB in einer Datei ab. Das macht den Docker-Container zustandsbehaftet und damit nicht redundant betreibbar. Eine Persistenz-Lösung außerhalb des Prozesses wäre daher sinnvoll, naheliegend ein Verzeichnisdienst wie Active Directory oder OpenLDAP. Das sind auch seriöse Orte, um Kennwörter abzulegen, die Authentifizierung durchzuführen oder Gruppenzugehörigkeiten zu speichern. Vielleicht zeige ich das nochmal in einer späteren Folge dieser Blog-Serie, und erinnere mich an meine LDAP-Vergangenheit.

Eine andere Option wäre gleich eine Lösung, die Authentifizierung und Single Sign-on für Webapplikationen „hauptberuflich“ implementiert, beispielsweise CAS oder Keycloak. Auch die Möglichkeit, sich via Google-Account etc. anzumelden, ist denkbar. Beim Betrieb in Cloud-Umgebungen oder Container-Orchestrierungen steht Authentifizierung oftmals als Dienst zur Verfügung und wäre auch eine Option.

In den Modulen rules und chess-diagrams habe ich das JWT-Token bisher nicht überprüft. Bei chess-diagrams ist es aktuell nicht auch nötig. Auch nicht angemeldete Benutzer können eine Partie verfolgen. rules ist aktuell von außen nicht erreichbar, bisher greift nur games darauf zu. Hier könnte man das Token bereits jetzt durchschleifen und in rules überprüfen. Den Einbau hole ich mach, wenn wir rules direkt von außen nutzen (kommt noch).

Unsere Schachplattform wächst und gedeiht — das Menu weist nun zwei Bereiche auf. Einen für die Partien (Games), einen für die Spieler (Players). Da die beiden Bereiche separate Webanwendungen sind, und in games auch noch die SPA play steckt, ist das Menu aktuell 3x implementiert. Inkl. der Logik für das Anzeigen des Benutzers bzw. des Login-Links. Das Hinzufügen eines weiteren Bereiches mit eigenem Menu macht das Anfassen an allen anderen Teilen erforderlich, um die Menüpunkt aufzunehmen.

JWT HandbookWeitere Informationen. Und wie es weiter geht.

Wenn Ihr mehr über JWT erfahren wollt lege Euch das schöne E-Book von Sebastián Peyrott ans Herz: JWT Handbook (Cover siehe rechts).

Aktuell wird die Startseite statisch vom Reverse Proxy geliefert. Im nächsten Beitrag in dieser Serie fügen wir mit der Homepage ein weiteres Modul mit UI-Anteil hinzu und diskutieren dabei auch den obigen Kompromiss im Menu (ein neuer Punkt muss in allen Modulen eingefügt werden) und Alternativen dazu. 

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

Zur Blog-Serie Micro Moves

Zeigermann_poster_Neural Embeddings

Understanding Neural Embeddings – Poster by Oliver Zeigermann

By | Inhaltliches, Publikationen | No Comments

Understanding Neural Embeddings

Understanding Neural Embeddings
Speaker: Oliver Zeigermann
Data Institute SF, Annual Conference
March 10 to 12, 2019
Data Institute SF, University of San Francisco, San Francisco, CA
@DataInstituteSF #DSCO19

Oliver picks up on his poster a short overview of the semantic embedding of data on the basis of example airline data. How can the data be embedded and how does it support machine learning? You will also find a link to the notebook that generates these embeddings and visualizations.

Oliver Zeigermann, Poster: Understanding Neural Embeddings

follow us on Twitter – @embarced

eBook: arc42-Starschnitt Gradle bei Leanpub

By | Bücher, Inhaltliches, Publikationen | No Comments
arc42 Starschnitt: Gradle

arc42-Starschnitt Gradle, Cover

arc42 Starschnitt: Gradle – Ein Architekturüberblick in Lebensgröße
Autor: Stefan Zörner
Deutsch, ca. 137 Seiten, Januar 2019
Verlag: Leanpub


Das Buch bei Leanpub kaufen

Dieses Buch illustriert zentrale Inhalte einer Architekturdokumentation nach arc42. Hier erhalten Sie Impulse zum Dokumentieren Ihrer Softwaresysteme und lernen arc42 auf vergnügliche Weise kennen. Für eigene Lösungsüberblicke können Sie sich an den Zutaten und Beispielinhalten orientieren. Nebenbei lernen Sie etwas über das Buildsystem Gradle.

Zum Thema Dokumentationskonzepte

IT-Tage 2018_SZoerner_embarc

IT-Tage 2018 – Was (genau) ist eigentlich Architekturbewertung?

By | Inhaltliches, Publikationen, Vorträge | No Comments
„Nörgeln ist einfach. Aber was (genau) ist eigentlich Architekturbewertung?“

IT-Tage 2018

Nörgeln ist einfach. Aber was (genau) ist eigentlich Architekturbewertung?
Sprecher: Stefan Zörner
Vortrag bei den IT-Tagen 2018
Dienstag, 11. Dezember 2018, ab 11:30 Uhr
Frankfurt am Main, im Kongresshaus Kap Europa

Foliendownload (PDF)



Statler: “Das war wirklich mal was zum Lachen!”
Waldorf: “Ja, das ist echt komisch gewesen!”
Statler: “Was glaubst Du – ob das beabsichtigt war?”
(aus der Muppet Show)


Jedes interessante Softwaresystem hat eine Softwarearchitektur. Diese ist kunstvoll geplant oder zufällig entstanden, meist aber irgendwas dazwischen. Hätte man es anders machen sollen? In diesem Vortrag stelle ich vor, wann und wie Ihr Eure Softwarearchitektur bewertet! Seid Ihr auf dem richtigen Weg? Können Eure Architekturideen in der Umsetzung aufgetretene Probleme effektiv lösen? Helfen diese bei der Erreichung Eurer Ziele oder behindern sie diese eher? Architekturbewertung kann Sicherheit schaffen und Risiken aufzeigen und damit helfen die Aufwände im Vorhaben zu fokussieren. Ihr lernt qualitative und quantitative Bewertungsmethoden kennen: Was argumentative, Workshop-basierte Verfahren wie ATAM leisten thematisiere ich ebenso wie welche Aspekte Eurer Architekturziele sich mit Messungen verknüpfen lassen.

Stefan Zörner - Stefan Zörner - Was (genau) ist eigentlich Architekturbewertung

follow us on Twitter – @embarced

Bullshit_Bingo_Artikel JAXenter

Artikel auf JAXenter – Hohle Phrasen in der IT erkennen und vermeiden

By | Artikel, Inhaltliches, Publikationen | No Comments
Bullshit Bingo: Wie man hohle Phrasen in der IT erkennt und vermeidet

JAXenter Logo


Bullshit Bingo: Wie man hohle Phrasen in der IT erkennt und vermeidet
Autor: Herbert Dowalil
Online-Beitrag bei JAXenter, erschienen am 19. Oktober 2018

Online lesen auf JAXenter


Jeder kennt ihn, jeder belächelt ihn und jeder benutzt ihn hin und wieder mal als Werkzeug, um unverdient zu Glänzen: Den Bullshit. Er ist in unserer Branche so omnipräsent, dass er eigentlich kaum noch wegzudenken ist. Es muss die Frage erlaubt sein, worum es sich dabei eigentlich handelt und warum gerade wir in der IT so massiv darunter zu leiden haben.

Online lesen

 
follow us on Twitter – @embarced

Software vermessen_Blog Dowalil

Über die Vermessung von Software

By | Inhaltliches | No Comments


“If you can´t measure it, you can´t manage it”

Das wusste schon Peter Drucker! Wenn sich die Qualität von Software als Zahl wiedergeben lässt, so wird es wesentlich einfacher gute Entscheidungen zu treffen. Aber auch wenn Sie so eine Bürde nicht zu tragen haben, möchten Sie vielleicht wissen, wie weit Sie im Zuge von Verbesserungsmaßnahmen eines Produktes bereits gekommen sind. Es ist also kein Wunder, dass es den Wunsch nach der Objektivierung durch Kennzahlen in der Welt der IT ebenfalls gibt. Zunächst klingt es verlockend, denn so scheint es doch, als würden sich die Bits und Bytes von Software besonders für die Abbildung durch Metriken eignen. Tatsächlich habe ich in meiner Karriere aber sehr oft erleben müssen, dass durch die Anwendung einer Kennzahl mehr Schaden angerichtet wurde, als Nutzen generiert. In diesem Artikel möchte ich Ihnen einerseits Möglichkeiten aufzeigen, Software in Zahlen zu Vermessen, als auch andererseits vor typischen Fallstricken warnen, die es im Zuge dessen zu vermeiden gilt.

Kategorien von Software Metriken
Zyklomatische Komplexität nach McCabe als Beispiel
Mögliche Fallstricke beim Einsatz von Metriken
Mögliche Lösungsansätze beim Umgang mit Metriken

Eingehen möchte ich auf Kennzahlen zur Messung von Komplexität und struktureller Qualität von Software. Tatsächlich gibt es noch weitere Kategorien von Metriken im Umfeld von Software. Bei der folgenden Auflistung beziehe ich mich dabei ausschließlich auf Kennzahlen, welche sich auf das fertige Software-Produkt beziehen, und nicht auf solche, die im Umfeld davon existieren. Es gibt Kennzahlen, welche versuchen die Produktivität bei Entwicklung der Software zu vermessen, oder die Effizienz im Zuge der Benutzung. Dies ist aber nicht Fokus dieses Artikels und ich verweise dafür auf das Buch Software in Zahlen von Harry M. Sneed, Richard Seidl und Manfred Baumgartner.

Kategorien von Software Metriken

Quantität

Dabei wird die schiere Menge an Software gemessen, welche vorhanden ist. Es kann sich um die Anzahl an Codezeilen handeln, die Anzahl an Klassen, Codedateien oder auch Bildschirmmasken, welche vorhanden ist. Der Vorteil dieser Art von Kennzahl ist die Transparenz, da man sich darunter relativ einfach etwas vorstellen kann. Der Nachteil ist, dass es relativ wenig über die Software aussagt. Beim Hauptschirm einer Applikation wie Microsofts Word ist mit Sicherheit das x-fache an Komplexität enthalten, als in einer durchschnittlichen Bildschirmmaske.

Komplexität

Wie schwierig ein Stück Software zu verstehen oder zu entwickeln ist wird wesentlich besser von den Kennzahlen aus dem Gebiet der Komplexität wiedergegeben. Wenn Sie die Wartungs- oder Entwicklungstätigkeiten in Relation setzen möchten, so verwenden Sie bitte besser eine dieser Kennzahlen.

Struktur

Diese Art von Metriken misst die Kapselung des Codes in Bausteine, und auch, wie diese dabei zueinander in Relation stehen. Beispiele dafür wären die Software-Package-Metrics von Robert C. Martin oder auch die Metriken von John Lakos. Ich ziehe dabei diejenigen von John Lakos vor, da diese Abhängigkeiten transitiv betrachten. Eine Abhängigkeit, wo Baustein B den Baustein A benötigt wirkt sich dort nämlich auch auf einen potentiellen Baustein C aus, wenn dieser den Baustein B benützt. Dies entspricht viel mehr der tatsächlichen Natur der Abhängigkeiten im Code.

Testautomatisierung

Die bekannteste Kennzahl aus dem Bereich der automatisierten Tests ist mit Sicherheit die der durch Unit-Tests abgedeckten Code-Zeilen. Testabdeckung wird dabei aber auch durch System- und Integrationstests erzeugt, welche zwar ebenfalls Code automatisch testen, dabei aber nicht die Kennzahl der Abdeckung durch Unit-Tests beeinflussen. Das kann ein guter oder schlechter Aspekt dieser Kennzahl sein. Tatsächlich ist meine Erfahrung, dass eine sehr niedrige Abdeckung durch Unit-Tests ein Indiz für das Fehlen von Strukturen in der Codebasis darstellt. Unit-Tests testen nämlich per definitionem gezielt die einzelnen kleinsten Bausteine des Codes. Wenn dies nicht passiert, so ist es nicht selten so, dass es diese Zerlegung in Bausteine nicht gibt und das Unit-Testen dadurch gar nicht erst möglich ist.

Qualität

Die Erfüllung gewisser Qualitätsmerkmale der laufenden Software kann ebenfalls recht gut auf objektive Art und Weise gemessen werden. Ein Beispiel dafür ist die Zuverlässigkeitsmessung nach Tom Gilb, welche schlichtweg wie folgt errechnet wird:

(Anzahl richtiger Ergebnisse) / (Anzahl aller Ergebnisse)

Andere Beispiele wären die durchschnittliche Zeit bis zum Zusammenbruch des Systems (Mean-Time-to-Failure) und die durchschnittliche Zeit bis sich dieses wieder von seinem Problem erholt (Mean-Time-to-Recover). Eine Messung der durchschnittlichen Response Time würde wiederum die Erfüllung von Performance Anforderungen prüfen.

Zyklomatische Komplexität nach McCabe als Beispiel

Verdeutlichen möchte ich die Thematik noch anhand eines konkreten Beispiels, nämlich der Zyklomatischen Komplexität nach McCabe, da es sich dabei um die populärste der Komplexitätsmetriken handelt. Als Beispiel soll uns das folgende Code-Snippet dienen:


while (x < 100) {                  // 1

   if (values(x) % 2 == 0) {       // 2 
      parity = 0;                  // 3 
   } else {
      parity = 1;                  // 4 
   }                               // 5 

   switch (parity) {               // 6 
   case 0: 
      System.out.pritln("Even");   // 7 
      break; 
   case 1: 
      System.out.println("Odd");   // 8 
      break; 
   default: 
      System.out.println("Weird"); // 9 
      break; 
   } 

   x++;                            // 10 
}
result = true;                     // 11


Diese Abbildung illustriert die möglichen Wege durch dieses relativ kurze Stück Code. Hier sehen wir 11 verschiedene Knoten (welche auch im Code durch Kommentare am Zeilenende markiert sind) sowie 14 Pfeile, welche die möglichen Pfade zwischen dieses Knoten illustrieren. Die Formel lautet nun:

(Anzahl Pfeile) – (Anzahl Knoten) + 2

Was im konkreten Fall eine zyklomatische Komplexität nach McCabe von 5 ergibt.

Bleibt noch die Frage zu beantworten, was man denn nun konkret mit einer solchen Kennzahl wie McCabe anfangen kann?! Eine mögliche Anwendung ist die Beantwortung der Frage, ab wann man einen Baustein (wie eine Klasse) auf Grund seiner Komplexität weiter aufteilen sollte. Ein hohes Maß an Komplexität könnte bedeuten, dass diese eine Klasse mehr als nur eine Verantwortung hat, was aber im Sinne des Single-Responsibility-Prinzips nicht sein sollte. Außerdem ist eine derart komplexe Klasse auch für den Entwickler schwieriger zu verstehen, und sollte daher eher auf weitere Einzelteile aufgebrochen werden. Es ist demnach durchaus sinnvoll, eine Obergrenze für Komplexität für Klassen oder Methoden festzulegen.

Mögliche Probleme beim Einsatz von Software Metriken

Leider gibt es jede Menge Fallstricke, welche es auf dem Weg zum erfolgreichen Einsatz von Metriken im Unternehmen zu vermeiden gilt. Die am häufigsten anzutreffenden sind:

Einschränkende Sicht auf die Gesamtproblematik

Eine Kennzahl ist eine numerische Abstraktion eines Aspektes einer Software. Die Gefahr beim Messen einer solchen ist, dass daraufhin dieser Aspekt maximiert wird, und dadurch nicht erfasste Aspekte vernachlässigt werden. Ein Beispiel dafür: Ein Unternehmen war ausgesprochen unglücklich über die hohen Kosten, welche vom Rechenzentrum für CPU Zeiten berechnet wurden, welche am Großrechner anfielen. Ergo gab man das Ziel aus, die CPU Nutzung am Großrechner messbar zu reduzieren. Als möglicher Verursacher wurde die Kompilierung von Cobol Sourcen ausgemacht. Auf die Aufforderung, die Sourcen in Zukunft auf alternativer Hardware zu übersetzen und erst danach auf den Großrechner zu übertragen, konnte das Development Team aufzeigen, dass die Kosten für die Mehraufwände die möglichen Ersparnisse durch die Reduktion der CPU-Zeiten überstiegen hätten.

Auf Umwegen zum Ziel

Viele Wege führen nach Rom. Ähnliches gilt für die Erfüllung von Kennzahlen. Meist möchte man, dass mit der gewünschten Veränderung einer Kennzahl auch eine gewünschte Veränderung im Unternehmen stattfindet. Allerdings wird dieses eigentliche Ziel dabei oft nicht erreicht, einfach weil übersehen wird, dass ein für die Mitarbeiter angenehmerer Weg ebenfalls zum gewünschten Ziel führt. Im schlimmsten Fall kann genau das sogar zu einer Verschlechterung der Situation führen. Das klassische Beispiel um dies darzustellen ist die folgende Geschichte, die sich so angeblich in Indien zugetragen hat, als es noch von den Briten besetzt war: Ein Gouverneur wollte die Anzahl der Kobras in seinem Herrschaftsgebiet reduzieren, indem er ein Kopfgeld für jede tote Kobra ausbezahlte. Das Ergebnis war aber, dass sich die bestehende Kobra-Plage in der Gegend sogar noch verschlimmerte. Durch das Kopfgeld erhielten tote Kobras plötzlich einen Wert. Was dazu führte, dass die Einwohner begannen, Kobras zu züchten und später zu töten, um diese Prämie zu kassieren. Manche dieser Tiere entkamen und verschlimmerten dadurch sogar die Problematik.

Ein Beispiel aus der IT Praxis ist die folgende Anekdote: Ein Konzern wollte von einem der großen Consultingunternehmen ob der Produktivität seiner Programmierer bescheid wissen. Das Unternehmen untersuchte die beiden Teams, von denen eines für die Erstellung und Wartung von Cobol Sourcen verantwortlich war, und das andere entsprechend für den Java Code. Das Ergebnis dieser Untersuchung war: Man sollte auf Cobol Entwicklung umsteigen, weil dieses Team angeblich wesentlich produktiver wäre. Was war hier geschehen? Gemessen wurde die Anzahl an Codezeilen in Relation zu den Stunden, welche die Mitarbeiter zur Programmierung dessen brauchten. Da derselbe Sachverhalt in Java allerdings in wesentlich weniger Codezeilen ausgedrückt werden kann, war man danach irrtümlich der Meinung, dass Cobol Entwicklung produktiver wäre.

Falsche Anreize

Verschärft wird jede Art von Problemen mit Kennzahlen immer, wenn an der Erfüllung eines gewissen Grenzwertes der Erfolg von Mitarbeitern gemessen wird. Ganz besonders dann, wenn damit ein monetärer Anreiz verbunden ist. Überlegen Sie sich also gut, ob sie eine finanzielle Belohnung im Umgang mit Metriken einsetzen möchten.

Mögliche Lösungsansätze

Ich hoffe ich konnte Sie überzeugen, dass beim Einsatz von Metriken zur Vermessung von Software auf jeden Fall Vorsicht geboten ist. Nachdem Sie nun aber genug über Probleme gelesen haben, möchte ich nun zu möglichen Lösungsstrategien zur Thematik kommen. Was empfiehlt sich, um mit Metriken auch tatsächlich erfolgreich zu sein?

Wenig reduzierend

Den Terminus “gesamtheitlich” vermeidend möchte ich empfehlen, Kennzahlen zu wählen, welche bei der Auswahl der Aspekte des Erfolges möglichst wenig einschränkend sind. Also so breit wie möglich die Güte der Arbeit der Mitarbeiter wiedergeben. Dadurch wird vermieden, dass nur ein Aspekt besonders betont wird und somit falsche Prioritäten gesetzt werden. Dies hat wiederum den Nachteil, dass es somit u.U. schwierig wird, die Performance einzelner Teams einzuschätzen. Entschärfen können Sie diese Problematik wiederum durch eine Organisation um die fachlichen Teilbereiche der Problemdomäne herum. Ein Team ist dann genau der Behandlung einer Subdomäne (oder Business Capability) zugeordnet und dafür verantwortlich. Anhand dessen kann der Erfolg prinzipiell recht umfangreich, aber eben nur für einen solchen Teilbereich, gemessen werden. Wesentlich schwieriger is so etwas, wenn Ihre Organisation anhand der Aufgaben bei der Entwicklung von Software strukturiert ist, wie Analyse, Architektur, Entwicklung und Test. In so einem Fall könnten isolierte Kennzahlen sogar die Zusammenarbeit zwischen den Teams behindern. Wenn jedes Team an der Erfüllung der eigenen Kennzahl gemessen wird, dann wird es auch alles danach ausrichten, diese zu optimieren, und zur Not die Schuld, wenn es nicht klappen sollte, bei anderen suchen.

Zusammenfassen

Um nicht reduzierend zu wirken kann man Kennzahlen auch kombinieren. Dafür müssen sie natürlich zuerst in ein einheitliches Format gebracht werden. Für Software bietet es sich an, Verfehlungen bei Metriken in Aufwänden zu messen, welche im Zuge einer Behebung anfallen würden. Wenn es sich bei der Metrik beispielsweise um “Anzahl zyklischer Abhängigkeiten zwischen Modulen” handelt, dann kann diese Umrechnung erfolgen, indem man eine Zeiteinheit (z.B.: 1 Manntag) definiert, welcher nötig wäre, um einen Zyklus im Code zu beheben. Die Aufwände berechnen sich dann wie folgt:

(Anzahl_Zyklen – Obergrenze_Anzahl_Zyklen) * Aufwand_pro_Zyklenbehebung

Wenn Sie das für all Ihre Kennzahlen machen, können Sie die Aufwände am Ende summieren. Das u.a. als Plugin für SonarQube erhältliche SQALE Rating tut etwas ähnliches. Es errechnet zuerst die Kosten der gefundenen Issues im Code (Technical Debt) und erstellt daraus ein Scoring indem es die Kosten für Issues zur Quantität des Codes in Relation setzt. Das Ergebnis geht dann von A (gut) bis F (schlecht) und ist somit sehr plakativ. Etwas problematisch an SQALE finde ich, dass es teilweise Kennzahlen verwendet, welche in meinen Augen “Kobras” sind, wie die Anzahl der dokumentierten public Methoden in Java. Diese sollte man nicht messen, da es dadurch dazu kommen kann, dass veraltete und unverstandene Kommentare von den Entwicklern nicht gelöscht werden. In so einem Fall ist aber kein Kommentar besser als ein schlechter Kommentar. Außerdem ist es möglich (und auch besser) Code auf eine Art und Weise zu schreiben, sodass er im Endeffekt selbsterklärend ist, wodurch es dann keinen expliziten Kommentar mehr braucht.

Gezielte Auswahl für einen bestimmten Anwendungsfall

Es gibt meiner Erfahrung nach durchaus Problemgebiete, deren Erfüllungsgrad durch einige sehr passende Kennzahlen sehr gut wiedergegeben werden. Die Qualität der Strukturen von Software ist so ein Beispiel. Wenn Sie dies messen möchten, so empfehle ich Ihnen die Kennzahl NCCD aus der Familien der Kennzahlen von John Lakos. Eine Alternative stellt die Messung und Kontrolle der Relativen Strukturzyklizität dar, welche auch gut Schritt-für-Schritt gezielt erreicht werden kann, was motivierende Effekte auf ein Team haben kann.

Lockerer Umgang mit dem Thema

Monetäre Anreize führen manchmal zu einer Übermotivation bei der Erfüllung einer bestimmten Metrik. Und das Team vernachlässigt dann andere Aspekte des Erfolgs. Ähnlich verhält es sich, wenn aufgrund einer Untererfüllung eines Kennzahl-Grenzwertes harte Entscheidungen getroffen werden. Wenn in so einem Fall nicht mehr deployt werden kann, dann werden sich die Mitarbeiter Tricks einfallen lassen, um diese Kennzahl zur Not irgendwie noch zu erfüllen. Stattdessen können Sie auf die intrinsische Motivation Ihrer Mitarbeiter setzen. Kommunizieren Sie Ihrem Team bei jeder Gelegenheit wie weit es bei der Erfüllung seiner Ziele bereits ist. Wenn die Mitarbeiter bei der Auswahl der Kennzahlen teilhaben durften, wird die Motivation umso höher sein.

Fitness Functions

Fitness Functions, als Idee aus dem Buch Building Evolutionary Architectures von Neal Ford, Rebecca Parsons und Patrick Kua, basieren nicht selten auf Metriken, gehen aber in ihrer Definition noch darüber hinaus. Es werden dabei entweder punktuell oder laufend, einzelne oder gesamtheitliche Aspekte der Software überwacht. So kann ein einfacher Unit-Test, der auf ArchUnit oder JDepend basiert prüfen, ob die Modulstruktur des Systems auch frei von zyklischen Abhängigkeiten ist. Dies wäre atomar und punktuell. Die Simian Army von Netflix wiederum prüft kontinuierlich und holistisch die Stabilität der Produktionsumgebung. Mehr Informationen dazu im Blog-Beitrag zum Thema Evolutionäre Architektur.

Werkzeuge

Hier noch ein Überblick welche Kennzahl in welchem Tool umgesetzt ist:

Tool Plattformen LCOM4 Relational Cohesion McCabe Visibility Depth-of-Inheritance Component Rank Software Package Metriken John Lakos Metriken Zyklen-Erkennung Architektur Soll/Ist Prüfung
Axivion Bauhaus Suite C/C++, C#, Java + + + + +
Sonargraph C/C++, C#, Java, Python + + + + + + + + + +
JDepend Java + + +
Structure101 Java + + +
STAN Java + + + + +
NDepend / CppDepend / JArchitect C/C++, C#, Java + + + + + +
SDMetrics UML + + +

Fazit

Metriken können ein mächtiges Werkzeug zur Entwicklung und Kontrolle diverser Qualitätsaspekte im Unternehmen sein. Bei der Auswahl und dem Einsatz ist aber Vorsicht geboten. Im Zuge meines Trainings zum Thema Software-Design erlernen die Teilnehmer Schritt für Schritt worauf es bei Messung und Entwicklung effektiver Strukturqualität ankommt.

Nähere Informationen zu diesem Thema finden Sie in meinem Buch Grundlagen des modularen Softwareentwurfs, dessen Kapitel 9 zu diesem Zweck mit freundlicher Genehmigung des Carl Hanser Verlages zur Verfügung gestellt wurde:

Download: Grundlagen des modularen Softwareentwurfs / Kapitel 9

Machine Learning Summit

Interview: Anwendungsmöglichkeiten von Machine Learning

By | Allgemein, Inhaltliches | No Comments
„Anwendungsmöglichkeiten von Machine Learning“ (Interview zum ML-Summit)


Anwendungsmöglichkeiten von Machine Learning
Interview mit Oliver Zeigermann
online im ML-Summit Blog
erschienen am 24. Juli 2018


embarc Vorträge & Workshops auf dem ML-Summit

Oliver Zeigermann, Programm Chair und Trainer auf dem ML Summit, hat im Vorfeld des Events mit dem Entwickler Magazin über die vielfältigen Möglichkeiten zum Einsatz von Machine Learning und Deep Learning gesprochen. In dem Interview zeigt Oliver auf, welche Chancen ML auch für laufende Projekte bietet und womit ein Einstieg in die Arbeit mit Deep Learning möglich ist.

Lesen Sie das ganze Interview und erhalten Sie einen Vorgeschmack auf das große 2-in-1-Trainingsevent vom 1. bis 2. Oktober in Berlin.

zum Interview

MicroMoves_Bauteil 6

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
 

 

 

 

Beitragsbild_Bauteil_5

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

Beitragsbild_Bauteil_4

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 (Docker Commands Cheat Sheet) 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