Yearly Archives

2019

CONCEAL: Verbergen (5C Design, Teil 3)

By | Allgemein, Inhaltliches | No Comments

Verbirg so viel der internen Struktur eines Bausteins und der Art der Umsetzung vor der Außenwelt wie möglich.

  1. Software-Entwurf: Ein Blick zurück und nach vorn
  2. CUT: Richtig schneiden
  3. CONCEAL: Verbergen
  4. CONTRACT: Schnittstelle festlegen
  5. CONNECT: Verbinden
  6. CONSTRUCT: Aufbauen
  7. Its a Wrap: Zusammenfassung

Es war 1972 als David Parnas sein visionäres Papier mit dem Titel “On the Criteria To Be Used in Decomposing Systems into Modules” veröffentlichte. Während Parnas hier viele Aspekte der Modularisierung angesprochen hat, ist es inzwischen hauptsächlich noch für eines bekannt, und zwar für die Formulierung des Information-Hiding Prinzips. Alles, von dem man annahm, dass es sich später ändern könnte, sei vor der Außenwelt zu verbergen. So hat man die Garantie, dass es von außerhalb eines Bausteins niemals Abhängigkeiten dazu geben wird. Dadurch kann dies später jederzeit isoliert geändert werden. Ich unterstreiche das und gehe sogar noch weiter: Verbergen Sie am besten einfach alles, was sich verbergen lässt. Standardmäßig sollte einmal alles zunächst einmal verborgen sein. Nur wenn es außerhalb wirklich benötigt wird, sollte es auch veröffentlicht werden. Schließlich ist es schwierig vorherzusehen, was tatsächlich später einmal geändert werden muß.

Einer der Vorzüge der aktuell so beliebten Microservices ist, dass dabei Interna der einzelnen Module (dort eben Microservices genannt) automatisch hinter einer Remote-Schnittstelle (hier oft REST) verborgen werden. Durch den Verteilungsaspekt eines Service sind das nicht nur die konkreten Komponenten, sondern auch deren Laufzeitumgebung und die jeweils eingesetzte Technologie zur Implementierung.

Die verschiedenen Technologien bringen allerdings selbst bereits Dinge mit, um Subbausteine zu verbergen. In Java ist dies wie folgt möglich, wobei wir auf der niedrigen Abstraktionsebene beginnen und danach “nach außen zoomen”:

  • Innerhalb einer Klasse lassen sich einzelne Member (wie Methoden und Variable) verbergen, beispielsweise mit dem private-Keyword. In Java hat sich eingebürgert Instanzvariable einer Klasse prinzipiell zu verbergen, und wenn, dann nur über sogenannte getter und setter zu veröffentlichen.
  • Klassen lassen sich innerhalb eines Java-Packages verbergen, indem man diese als package-protected definiert. Die Angabe erfolgt dann ohne visibility Modifier in der Klassen-Deklaration.
  • Ab Java 9 können Packages mittels JigSaw (nähere Infos dazu hier) zu Modulen zusammengefasst werden. Dabei kann man gezielt Packages exportieren oder vor der Außenwelt verbergen.

Facade Pattern

Eine Möglichkeit um das Information-Hiding Prinzip umzusetzen stellt das Facade Pattern dar (nähere Infos dazu hier). Bei einer Fassade handelt es sich um einen dezidierten Einstiegspunkt in ein Modul oder Subsystem. Die einzelnen Bestandteile werden gezielt verborgen und jede Interaktion mit ihnen von außen erfolgt über die Fassade. Bei einer REST-API eines (Micro-)Service beispielsweise handelt es sich im Grunde ebenfalls auch immer um eine Fassade.

Law-of-Demeter / Principle of least Knowledge

Beim Law-of-Demeter (zum Ursprung und Namen des Prinzips siehe hier) handelt es sich um einen Spezialfall des Information-Hiding Prinzips. Für den Consumer einer Schnittstelle soll das Zusammenspiel des Providers mit anderen Komponenten möglichst verborgen sein. Der Consumer vemeidet dadurch Abhängigkeiten zu den vom Provider verwendeten weiteren Komponenten. Im folgenden Listing muss der Fahrer wissen, aus welchen einzelnen Bauteilen das Auto besteht, um es zu verwenden. Für die Hersteller des Autos hat dieses Design den Nachteil, bei Änderungen die Fahrer informieren zu müssen, damit diese sich darauf einstellen können.


Car wartburg = new Car();
wartburg.getSeatbelt().fasten();
wartburg.getEngine().start();

Im folgenden Listing sind diese Probleme behoben. Nun kann ich den Verbrennungsmotor durch Elektromotoren auswechseln, die an den Radnaben montiert sind, und außerdem den Start des Autos verweigern, sollte sich der Fahrer nicht angeschnallt haben. Den Fahrer muss ich über diese Änderungen gar nicht informieren.


Car tesla = new Car();
tesla.secureDriver();
tesla.activate();

Wenn Sie konsequent Dinge verbergen stellt sich die Frage, wie den möglichen Consumern der Zugang zur Funktionalität des eigenen Bausteins gewährt werden soll. Hier kommen dann Schnittstellen ins Spiel, was uns auch zu unserem nächsten Beitrag dieser Serie bringt, nämlich: “CONTRACT: Schnittstelle festlegen“. (Coming soon, stay tuned…)

Einheitlicher UI-Rahmen mit PHP und Server Side Includes (Micro Moves, Bauteil 8)

By | Inhaltliches | No Comments
Blog-Serie Micro Moves -- Logo

Bereits einer früheren Folge dieser Blog-Serie  hatten wir unterschiedliche Optionen für die UI-Frage rund um Microservices diskutiert, und auch zwei Extreme dargestellt. Wir haben uns dann für einen Weg entschieden, und dabei Nachteile in Kauf genommen. In dieser Folge zeige ich einen alternativen Ansatz, der diese Mankos vermeidet. Wie jeder Kompromiss hat aber auch dieser seinen Preis.

Er ist es wert, wenn wir beispielsweise beim Hinzufügen einer neuen fachlichen Funktionalität, die sich im Hauptmenü widerspiegelt, nicht viele bestehende Module anfassen wollen.

Um was geht es — ein Überblick

