Category

Inhaltliches

Interview – Kim Nena Duggen über Organisationsentwicklung und Mitarbeiterführung

By | Inhaltliches, Publikationen | No Comments

Interview – Kim Nena Duggen über Organisationsentwicklung und Mitarbeiterführung

Kim Nena Duggen im Gespräch mit New Work Heroes in Berlin
Kim Nena Duggen und Jörn Hendrik Ast
Podcast über Selbstorganisation, Mitarbeiterführung, Erfahrungswerte und mehr..
veröffentlicht am 15. Januar 2020
New Work Heroes (podcasts)

Wie kann die Idee gelingen, eine Organisation dahin zu verändern, dass Mitarbeiter freier agieren?

Kim spricht in dem Interview darüber, was Selbstorganisation und New Work für sie bedeuten und welche Erfahrungen sie in ihren Jobstationen gemacht hat. Sie gibt spannende Einblicke und Tipps, wie Du Teams motivierst, neue Organisationsformen zu lernen und aufzubauen.

Wo lauern Hürden und wie kannst Du damit umgehen? Wie kann jeder Einzelne die Organisationsentwicklung und den Change aktiv gestalten? Warum kann es beispielsweise förderlich sein, als Führungskraft die eigene Unsicherheit zu zeigen? Wo ist der Unterschied zwischen Konsens und Konsent in der Führung? Was sind Sprechdenker und warum ist Teamwork für sie essentiell?

 

 

Zum Interview

Interview zum Modul Soft Skills auf dem Architecture Gathering 2019

By | Inhaltliches, Video | No Comments
Interview mit Kim Nena Duggen – Software Architecture Gathering Oktober 2019

Mirko Hillert im Gespräch mit Kim Nena Duggen
online auf YouTube
veröffentlicht am 29. November 2019

Seminar SOFT – Konfliktlösung:
Als Softwarearchitekt konstruktiv mit Konflikten umgehen

embarc Session auf dem Architecture Gathering in München:
Einführung in Machine Learning (Oliver Zeigermann)

Kim Nena Duggen betreut als Kuratorin inhaltlich das Advanced Level-Modul SOFT im iSAQB e.V.. Im Rahmen des TAG 2019 (The Architecture Gathering) hat sie mit Mirko Hillert vom iSAQB gesprochen. In dem Interview zeigte Kim auf, wie Soft Skills Softwarearchitekten in ihrer Arbeit unterstützen können und welche Schwerpunkte das Advanced Modul setzt: Wie gehen Softwarearchitekten mit den unterschiedlichen Erwartungshaltungen in ihrem Projektalltag um? Wo liegen Fallstricke in der Kommunikation? Wie können Sie Konflikte konstruktiv lösen?

 

Artikel iX Developer von Oliver Zeigermann – TensorFlow 2 und Machine Learning

By | Artikel, Inhaltliches | No Comments
„TensorFlow 2 & Machine Learning im Browser“

Artikel: TensorFlow 2 und Machine Learning im Browser
Autor: Oliver Zeigermann
iX Developer, ab S. 140,  erschienen am 5. Dezember 2019
iX Developer online bestellen

Für unseren Artikel im aktuellen iX Developer Sonderheft haben wir uns ein etwas exotisches Thema ausgesucht: Machine Learning mit TensorFlow.js im Browser. Warum das Sinn machen kann und wie Machine Learning im Browser funktioniert beschreibt unser Artikel auf Seite 140.

Mehr über die Grundidee und die Anwendungen von Machine Learning gibt es in dem Vortrag von Oliver Zeigermann auf der OOP in Februar 2020 in München: „Was macht Machine Learning anders?“

 

 

Zum iX Developer Heft

 

 

 

 

 

 

 

 

follow us on Twitter – @embarced

W-JAX 2019: Oliver Zeigermann – Neuronale Netzwerke mit TensorFlow 2 (Videoaufzeichnung)

By | Inhaltliches, Video | No Comments

WJAX_Logo

Neuronale Netzwerke mit TensorFlow 2: von unten nach oben
Sprecher: Oliver Zeigermann
Vortrag auf der W-JAX 2019
Donnerstag, 07. November 2019, 11.45-12.45 Uhr
The Westin Grand in München
@jaxcon
Vortragsaufzeichnung auf Youtube

TensorFlow ist Googles Framework für Neuronale Netzwerke und auch über die Grenzen von Google hinaus ein Standard. In Version 2 hat sich vor allem das Low-Level-API stark verbessert. Damit werden wir beginnen, um Neuronale Netze von Null auf zu verstehen, ohne eine Zeile Mathematik zu benötigen. Danach sehen wir uns dasselbe mit der High-Level-Keras-API an, die das gleiche tut, aber von den (von uns) vorher gemachten Schritten abstrahiert.

Als Teilnehmer oder Zuschauer des Vortrages lernst Du:

  • dass Matrixmultiplikationen die Basis für Neuronale Netzwerke sind
  • was Loss Functions sind
  • wie man von einer Loss Function zu einem trainierten Neuronalen Netzwerk kommt

CONSTRUCT: Aufbauen (5C Design, Teil 6)

By | Allgemein, Inhaltliches | No Comments

Eine Bausteinstruktur kann auf einer Ebene selbst wieder unübersichtlich werden. Baue Systeme höherer Komplexität durch Zusammenfassen von Bausteinen einer Ebene zu einem neuen Baustein einer nächsthöheren Ebene. Dabei sind weiterhin dieselben Handlungsmaximen anzuwenden.

  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

In den bisherigen Artikeln ging es darum, wie man ein System auf einer Ebene effizient in einzelne Module zerlegt. Bei entsprechend großem Umfang der Lösung kann dabei nach wie vor Folgendes passieren: Die schiere Anzahl der Module wächst Ihnen über den Kopf und das gesamte System ist im Endeffekt wieder unübersichtlich. Was kann man in so einem Fall tun? Eine schlechte Idee wäre, mehr Logik in die einzelnen Module zu geben, sodass die Gesamtzahl an Modulen wieder überschaubar wird. Dadurch würde nur Komplexität in die Module hinein verlagert. Die Alternative ist Strukturen auf der nächst höheren Abstraktionsebene zu bilden. Wir zoomen aus unserer Architektur heraus und bauen dann modulare Strukturen nach denselben Regeln, nach denen wir schon die Module auf der unteren Abstraktionsebene entworfen hatten. Während dies den Vorgang eher bottom-up beschreibt, so ist dasselbe Ergebnis natürlich auch top-down zu erreichen. 

Whole-Part Pattern

Ein fast schon in Vergessenheit geratenes Muster, das dabei hilfreich sein kann, ist das Whole-Part Pattern. Vorgestellt wurde es in einem zeitlosen Klassiker der Software-Architektur namens “Pattern-Oriented Software Architecture” aus dem Jahr 1996. Als Beispiel dient uns der Motor eines Autos. Er besteht aus vielen Einzelteilen, wie dem Kolben, der Kurbelwelle und den Zündkerzen. Der Motor selbst ist aber wiederum nur ein Teil des Autos, das außerdem noch aus Dingen wie dem Lenkrad, der Karosserie, den Rädern und dem Auspuff besteht. Vor dem Fahrer wird diese Komplexität weitestgehend verborgen. Er dreht den Schlüssel um, beschleunigt mit dem Gaspedal und beeinflusst mit dem Lenkrad die Fahrtrichtung. Einmal im Jahr kommt das Auto in die Werkstätte zur Wartung. Dass dort der Filter der Klimaanlage genauso gewechselt wird wie das Motoröl kann dem Fahrer egal sein. Die Abstraktion “Auto” verbirgt all diese Komplexität vor seinem Nutzer so gut es geht.