Diese Folge liefert einen einheitlichen Rahmen für das webbasierte Frontend, das bisher auf mehrere Bauteile (games, players, play, …) verteilt ist. Im Grunde bleibt es das auch. Wir ziehen lediglich wiederkehrende Teile heraus. So erhöhen wir die Wartbarkeit und reduzieren redundant implementierte Funktionalität.

Überblick, Bauteil 8

Die gemeinsamen Fragmente des UI-Rahmens liefert ein neues Modul homepage, das mit PHP 7 realisiert ist und in einem Apache httpd läuft. Die rote (8) markiert den Standpunkt des Bauteils im Gesamtbild („Sie befinden sich hier.“). Die Einbindung der Fragmente des UI-Rahmens erfolgt mit Server Side Includes (SSI) in unserem Reverse Proxy nginx, den wir in Folge 4 eingeführt hatten.

UI-Optionen und ein Kompromiss

Die UI-Frage im Zusammenhang mit Microservices: Trotz mehrerer Teile soll sich die Anwendung dem Benutzer “aus einem Guss“ präsentieren. Wie realisieren wir mit mehreren Teilen ein UI?

In Folge 3 dieser Serie haben wir zwei extreme Antworten für diese Frage beschrieben: Jeder Microservices hat sein eigenes UI bzw. es gibt einen gemeinsamen Client für alle. In der Abbildung unten seht Ihr sie als Optionen (1) bzw. (3). Dazwischen eine neue Option (2) als Kompromiss — die Heldin dieser Folge.

Die folgende Tabelle beschreibt die einzelnen Optionen und nennt Beispiele für die technische Umsetzung.

OptionBeschreibungUmsetzung
(1) Jeweils eigenes UIJeder Microservice bringt sein komplett eigenes UI mit. Die Integration zwischen den UIs erfolgt innerhalb des Browsers über Links.z.B. klassische HTML-basierte Web-Applikationen à la amazon.de, Web- MVC-Framework (Request/Response) garniert mit JavaScript (etwa Spring Web MVC oder PHP Micro Framework)
(2) Plugin-AnsatzMicroservices integrieren sich über eigene UI-Anteile in ein übergeordnetes UI. Im einfachsten Fall enthält dieses nur die Hauptnavigation.z.B. Desktop-Applikation à la Spotify, Portalserver, anderweitig server- oder client-seitig eingebettete HTML-Fragmente (etwa mit Server Side Includes oder als Single Page Application z. B. mit AngularJS).
(3) Full ClientEin gemeinsames UI nutzt alle Microservices über deren Schnittstellen. Diese sind selbst UI-los und enthalten nur Geschäftslogik.z.B. Mobile-App à la YouTube für Smartphone und Tablet, nativ entwickelt für Zielsysteme wie iOS, Android etc. oder hybrid erstellt, etwa mit PhoneGap

Jeweils eigenes UI

Bisher in dieser Serie haben wir im Wesentlichen Variante (1) verfolgt. So ist games mit Java und Spring Web MVC realisiert, players mit Python und Flask. Beide liefern jeweils den gesamten Inhalt ihrer Seiten. Für den Benutzer wirkt es durch den gemeinsamen Nenner Bootstrap wie aus einem Guss. Ein Wechsel zwischen games und players wirkt nicht wie ein Bruch, weil die Oberflächen gleich gebaut sind.

Die Vorteile von Ansatz (1) kommen besonders dort zum Tragen, wo Teams für Vertikalen verantwortlich sind und unabhängig arbeiten wollen:

  • Das Team hat große technologische Freiheiten im UI
  • Funktionalität ist vom Team vollständig (inkl. UI) lieferbar, unabhängig von anderen Teams

Full Client

Full Client so

Das Modul play unserer Serie deutet dagegen an, wie Ansatz (3) funktioniert. Bei play handelt es sich um eine Single Page Application (SPA) in Vue.js. Sie greift auf andere Services (chess-diagrams, games via REST) zu.

Die Vorteile dieses Ansatzes liegen in der potentiell besseren (bzw. leichter erreichbaren) User Experience:

  • Inhalte und Funktionen aus verschiedenen Themen lassen sich nahtlos auf einer Seite integrieren – es gibt keine Brüche
  • ein einheitliches User Interface ist leicht erreichbar
  • ein spezialisiertes Team für optimales UX denkbar

In unserer Serie hier führte die Wahl von (1) dazu, dass wir Teile der Oberfläche, etwa die Navigation, mehrmals implementiert haben. Das JWT-Token aus Folge 7 haben wir gleich drei mal ausgelesen (1x in Java in games, 1x in JavaScript in play, 1x in Python in players). Wie können wir diesen redundanten Code vermeiden, und trotzdem die Module unabhängig voneinander inkl. Oberfläche entwickeln und deployen?

Transklusion als weiterer Lösungsansatz

Eine verbreitete Idee für Weboberflächen nach Option (2) bezieht die Inhalte der Seite(n) von unterschiedlichen Stellen (bei uns Modulen wie players oder games) und fügt sie an einer Seite zusammen. Als Buzzword fällt hier mitunter „Transklusion“, lt. Wikipedia „die Übernahme von einem elektronischen Dokument oder Teilen davon in ein oder mehrere andere Dokumente …“. Auch in der Angular-Szene ist dieser Begriff gebräuchlich.

Auf diese Weise ist es möglich, wiederkehrende Schnipsel der Oberfläche wie zum Beispiel die Navigation von zentraler Stelle zu beziehen und immer wieder einzubinden. Im Ursprung des Begriffes ist von Dokumenten die Rede, es kann sich aber auch um dynamische Teilinhalte handeln. Dann wird es interessant.

Die Einbinde-Idee kennt verschiedene Möglichkeiten zur Umsetzung. Sie unterscheiden sich vor allem durch das Programm, das die Gesamtseite zusammenbaut. Ort des Geschehens kann sowohl der Client (also der Web-Browser), als auch das Backend (ein Server) sein.