Das Beispiel mit dem Auto könnte man sogar noch weiterdenken. Ein Robotertaxi-Service könnte eine weitere Abstraktionsebene darstellen. Eine Serversoftware weiß wo sich die einzelnen selbstfahrenden Taxis gerade befinden. Wenn ein Kunde per App eines der Taxis anfordert wird die Software des nächstbesten Robotertaxis dieses zum Kunden bewegen und ihn in weiterer Folge an sein gewünschtes Ziel bringen. Das Auto, die Software darin, die Software am Server und die App am Handy des Kunden bilden dann in Summe eine weitere Abstraktionsebene, die die Mobilität für den Kunden noch weiter vereinfacht.

In einer komplexen Enterprise Architektur können Sie sich dieses Prinzip wie folgt zu Nutze machen: Die Unternehmensarchitekten legen fest, wie sich die Systemlandschaft in einzelne Subsysteme zerlegt. Sie kümmern sich außerdem um Themen, die über die einzelnen Subsysteme hinaus gehen, wie die Wahl der Technologie zur Integration der einzelnen Systeme. Für die Implementierung der einzelnen Subsysteme selbst wird aber nur der grobe Rahmen definiert. Die Verantwortung für die korrekte Umsetzung wird in den einzelnen Teams belassen. Zu klären, wie sich ein konkretes Subsystem selbst dann weiter in Module zerlegt, ist also wiederum Aufgabe eines der Teams. Die Unternehmensarchitekten kümmern sich in einem solchen Modell um die strategischen Themen und überlassen die Taktik den einzelnen Teams.

Self-Contained Systems

Da die Gedanken bisher eher abstrakt waren, möchte ich noch einen konkreten Architekturstil vorstellen, mit dem man das Prinzip der hierarchischen Zerlegung prima umsetzen kann, nämlich Self-Contained Systems (oder kurz: SCS). Diese setzen explizit auf eine sehr lose Kopplung zwischen den einzelnen Teilsystemen. Bevorzugt wird eine Integration über das User-Interface. Ansonsten ist zeitliche Abhängigkeit zwischen den Systemen durch synchrone Kommunikation (wie über SOAP-RPC) eher verpönt und man sollte stattdessen auf asynchrone Integration (wie über Messaging) und Datenreplikation setzen.

Damit das klappt muss anfangs strategisch festgelegt werden, an welchen Grenzen sich das System gut in solch lose gekoppelte Subsysteme zerlegen lässt. Wenn das allerdings klappt, können die einzelnen Teams relativ isoliert voneinander an ihrem jeweiligen Subsystem arbeiten. Die Art der Umsetzung der einzelnen Teilsysteme kann sich dabei auch stark voneinander unterscheiden. So kann es sich bei einem der Subsysteme um einen Legacy-Deployment-Monolithen handeln, während ein anderes auf einer Microservice-Architektur aufbaut.

Im nächsten und bereits letzten Beitrag dieser Artikelserie fass ich das Thema noch einmal kurz zusammen. Außerdem zeige ich Möglichkeiten, wie man eine Architektur nach 5C umsetzt.

CONNECT: Verbinden (5C Design, Teil 5)

By | Allgemein, Inhaltliches | No Comments

Durch Verwendung einer Schnittstelle eines anderen Bausteins kommt es immer zu Abhängigkeiten. Plane explizit zwischen welchen Bausteinen es welche Art von Abhängigkeit geben soll.

  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