ClientBackend
Im Browser laden geeignete JavaScript-Routinen Teile (HTML-Fragemente) dynamisch nach und fügen sie in das Dokument ein. Das Einbinden der Schachbrett-Grafik aus chess-diagrams in play durch ein img-Tag fällt auch in diese Kategorie und ist in der Umsetzung noch schlanker.Auf der Server-Seite kommen Edge-Server, CDNs (Content Delivery Networks) oder auch Proxy-Server oder der Web-Server selbst in Frage. Standardisierte Technologien in diesem Umfeld: SSI (Server Side Includes) und ESI (Edge Side Includes). Darüber hinaus gibt es darauf spezialisierte Bibliotheken und Framework-Lösungen wie Tailor von Zalando.

Ich zeige im Folgenden eine Lösung mit SSI und PHP. Anschließend diskutiere ich Stolpersteine und Konsequenzen dieses Ansatzes.

Eine Umsetzung mit PHP-Schnipseln

In unserer Schachplattform illustrieren wir die Technik mit drei wiederkehrende Schnipseln:

  • Den Header, inkl. den JavaScript- und CSS-Ressourcen
  • Das Menu (Navigationsleiste), inkl. der Kennzeichnung des aktiven Moduls
  • Den Footer, inkl. Link zur About-Seite des Moduls

Dabei sind der zweite und dritte Schnipsel mit (überschaubarer) Dynamik ausgestattet: In der Navigation ist das aktive Modul in der Menu-Zeile hervorgehoben. In Abhängigkeit vom Anmeldestatus des Benutzers ändern sich die Links im rechten Teil des Menüs (Anmelden / Registrieren vs. Profil /Abmelden). Hier ein Beispiel:

Aktives Modul: games, Benutzer Peter Pan ist angemeldet.
Aktuelles (aktives) Modul: games, Benutzer Peter Pan ist angemeldet.

Beim Footer-Schnipsel ist die Jahreszahl des Copyrights dynamisch und der Link auf die About-Seite des Moduls abhängig vom aktiven Modul. Das folgende Bild zeigt die Platzierung der Navigationsleiste navbar und der Fußzeile footer in der Oberfläche.

Sichtbare Schnipsel in der Weboberfläche
PHP 7 Logo

Umgesetzt sind die Schnipsel mit einem neuen Modul: homepage, Quelltext wie üblich auf GitHub. Es ist in PHP 7 geschrieben und läuft in einem Apache HTTP Server. PHP ist laut Selbstauskunft „eine beliebte, universell einsetzbare Skriptsprache, die sich besonders für die Webentwicklung eignet.“ Als Nebeneffekt haben wir mit PHP eine weitere Programmiersprache im System — polyglott war ja ein Ziel dieser Serie.

Das homepage-Modul besteht aus einer Handvoll HTML-Seiten, teilweise mit eingebettetem PHP. Die Tabelle unten listet sie auf.

SeiteBeschreibung
header.phpKopf einer Seite mit head– und title-Tag und CSS für Bootstrap.
navbar.phpNavigationsleiste. Der Request-Parameter active setzt den hervorgehobenen Menüeintrag. Mögliche Werte bisher: „games“, „players“.
footer.phpFuß der Seite mit Links, enthält auch die JavaScript-Bibliotheken für jQuery und Bootstrap. Das Copyright-Jahr setzt es via PHP automatisch, der Parameter module beeinflußt den Link auf die About-Seite. Beispiel: „players“ -> /players/about.html
index.htmlHomepage der gesamten Anwendung. Statischer Inhalt, die drei Schnipsel unten bindet es ein.
about.htmlÜber das Modul homepage. Statischer Inhalt, die drei Schnipsel unten bindet es ein.

Wie bei den anderen Modulen gibt es auch bei homepage ein Dockerfile. Die Einbindung in das Gesamtsystem erfolgt in der Datei nginx.conf für den Reverse Proxy sowie in docker-compose.yml für Docker Compose.

Server Side Includes

Server Side Includes (kurz SSI) sind eine ziemlich archaische Technologie für Web-Server. Ein solcher führt, wenn er SSI unterstützt, die entsprechenden Skript-Anweisungen innerhalb eines Dokumentes aus, bevor er es an den HTTP-Client (den Browser) ausliefert. Zuvor ersetzt er die SSI-Anweisungen durch deren Ergebnis.

Die für uns interessante SSI-Anweisung ist include — mit ihr bindet ein Server (oft wiederkehrende) Dokumentteile ein, und reduziert so Redundanz und Wartungsaufwand. Hier ein Beispiel für das Einbinden einer Datei:

<!--#include file="footer.html" -->

Alternativ zu statischen Dateien können auch dynamische Inhalte eingebunden werden. Im folgenden Fall führt der Server das PHP-Skript aus und inkludiert das Ergebnis.

<!--#include virtual="navbar.php" -->

Anwendung in FLEXess

In FLEXess habe ich die wiederkehrenden Elemente (Header, Footer, Navigation) aus den HTML-Seiten, welche die Module players, games etc. liefern, gelöscht und durch SSI-Anweisungen ersetzt. Bevor unser Reverse Proxy sie an den Browser ausliefert, führt er sie aus und fügt so die Inhalte aus den Modul homepage ein. Die SSI-Konfiguration in nginx ist dabei einfach. Hier ein Ausschnitt aus der Datei nginx.conf im FLEXess-Modul reverse-proxy für das Modul players :

...
location / {
    ssi on;
    proxy_pass http://homepage:80/;
}
...
location /players/ {
    ssi on;
    proxy_pass http://players:8000/;
}
...

Die erste Anweisung lenkt Anfragen von der Document-Root auf das neue homepage-Modul. Dieses liefert von nun an die Startseite (/index.html) und das Favicon (/favicon.ico). Vor allem aber die zentralen Fragmente für Header, Navigation und Footer. Sie landen via SSI auch in die statischen Seiten von homepage selbst (deswegen ist SSI auch dort aktiv geschaltet). Wichtig aber vor allem die Anweisung „ssi on;“ in players. Auf diese Weise scant nginx in HTML-Seiten von dort nach SSI-Anweisungen und führt sie aus. Details zur SSI-Konfiguration entnehmt Ihr der Dokumentation von nginx.

In players ist der Einbau der Fragmente sehr einfach. Die verwendete Template-Engine unterstützt Basisseiten. Von einer solchen sind alle anderen Seiten abgeleitet. Hier die zentrale Basisseite für players mit den SSI-Anweisungen.

<!DOCTYPE HTML>
<html>
<!--# include virtual="/header" -->
<body>

    <!--# include virtual="/navbar?active=players" -->

    <div id="header" class="container">
        <div class="page-header">
        {% block header %}
        {% endblock %}
        </div>
    </div>

    <div id="messages" class="container">
    {% with messages = get_flashed_messages(with_categories=true) %}
      {% if messages %}
        {% for category, message in messages %}
          <div class="alert alert-{{category}}">{{ message }}</div>
        {% endfor %}
      {% endif %}
    {% endwith %}
    </div>


    <div id="content" class="container">
        {% block content %}
        {% endblock %}
    </div>

    <!--# include virtual="/footer?module=players" -->

</body>
</html>

Feinheiten

Noch zwei Details zur Implementierung: Zum einen müssen wir nicht footer.php o.ä. schreiben, es reicht footer. Mir war wichtig, dass die verschiedenen Module nicht wissen, dass die Homepage in PHP geschrieben ist. Schlimm genug dass sie wissen müssen, dass die Einbindung mit SSI erfolgt. Erreicht habe ich das durch eine Konfiguration des Apache httpd. Details dazu lest Ihr in diesem Beitrag von Alex Cican: „How to remove .php, .html, .htm extensions with .htaccess“.

Zum zweiten: In den Verwendungen in der Seite aus players oben tauchen Aufrufparameter auf. Beispielsweise active für den aktiven Menupunkt. Da das Fragment aus homepage nicht weiß, von wo es eingebunden wurde, übergeben wir diese Information. Alternativ zu den Parametern könnten wir auch versuchen die URL auszuwerten. Im Falle des angemeldeten Benutzers arbeiten wir nochmal anders. Nämlich mit dem JWT-Cookie aus Folge 7, das sich in PHP auch prima auslesen lässt.

ESI statt SSI?

Neben SSI zum Einbinden der Schnipsel wäre ESI (Edge Side Includes) ein gangbarer und vergleichbarer Weg. Der ESI-Standard ist neuer und umfangreicher als SSI. Auch dort gibt es ein include. Die Syntax der Anweisungen ist etwas moderner: XML statt HTML-Kommentare. Die Wahl hier fiel vorrangig deshalb auf SSI, weil unser Reverse Proxy nginx es unterstützt, ESI hingegen nicht. Eine Alternative zu nginx wäre hier Apache Traffic Server, der könnte auch ESI.

Stolperfallen

Die Sache mir SSI funktioniert in unserem Fall hier ganz prima. Es gibt dennoch Stolperfallen, die ich Euch nicht vorenthalten möchte.

Stolperfalle

Zum einen: Der Reverse Proxy (bei uns nginx) führt nur SSI-Anweisungen in Dokumenten aus, die er „sieht“. Konkret muss dafür SSI für das betreffende eingebundene Modul aktiv sein („ssi on;“). Zum anderen (jetzt kommt die Falle) darf der Content nicht komprimiert sein.

Apache httpd beispielsweise zippt Inhalte standardmäßig, bevor er sie zum Client sendet. Sie landen dann im Browser und werden dort entpackt, inklusive der nicht ausgeführten SSO-Anweisungen.

Entweder, Ihr gewöhnt dem Webserver in der Konfiguration das Zippen ab, wenn nginx die Inhalte per SSI anreichern soll. Oder Ihr konfiguriert nginx umgekehrt durch Header-Anpassung so, dass er komprimierte Inhalte nicht akzeptiert. Apache sendet sie dann artig unkomprimiert. Ich habe das auf dem zweiten Weg gelöst. Eine kurze Beschreibung dazu findet sich zum Beispiel bei Stackoverflow („With nginx, how do I run SSI on a page returned from another server?“).

Weiterleitungsziele ermitteln (Redirect)

Ein weiteres Problemhat zwar nicht direkt mit SSI zu tun. Aber es ist bei der Umstellung in FLEXess auf SSI zu Tage getreten. Module möchten mitunter ein HTTP Redirect (Status Code 3xx) senden.

In unserem Fall war das bei players nach dem An- und Abmelden eines Benutzers erforderlich. Der Grund: In diesen beiden Fällen setzt players entweder ein Cookie (beim Anmelden) oder entfernt es (beim Abmelden). Anschließend stellt players eine Seite dar, in welcher der Anmeldestatus korrekt in der Navigation angezeigt sein muss. Problem: Die Homepage ermittelt den Anmeldestatus aus dem JWT-Token des aktuelle Requests — die Änderung des Cookies ist erst mit dem nächsten Request aktiv.

Früher konnte players das intern klären. Jetzt ist die einfachste Lösung ein Redirect. Der Browser sendet dann einen neuen Request, bei dem der Cookie mit dem JWT-Token nach Anmelden enthalten ist, und nach Abmelden auch schon gelöscht.

Problem: players hängt unterhalb des Reverse Proxies. Für das Redirect-Ziel benötigt es die Original-URL. Konkret: Schema (http vs. https), Servername, Port, URL … Die einfachste Lösung hier war es im nginx die Konfiguration so zu ändern, dass dieser die Daten in HTTP-Headern an players weiterleitet („proxy_set_header“). Eine Beschreibung dazu liefert ein schöner Beitrag bei Digital Ocean: „Understanding Nginx HTTP Proxying, Load Balancing, Buffering, and Caching“.

Diskussion des Ansatzes

Positive Konsequenzen

Mit dem beschriebenen Ansatz lassen sich gleich drei Module in FLEXess von Code für redundante Funktionalität im Frontend befreien (games, players und play). Bei play ist es zudem nicht mehr erforderlich, dass es das JWT-Token im Browser per JavaScript auslesen kann. Das lässt sich nun verbieten („httpOnly“) und damit eine Sicherheitslücke schließen (siehe XSS-Angriff).

Chat

Der größte Vorteil ist sicher, wenn ein weiteres Modul hinzukommt, dass den Rahmen der Weboberfläche nutzen und sich in der Navigation einnisten möchte. Es ist nun erforderlich nur zwei Module anzupassen und neu zu deployen: das neue Modul selbst und das angepasste homepage-Modul. Vorher hättet Ihr alle Module anpassen müssen, in denen die Navigation auftaucht.