Damit ein System, das in seine Einzelteile zerlegt wurde, in Summe das gewünschte große Ganze ergibt, müssen seine Einzelteile miteinander interagieren. Diese Interaktionen erzeugen Verknüpfungen, die wiederum die einzelnen Teile in Abhängigkeiten zueinander bringen. Vereinfacht gesagt sind bei Änderungen, die nach außen wirksam sind, die angebundenen Module immer zu berücksichtigen. Diesen Aspekt gilt es möglichst klein zu halten. Die Abhängigkeiten können unterschiedliche Bereiche betreffen:

  • Daten und Formate: Ein Consumer muss das Format des Providers einer Schnittstelle verstehen. Bei Änderungen daran ist der Consumer ebenfalls betroffen.
  • Zeit: Wenn der Consumer seine Arbeit nur abschließen kann, wenn der Provider im selben Moment auch gerade verfügbar ist, so ist seine Verfügbarkeit auf Zeiten limitiert, in denen der Provider ebenfalls verfügbar ist. In dem Fall kommt es zu einer zeitlichen Abhängigkeit zwischen den Modulen.
  • Technologie: Ist der Consumer in der technologischen Auswahl seiner Implementierung auf irgendeine Weise eingeschränkt? Ein Netzwerkprotokoll wie Java RMI wirkt beispielsweise einschränkender als eine REST-Integration auf Basis des HTTP-Protokolls.
  • Ausführungsort: Ist es zur Interaktion nötig, dass die Module auf derselben (virtuellen) Maschine laufen, so sind sie was den Ausführungsort angeht eingeschränkt. Bei Kommunikation innerhalb der Grenzen des eigenen Prozesses ist dies beispielsweise so.

Diese technischen Abhängigkeiten ziehen in weiterer Folge Abhängigkeiten auf der organisatorischen Ebene nach sich. Ein Unternehmen, das zur Weiterentwicklung seiner Software überdurchschnittlich viele Meetings benötigt wäre ein Beispiel, wo sich diese Auswirkungen zeigen. Das und ein gezwungenermaßen hohes Maß an Bürokratie sind Indizien dafür, dass man die Abhängigkeiten innerhalb der Software nicht mehr im Griff hat.

Zur Vermeidung von Missverständnissen: Wenn Interaktion nötig ist, ist es nicht möglich, Abhängigkeiten völlig zu vermeiden. Und: Interaktion wird nötig sein. Es gilt die negativen Auswirkungen von Abhängigkeiten durch geschickten Architekturentwurf im Zaum zu halten. Ein bekanntes Entwurfsprinzip, dessen Befolgung dabei hilft, ist das Prinzip der azyklischen Abhängigkeiten. In einem Entwurf wo Module zyklisch voneinander abhängig sind, sind diese Module von jedem anderen in diesem Zyklus direkt oder indirekt abhängig. Diese Situation birgt hohe Risiken von unerwünschten Seiteneffekten bei Änderungen. Daher der Grundsatz Strukturzyklen möglichst zu vermeiden.

Kaskadierende Abhängigkeiten

Ebenfalls problematisch sind sogenannte Kaskadierende Abhängigkeiten. Dabei pflanzt sich ein und dieselbe Abhängigkeit, über die jeweiligen Modulgrenzen hinaus über weitere Abhängigkeiten zu weiteren Modulen fort. Mit anderen Worten: Was auch immer diese Abhängigkeit betrifft, sei es ein Datenformat oder eine Technologie, man wird sie nur ändern oder entfernen können indem man Änderungen am gesamten System vornimmt. Die folgende Grafik illustriert das:

Das Schnittstellenformat, das Baustein A anbietet, wird von den Bausteinen B und C selber wiederum intern benützt. Weiters kommen einzelne Aspekte dieser Schnittstelle in den externen Schnittstellen der Bausteine B und C wieder vor, wodurch sie sich auf deren Consumer (in diesem Fall D, E, F und G) ausbreiten. Eine Änderung der Schnittstelle des Bausteins A zieht dann eine Kaskade an Änderungen nach sich, die schließlich das gesamte System betreffen. Um so etwas zu verhindern bietet sich die Anwendung des Integrationgsmusters Anti-Corruption Layer aus dem Domain-Driven-Design an, das in der folgenden Grafik dargestellt ist. Dabei kommuniziert ein Consumer nur über einen solchen Layer mit seinem Provider, der das Modell des Providers in sein eigenes Format konvertiert.

Wenn Sie den Empfehlungen dieser Blog-Serie beim Entwurf Ihres Systems bis hierhin gefolgt sind kann es sein, dass Ihr Entwurf bereits aus einer sehr großen Zahl an Modulen besteht. Eine hohe Anzahl an Modulen kann, auch wenn diese jeweils wunderbar gekapselt sind, wiederum unübersichtlich werden. Sie tun dann gut daran, wiederum dieselben Regeln anzuwenden, allerdings eine Abstraktionsebene höher. Dies bringt uns zum nächsten Beitrag dieser Reihe: „CONSTRUCT: Aufbauen“.

CONTRACT: Schnittstelle festlegen (5C Design, Teil 4)

By | Allgemein, Inhaltliches | No Comments

Entwerfe Schnittstellen so, dass eine möglichst reibungslose Interaktion zwischen dem Baustein und seinen Consumern möglich ist.

  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

Wie legt man eine Schnittstelle fest, die eine möglichst reibungslose Interaktion ermöglicht? Tatsächlich ist das Thema bereits in den SOLID Prinzipien sehr prominent vertreten. So steht das L darin für das Liskovsche Substitutionsprinzip von Barbara Liskov (nähere Infos hier). Noch interessanter finde ich aber das I aus SOLID, das für das Interface-Segregation Prinzip steht. Dies ist die erste von 3 Techniken für sauberes Schnittstellendesign, die ich ihnen hier vorstelle. Es besagt, dass die Schnittstelle eines Moduls immer nach ihren einzelnen Verantwortlichkeiten aufgeteilt sein sollte. Es darf demnach nicht eine einzige generische Schnittstelle geben, sondern für jeden Anwendungsfall der Benutzung einer Komponente jeweils eine eigene. Ich möchte das anhand eines Beispiels erläutern.

Interface Segregation und REST-APIs

Die Web Service Description Language (oder kurz WSDL) ist eine Beschreibungssprache für Schnittstellen und ein offizieller Standard des W3C Konsortiums. Sie wird meist in Kombination mit dem Simple Object Access Protocol (oder kurz SOAP) verwendet. Bei meinem Grundlagen-Seminaren zum Thema Software-Architektur geht es u.a. immer auch um das Interface-Segregation Prinzip. Ich frage die Teilnehmer dann gerne, ob jemand unter ihnen schon mal eine WSDL-Datei bekommen hat, die mehrere Megabyte groß war, und von der er (oder sie) sich zunächst einmal wie erschlagen fühlte. Eigentlich ist immer jemand dabei, der so eine Situation schon mal erlebt hatte. Solch komplexe Schnittstellen haben 2 potentielle Probleme:

  • Der Einarbeitungsaufwand für den Entwickler, der diese Schnittstelle verwenden möchte, ist ausgesprochen hoch. In der WSDL Spezifikation ist nicht vorgesehen, die Schnittstelle zu strukturieren.
  • Es besteht die Gefahr, dass der Code, der die Schnittstelle konsumiert, Abhängigkeiten zu Teilen der Schnittstelle entwickelt, die von diesem gar nicht benötigt werden. Sobald das Modell zur Schnittstelle vom Client generiert wurde ist es möglich, auch zu nicht relevanten Teilen einer nur teilweise verwendeten Schnittstelle Abhängigkeiten zu entwickeln.

Als Standard für Schnittstellen im Web hat sich inzwischen der Representational State Transfer als Alternative etabliert. Dabei wird eine komplexe Schnittstelle in ihre einzelnen „Subjekte“ bzw. Ressourcen aufgeteilt. Dieser Schritt entspricht einer Umsetzung des Interface-Segregation-Prinzips. REST hat also das I aus SOLID quasi „mit eingebaut“.

Die Kommunikation mit diesen einzelnen Ressourcen erfolgt mit den „Verben“ des HTTP-Protokolls, also beispielsweise mittels GET, PUT, POST und DELETE. Dabei gibt es auch Weiterleitungen von einer Ressource zur nächsten, die damit in Verbindung steht. Diese Weiterleitung (genannt Hypermedia As the Engine of Application State oder kurz: HATEOAS) bietet eine gewisse Flexibilität was die Änderung der einzelnen Endpoints angeht. Außerdem ist sie noch eine Form der Selbstdokumentation für Nutzer der Schnittstelle. Das Gefühl der Frustration, weil man so etwas wie eine monströse WSDL erhalten hat, sollte es bei REST-APIs also gar nicht erst geben.