Änderungen im zentralen Design oder eine Aktualisierung der Bootstrap-Version sind nun auch an zentraler Stelle möglich. Ein einheitlichen Aussehen lässt sich ebenfalls leicht realisieren.

Und der Preis dafür

Doch es gibt auch Nachteile. Einzelne Module können nun eben keine unterschiedlichen Bibliotheksversionen mehr im Frontend benutzen, und verlieren an Freiheit. Die Anwendung lässt sich nicht mehr schrittweise umstellen (falls gewünscht). Falls unterschiedliche Teams für die Module verantwortlich sind entstehen Abhängigkeiten zwischen diesen.

Zum Testen eines Moduls selbst ist zumindest für die Weboberfläche nun eine weitere Komponente erforderlich, die sich um SSI kümmert. Ansonsten fehlt die Navigation und das Ganze ist sehr trist. Dazu benötigt das zuständige Team nicht das ganze Modul-Arsenal. Es reicht ein nginx, der statische Inhalte einfügt, wenn der Test die Dynamik in der Navigation und im Footer nicht erfordert.

Muss es zentral sein?

Tatsächlich könnte ein Team auf den zentralen SSI-Ansatz für seine Module auch verzichten. Es liefert die betreffenden Schnipsel selbst und erhält sich so seine Freiheit. Das homepage-Modul würde dann als Vorschlag oder Angebot angesehen, nicht als Teil der Makro-Architektur. Auf dieser Weise bleibt Teams die Möglichkeit, neue Dinge einfach auszuprobieren in diesem Bereich. Auf Kosten des Mehraufwandes (für das Team). Und mitunter auch auf Kosten der Konsistenz der Oberfläche (für die Gesamtanwendung).

Wie es weiter geht …

Ein Blick auf den Bauplan verrät: Mittlerweile haben wir fast alle Teile beisammen. Was noch fehlt ist der mobile Client. Weiterhin möchte ich ein paar querschnittliche Themen rund um den Betrieb diskutieren (Deployment, Monitoring, Tracing, Logging …). Bleibt also spannend.

Fragen und Anregungen sind natürlich jederzeit willkommen. Per Kommentar hier im Blog oder gerne auch per Maildirekt an mich …

CUT: Richtig schneiden (5C-Design, Teil 2)

By | Allgemein, Inhaltliches | No Comments

Je unabhängiger ein Baustein ist, desto einfacher wird er zu handhaben sein. Schneide Bausteine so, dass sie sich möglichst gut voneinander abgrenzen.

  1. Software-Entwurf: Ein Blick zurück und nach vorn
  2. CUT: Richtig schneiden
  3. CONCEAL: Verbergen
  4. CONTRACT: Schnittstelle festlegen
  5. CONNECT: Verbinden
  6. CONSTRUCT: Aufbauen
  7. It´s a Wrap: Zusammenfassung

Um Komplexität dauerhaft beherrschbar zu machen, gibt es kaum eine Alternative dazu, diese in einzelne kleinere Teile zu zerlegen. Dafür muss man sich zunächst einmal die Frage stellen, an welchen Grenzen diese Abgrenzung denn am besten funktioniert. Wie findet man diese? Im Endeffekt wird der Programmcode der Lösung ein Abbild der fachlichen Anforderungen sein. Die möglichen Grenzen müssten demnach bereits in den Anforderungen auffindbar sein. Warum eine solche “fachliche Grenzziehung” (auch Vertikalität genannt) zu mehr Eigenständigkeit der Bausteine führt möchte ich anhand eines Beispiels verdeutlichen.

Schichten vs. Vertikale Architektur

Abbildung 1 zeigt ein nach klassischer Manier in technische Strukturen zerlegtes System. Daraus ergeben sich die dort abgebildeten Schichten, manchmal auch Layer genannt. Damit werden vorrangig die technischen Aspekte der Lösung betont. Dies hat den Vorteil, dass Entwickler passend zu ihren Stärken Strukturen im Code wiederfinden. Es werden außerdem querschnittliche Themen wie Persistenz gleichartig behandelt, was meist auch gewünscht ist. Auch das User-Interface wird für den Benutzer hier wohl ein durchgängig flüssiges Erlebnis darstellen. Aber: Sobald der Wunsch auftaucht, in irgendeinem der bestehenden Features ein weiteres Eingabe-Feld hinzuzufügen, zieht dies eine Kaskade an Änderungen in allen Layern nach sich. Man beginnt in der DB, zieht dies dann im DB-Access-Layer nach, danach in der Service-Schicht und zum Schluss im UI. Auch wird ein Ausfall einer niedrigeren Schicht sich immer auch auf alle Schichten darüber auswirken. Solch technische Strukturen sind demnach nicht wirklich eigenständig.

Abb. 1: Schichtenarchitektur

Die Alternative zu technischen Strukturen sind fachliche bzw. vertikale Strukturen (siehe Abb. 2). Hier wird besonders die Fachlichkeit betont. Das heißt übrigens nicht, dass man bei näherem Blick in eine dieser fachlichen Strukturen nicht wieder so etwas wie Layer finden wird. Trotzdem: Der Aspekt, der der obersten Abstraktionsebene Form gibt, wird immer etwas stärker betont werden als der sekundäre Aspekt innerhalb dieser primären Struktur.

Abb. 2: Vertikale / Fachliche Architektur

Die Grenzen der fachlichen Zerlegung

Tatsache ist allerdings, dass vertikale Abtrennung in manchen Fällen etwas besser funktionieren wird, und in anderen Fällen wiederum nicht so gut. Die Anforderungen eignen sich nämlich nicht immer in gleichem Maß für eine solche Aufteilung. Manchmal sind bereits die Business-Regeln, die der Fachexperte formuliert, so dermaßen miteinander vernetzt, dass eine effiziente Zerlegung schwierig wird. Werfen Sie dazu bitte einen Blick auf die Architektur, die in Abb. 3 dargestellt ist. Es handelt sich dabei um einen Web-Shop, welcher nach allen aktuellen Regeln der Kunst in fachliche Microservices zerlegt wurde, während die User-Interfaces nach wie vor als monolithische Layer implementiert sind.

Zunächst ist bemerkenswert, dass man ein solch monolithische Design des UI in Microservice-Architekturen häufig antrifft. Hier möchte ich kurz innehalten und die Frage stellen, warum dem eigentlich so ist. Warum klappt hier die fachliche Zerlegung oft nicht so gut wie im Backend? Die Ursache ist bei einer nicht-funktionalen (oder qualitativen) Anforderung zu finden. Wenn ich dem User ein durchgängiges Erleben des UIs ermöglichen möchte, so ist ein vertikaler Architekturstil an dieser Stelle nicht besonders gut geeignet. Daran sieht man, dass qualitative Anforderungen ebenfalls Einfluss auf die optimal mögliche Zerlegung eines Systems haben können.

Im Backend wiederum scheint die vertikale Modularisierung prima geklappt zu haben. Im Service “Kundenkonto” werden die Daten gespeichert, die ich vom Kunden benötige um ihn in der Plattform zu registrieren. Dabei wird die Validität seiner Angaben entweder über eine EMail oder eine SMS geprüft. Aus diesem Grund haben wir das Thema “Kommunikation” hier mit rein gepackt. Weiters gibt es Services für die “Bestellung”, das “Lager”, die “Bezahlung” und die “Lieferung”. Querschnittliche fachliche Themen wie die Daten des Kunden (Zahlungsweise, Lieferadresse, Rechnungsadresse etc.) haben wir ebenfalls auf diese Services aufgeteilt wie das Produkt an sich (Preis, Lagerstand, Beschreibung etc.).

Das Ziel war Logik und Daten jeweils nahe beieinander zu haben um eine Architektur mit hoher Kohäsion und niedriger Kopplung zu erreichen. Dies ist hier auch ganz gut gelungen, wie ich finde. Würde mir das jemand als Architektur zum Review vorlegen mit der Fragestellubg, ob dies gängigen Best-Practices folgt, hätte ich zumindest auf den ersten Blick nichts zu beanstanden.

Abb. 3: Die einwandfreie Microservice-Architektur unseres Web-Shops

Eine neue Anforderung

Ab sofort soll es in gewissen Fällen möglich sein, frische Schnittblumen bei jeder Bestellung mit auszuliefern. Dies kann entweder über Bezahlung möglich sein, oder ab jeweils einem Umsatz von 1000 Euro einmalig als Gratis-Angebot. Dies ist allerdings nur möglich bei Lieferdiensten, die dabei auch mitmachen. Diese müssen nämlich die Blumen immer frisch bei örtlichen Blumenhändlern abholen. Außerdem ist dies nur möglich, wenn es in der jeweiligen Gegend auch einen Blumenhändler gibt, der mit uns kooperiert. Angeboten wird dies außerdem nur weiblichen Kunden. Für männliche Kunden wird überlegt evtl. später ein After-Shave als Bonus den Paketen beizulegen. Allerdings nur solange auch welche auf Lager sind. In welchem der Bausteine würden Sie diese Änderung verorten? Es gäbe dafür diverse Strategien:

  • Man schreibt einen neuen Service “Geschenke”, der genau dieses Feature abbildet.
  • Man gibt das neue Feature irgendwo dazu, z.B. bei Payment, Bestellung oder Lieferung.
  • Man extrahiert aus den bestehenden Services einen neuen namens “Produkt”, der Daten und Logik der Produkte beinhaltet und dabei die Geschenke als Sonderfall behandelt.

Jede dieser Lösungen führt dazu, dass sich die Kopplung zwischen den Services im Vergleich zur Situation davor vergrößert. Dies allerdings nicht, weil keine gute Arbeit geleistet wurde, sondern weil dies die Anforderungen selbst nötig machten.

Fazit

Es gibt nicht nur qualitative Anforderungen, die eigentlich immer querschnittlicher Natur sind. Es gibt ebenfalls funktionale Anforderungen, welche auf gewisse Weise übergreifend sind. Sie vernetzen bestehende Anforderungen und sind nicht so gut als Modul isolierbar. Sie führen dazu, dass sich die Software in Summe nicht so gut modularisieren lässt. Die Aufgabe ist, eine Struktur zu finden, welche möglichst gut zu den fachlichen Anforderungen passt. Außerdem haben Sie idealerweise noch die Möglichkeit, auf Änderungen der Fachlichkeit zu reagieren, indem Sie im Zuge eines Refactorings eine Restrukturierung vornehmen. Abhängig von der Komplexität der Anforderungen selbst wird Ihnen das manchmal besser gelingen, und manchmal sind dem gewisse Grenzen gesetzt. Wenn Ihnen das gelungen ist, geht es weiter mit dem nächsten der 5 C´s, nämlich: “CONCEAL: Verbergen” (Coming soon, stay tuned…).

Software-Entwurf: Ein Blick zurück und nach vorn (5C-Design, Teil 1)

By | Allgemein, Inhaltliches | No Comments

Als in den 80ern und 90ern des vorigen Jahrhunderts die Objektorientierte Programmierung angetreten ist alle Probleme der Entwicklung komplexer Software zu lösen, fasste Robert C. Martin einige Prinzipien des Software-Entwurfs unter dem Kürzel (und Schlagwort) SOLID zusammen. Diese Sammlung an Prinzipien ist auch heute noch populär, obwohl sich seither einiges getan hat. Anfangs der 2000er wurde dann die “SOA-Sau” durchs Dorf getrieben, und heute muss man sich fast schon schämen, wenn man keine “Microservices” macht. Und das, obwohl der Terminus “Microservices” alles andere als exakt definiert ist und eigentlich einige unterschiedliche Architekturstile umfasst. Die SOLID Prinzipien gab es als eine weithin anerkannte Grundlage des Software-Entwurfs dabei die ganze Zeit. Leider waren diese aber keineswegs hilfreich dabei die SOA zu widerlegen, noch kann man sie als Grundlage für gute Microservice Architekturen zu Rate ziehen.

  1. Software-Entwurf: Ein Blick zurück und nach vorn
  2. CUT: Richtig schneiden
  3. CONCEAL: Verbergen
  4. CONTRACT: Schnittstelle festlegen
  5. CONNECT: Verbinden
  6. CONSTRUCT: Aufbauen
  7. It´s a Wrap: Zusammenfassung

Über SOLID