Das Schnittstellen-Problem des Mars-Climate Orbiter

Neben der Schnittstellentrennung gibt es weitere Techniken für eine reibungslose Interaktion. Im konkreten Beispiel hätte ihre Anwendung einen Millionenschaden vermieden. Vielleicht hatten Sie es 1999 in den Nachrichten mitbekommen: Die NASA Sonde Mars-Climate Orbiter war abgestürzt. Sie hätte in einen Orbit um den Planeten Mars eintreten sollen, ist aber stattdessen auf dessen Oberfläche zerschellt (für nähere Infos dazu klicken Sie bitte hier). Passiert war Folgendes: Die Software wurde in Teilen von der NASA und teilweise von Lockheed-Martin entwickelt. An einer Stelle gab es eine Schnittstelle zur Weitergabe des gemessenen Impulses an die Steuerungseinheit. Während das eine Team davon ausging, dass dieser auf dem metrischen System basiert ging das andere Team von einer Abbildung basierend auf dem imperialen System aus. Das Ergebnis war der oben erwähnte Verlust der Raumsonde und ein entsprechender Millionenschaden. Ich könnte mir vorstellen, dass diese Schnittstelle nicht viel spezifischer definiert war als in folgendem Listing:


public double getSpeed ();

Eine solche Definition lässt einfach zu vieles offen. Um welche Einheit handelt es sich bei der Geschwindigkeit? Außerdem ist nicht klar ob auch negative Werte möglich sind, oder nur positive Werte als Rückgabewerte in Frage kommen. Diese Art von Problem kann durch eine spezifischere Definition des Schnittstellenkontraktes behoben werden. Viel besser ist die folgende Definition, wo wir uns des JSR305 bedienen und mit der entsprechenden Annotation beschreiben, dass diese Methode keine negativen Werte zurückliefert. Außerdem wird durch die Methodensignatur klar, dass es sich um die SI Einheit für Geschwindigkeit handelt:


public @Nonnegative double getSpeedInMetersPerSecond();

Achtung, Gefahr!

Besonders problematisch sind Schnittstellen, die einem jeden Consumer die Möglichkeit bieten, den Provider selbst in Probleme zu bringen. Dies zu vermeiden wäre mein 3. Tipp zum Thema. Wo man dies häufig antrifft ist, wenn sich mehrere Services oder Subsysteme den Zugriff auf ein und dieselbe Datenbank mit demselben Datenmodell teilen. So wie dies in der folgenden Grafik dargestellt ist:

Die Schnittstelle, die eine solche geteilte Datenbank ihren Consumern bereitstellt ist nämlich das Datenmodell selbst, und im Falle einer relationalen Datenbank die Abfragesprache SQL. Das beinhaltet die Möglichkeit einzelne Tabelleneinträge zu sperren, oder auch die Datenbank mit umfangreichen Query-Abfragen inkl. Joins über viele Tabellen zu lähmen. Eine dermaßen missbräuchliche Verwendung wird die anderen Consumer der Datenbank ebenfalls in Probleme bringen. Genau das sollte aber über eine Schnittstelle gar nicht erst möglich sein. Ich rede dabei wohlgemerkt nicht von Denial-of-Service Attacken, sondern von Dingen, welche früher oder später im Zuge der üblichen Wartungstätigkeit passieren.

Wird eine Schnittstelle zwar angeboten, aber von niemandem benutzt, ist natürlich noch keine Interaktion passiert. Im nächsten Beitrag (“CONNECT: Verbinden”) betrachten wir die Folgen, welche es zwangsläufig haben wird, wenn Bausteine über Schnittstellen miteinander interagieren.

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 …