Es war 1999 als Robert C. Martin auf der Basis seiner eigenen Arbeit, und der Arbeit einiger anderer wie Bertrand Meyer und Barbara Liskov, seine Top 5 Prinzipien des objektorientierten Entwurfs festgelegt hat. Später erst fiel es einem gewissen Michael Feathers auf, dass man diese prima unter dem Akronym SOLID zusammenfassen kann. Dass diese Abkürzung der Popularität geholfen hat merkt man alleine schon daran, dass die anderen Prinzipien, die er gleichzeitig für Kohäsion und die Kopplung von Packages festgelegt hat, heute kaum noch jemand kennt. Bei SOLID handelt es sich im Detail um die folgenden 5 Prinzipien:

  • Single-Responsibility Principle: Eine Klasse sollte einen, und nur (!) einen Änderungsgrund haben
  • Open-Closed Principle: Sie sollten in der Lage sein, das Verhalten einer Klasse zu erweitern, ohne diese im Zuge dessen ändern zu müssen
  • Liskov´s-Substitutionsprinzip: Basisklassen müssen durch ihre abgeleiteten Klassen ersetzbar sein
  • Interface-Segregation Principle: Entwerfe kleine Schnittstellen, jeweils passend für einen Anwendungsfall der Benützung der Klasse
  • Dependency-Inversion Principle: Sei immer von Abstraktionen abhängig, und niemals von konkreten Implementierungen

Wie man sieht ging man damals davon aus, dass Software-Entwurf dem Entwurf von Klassen entspricht. Darauf lag auch der Fokus von SOLID. Objektorientierung war damals das, was heute Microservices sind. Sie galten als Patentrezept zur Lösung so ziemlich aller Probleme der Software-Architektur. Unnötig zu erwähnen, dass man das heute anders sieht. Die OOP gilt als überschätzt, und auch wenn man ab und zu auf Vererbung setzt, so gilt doch der Grundsatz: Composition-over-Inheritance.

Dependency Inversion

Wenn auch tatsächlich keiner der 5 Punkte in SOLID falsch ist, so möchte ich doch zumindest die damals gültige Interpretation des Dependency-Inversion Prinzips hinterfragen. Robert C. Martin war der Meinung, dass eine Klasse A, welche die Schnittstelle einer anderen Klasse B benützt nicht direkt von Klasse B abhängig sein sollte. Niemals. Stattdessen sollte A eine Abstraktion der benötigten Schnittstelle festlegen. B implementiert dann diese Schnittstelle. Damit wäre die Abhängigkeit umgekehrt (siehe u.a. Abbildung), wenn man die neu hinzugefügte Schnittstelle als Teil von A sieht. Das hätte den positiven Effekt, dass später die Implementierung der Schnittstelle ausgewechselt werden kann. B kann also durch eine andere Klasse C ersetzt werden, wenn diese dieselbe Schnittstelle implementiert. Heute sieht man es keineswegs so, dass dies immer zu geschehen habe, sondern eher, dass dies in Ausnahmefällen Sinn macht. Eine abstrakte Schnittstellendefinition sollte man nur festlegen, wenn:

  • … es mehr als eine Implementierung davon gibt.
  • … es absehbar ist, dass es in naher Zukunft mehr als eine Implementierung davon geben wird.
  • … die Schnittstelle als möglicher Erweiterungspunkt dienen soll. Dies ist oft bei der Entwicklung von Frameworks der Fall.
Abhängigkeitsumkehr, dem D in SOLID entsprechend

Das genügt hoffentlich als Beleg dafür, dass SOLID keinesfalls als allumfassende Grundlage für zeitgemäßen Software-Entwurf gesehen werden kann. Wenn wir Software in ihre Einzelteile zerlegen, und uns dabei an Best-Practices orientieren möchten, dann benötigen wir etwas anderes, moderneres. Bevor wir so etwas entwickeln, schärfen wir aber noch die Zielsetzung. Schließlich gibt es ja moderne Prinzipien-Sammlungen, wie die der 12-Factor-App. Diese hat als konkrete Zielsetzung, dass sich die damit entwickelten Applikationen zum “Deployment auf modernen Cloud-Plattformen eignen, die Notwendigkeit von Servern und Serveradministration vermeiden”. Ein adäquater Ersatz für Robert C. Martins Prinzipiensammlung sollte sich aber eher um Wartbarkeit von Code drehen.

Die IT in der Legacy Krise

Dass Wartbarkeit ein großes Thema ist merkt man alleine schon an der Zahl der komplexen Softwaresysteme, die inzwischen als “Legacy” bezeichnet werden. Meist meint man damit einen gewissen Kontrollverlust, der sich in Instabilität und steigenden Kosten bei den laufenden Updates (der Wartung) am System zeigt. Um hier für Abhilfe zu sorgen werden wir zunächst eine Blick auf mögliche Ursachen werfen. Sobald das Team die Software nicht mehr versteht besteht die Gefahr des Kontrollverlustes. Das Verhalten bei Änderungen an der Software wird dadurch in hohem Ausmaß nicht-deterministisch und es kommt zu unerwünschten Seiteneffekten. Dies kann ganz banale Ursachen haben, wie das Fehlen einer Dokumentation. Oder es wurde den Entwicklern, denen das System zur Wartung übergeben wurde, schlichtweg nicht die Möglichkeit gegeben sich in den Code einzuarbeiten. Meist ist es aber so, dass das System zu komplex geworden ist.

Über Komplexität

Aber was heißt eigentlich Komplexität? Eine mögliche Definition ist, dass es dabei um die Knoten eines Graphen geht, und die Kanten mit denen diese miteinander verbunden sind. Je mehr Knoten in Summe, und je mehr Kanten in Relation zu diesen Knoten ein Graph hat, desto komplexer ist er. Ein Beispiel für eine sehr hohe Komplexität ist im folgenden Bild dargestellt. Es handelt sich bei den außen dargestellten Knoten um die Klassen eines der Module der OpenJPA , und bei den Kanten dazwischen um die Abhängigkeiten dieser zueinander. Man kann sich leicht vorstellen, dass die Wartung dieses Codes zumindest eine Herausforderung darstellt. Wenn die Entwickler der OpenJPA das Ding auch offensichtlich unter Kontrolle haben, so können wir uns hoffentlich darauf einigen, dass diese Situation keinesfalls als erstrebenswert bezeichnet werden kann. Ich habe jedenfalls noch nie erlebt, dass etwas vergleichbares vorab als Zielbild entworfen wurde. Demnach ist davon auszugehen, dass dies irgendwie “passiert” ist und niemals so geplant war.

Ein Teil (!) der Komplexität der OpenJPA Codebasis im Tool Sonargraph

So geht es weiter…

Nachdem wir nun ein gemeinsames Verständnis der Problematik haben, gehen wir einen Schritt weiter und beginnen mit der Lösungssuche. Wir brauchen ein Modell, welches den Entwicklern der Software hilft, dauerhaft die Kontrolle darüber zu behalten. Dazu ist es nötig die Komplexität, die bei der Entwicklung von Software automatisch entsteht, in geordnete Bahnen zu lenken. Dafür gibt es keinen besseren Weg als die Gesamtkomplexität in ihre einzelnen, danach im Kleinen einfacher beherrschbaren, Teile zu zerlegen. Ziel ist, dass diese einzelnen Teile danach:

  • … möglichst ohne Seiteneffekte und Abstimmungsarbeit weiterentwickelt werden können.
  • … unabhängig vom Rest zu dokumentieren und zu verstehen sind.
  • … an ihren ein- und ausgehenden Schnittstellen isoliert getestet werden können.
  • … unabhängig voneinander ausgetauscht werden können.

Im nächsten Teil dieser Artikelserie beginnen wir gleich mit dem ersten logischen Schritt, nämlich der möglichst effizienten Zerlegung eines großen Ganzen in seine Einzelteile und nennen dies: „CUT: Richtig schneiden“! Dabei handelt es sich um das erste von 5 C´s, welche dann in Summe die moderne Sammlung von Prinzipien zum Software-Entwurf darstellen werden.

meetup_vienna

Interaktiver Vortrag: Softwarearchitektur Speed-Dating beim Meetup in Wien

By | Publikationen, Vorträge | No Comments

 „Softwarearchitektur Speed-Dating“
Interaktiver Vortrag. Impuls und Moderation: Stefan Zörner


Veranstaltung beim Softwarearchitektur Meetup Wien
Mittwoch, 3. April 2019
Platinum Vienna, Untere Donaustraße 21, Wien

Foliendownload (PDF)

Zeitgemäße Softwarearchitektur ist nicht das Werk einzelner. Architekturansätze und Ideen entstehen im Team und werden gemeinsam reflektiert. Alle Entwickler müssen sie zumindest verstehen und mittragen können. Aber was genau müsst Ihr vermitteln? Reicht aufschreiben? Hilft UML?

Stefan Zörner zeigt auf lebendige Weise, wie Ihr Eure Softwarearchitektur wirkungsvoll kommunizieren könnt. Nach kurzen theoretischen Inputs rund um Architekturdokumentation und -bewertung probiert Ihr das Gehörte gleich aus. Ihr lernt die Lösungen anderer Teilnehmer kennen und erfahrt Schritt für Schritt, welche Zutaten in einem Architekturüberblick keinesfalls fehlen sollten – egal wie kurz er ist. Ihr lernt die richtigen Fragen zu stellen und passende Antworten parat zu haben.

Bringt bitte die Bereitschaft mit, Euch über Eure Projekte und Softwarelösungen auszutauschen, und anderen Teilnehmern Feedback zu geben. Die sonst üblichen Speed-Dating-Themen wie Ex-Partner und Kinderwünsche klammern wir aus.

Stefan Zörner - Softwarearchitektur Speed-Dating

follow us on Twitter – @embarced

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

xing_workshop Olli

Hands-On Einführung in Machine Learning

By | Allgemein | No Comments

Hands-On Einführung in Machine Learning

Hands-On Einführung in Machine Learning
Peter Götz & Oliver Zeigermann
Workshop
Dienstag, 02. Juli 2019, 9 – 17 Uhr
München

Neuronale Netze sind Software 2.0. Diese Meinung verbreitet jedenfalls Machine Learning Star Andrej Karpathy, Head of AI bei Tesla. Wir beschäftigen uns in diesem Workshop mit dieser These und wie die klassische Software-Entwicklung durch diesen Ansatz ergänzt wird.

Passend dazu lernst du an diesem interaktiven Workshop, wo uns als Software-Entwicklern Machine Learning begegnet und wo sich der Ansatz von
Machine Learning grundsätzlich von dem der Software-Entwicklung unterscheidet und wo es Parallelen gibt.

Anhand eines praktischen Beispiels werden wir dabei Machine Learning als Alternative zu klassischer Business-Logik erleben. Zum Abschluss werden wir diskutieren, wie man einen solchen Ansatz im eigenen Unternehmen oder Projekt integrieren kann und welche Anwendungsfälle sinnvoll sind.

Dieser Workshop eignet sich für jeden, der sich mit Software-Entwicklung beschäftigt und setzt kein Wissen über Machine Learning voraus.

Infos & Anmeldung (Xing)

ModulithFirst_HDowalil_InformatikAktuell

Modulith First! – Artikel von Herbert Dowalil auf Informatik Aktuell

By | Artikel, Publikationen | No Comments

Modulith First! Der angemessene Weg zu Microservices

Logo_Informatik_Aktuell

Modulith First! Der angemessene Weg zu Microservices
Autor: Herbert Dowalil
Artikel auf Informatik Aktuell
online erschienen am 05. März 2019

Hinter dem Microservice-Architekturstil steckt u. a. die Idee, mittels forcierter technischer Abgrenzung durch das Netzwerk die Motivation zur strukturellen und fachlichen Abgrenzung der einzelnen Module (dann Services genannt) zu erhöhen. Dies klappt beileibe nicht immer. Zudem bleibt die Frage, wo genau die Abgrenzung zwischen den einzelnen Services am besten funktioniert? Fachliche, vertikale Strukturen und Domain-Driven-Design sind in aller Munde, stellen aber ebenfalls kein einfach anzuwendendes Patentrezept für eine effiziente Abtrennung von Modulen und Services dar. In diesem Artikel werfen wir einen Blick auf vergleichsweise objektive Ansätze.

Artikel Online Lesen

follow us on Twitter – @embarced