Fantastisch elastisch!

Designherausforderungen von Cloud-Anwendungen

Uwe Friedrichsen

©istockphoto.com/prawny

Das häufigste Argument für Cloud-Lösungen in den Medien und auf Entscheiderebene ist, dass man beim Anwendungsbetrieb Kosten sparen kann, weil man nicht mehr durchgängig die Rechenleistung für die Spitzenlast vorhalten muss, sondern nur noch die Rechenleistung bezahlen muss, die man gerade benötigt. Dabei wird aber häufig übersehen, dass es nicht reicht, eine vorhandene Anwendung einfach in eine Cloud-Umgebung zu deployen, um die versprochene Kostenersparnis zu realisieren, sondern dass Anwendungen dafür explizit designt und umgesetzt werden müssen. Dieser Beitrag gibt einen Überblick, was beim Design einer Anwendung zu beachten ist, damit sie „Cloud-ready“ ist.

Für den von endlosen Budgetdiskussionen geplagten IT-Entscheider klingt es fast zu schön, um wahr zu sein: Flugs ein paar Anwendungen in die Cloud deployt und schon lassen sich bequem Betriebskosten sparen. Man bezahlt nur noch für die aktuell benötigte Rechenleistung und muss nicht mehr durchgängig die Ressourcen für die Spitzenlast vorhalten, die vielleicht nur einmal im Jahr anfällt. Und es muss ja auch gar nicht gleich alles zum Cloud-Provider verschoben werden. Via Hybrid-Cloud-Angeboten läuft bei Normallast alles wie gewohnt im eigenen Rechenzentrum und nur Lastspitzen werden dynamisch über den Cloud-Provider abgehandelt. Und da es immer mehr deutsche Cloud-Provider gibt, entspannt sich auch die Sicherheitsthematik zusehends, die die Nutzung von Cloud-Angeboten bislang recht schwierig gemacht hat.

Euphorie und Ernüchterung

Die Entscheidung fällt leicht: Es wird „Cloud gemacht“, und endlich kann der IT-Entscheider den nächsten Budgetverhandlungen mit dem CFO wieder gelassen entgegen sehen – denkt er. Denn nach anfänglicher Euphorie setzt in der Umsetzung schnell Ernüchterung ein: Die Anwendungen skalieren nicht richtig. Größer geht zwar halbwegs, aber schrumpfen funktioniert nicht. Der versprochene Business Case löst sich in Luft auf und anstelle von Kosteneinsparungen sitzt der IT-Entscheider plötzlich auf horrenden Kosten für die Cloud-Ressourcen – entspannte Budgetverhandlungen ade.

Was ist schiefgegangen? Das Problem ist, dass Anwendungen elastisch sein müssen, um den beschriebenen Business Case realisieren zu können. „Elastisch“ bedeutet in Kurzform, dass einzelne Anwendungsknoten jederzeit hinzugefügt oder weggenommen werden können, ohne dass es für den Anwender sichtbare Auswirkungen haben darf. Auch den ungewollten Ausfall eines Anwendungsknotens muss die Anwendung transparent für den Anwender kompensieren können.

Eine Anwendung wird aber nicht automatisch elastisch, indem man sie in eine Cloud-Infrastruktur deployt. Man muss eine Anwendung vielmehr explizit elastisch konzipieren und umsetzen. Das gilt grundsätzlich, egal ob man eine private, hybride oder öffentliche Cloud-Lösung einsetzt: Anwendungselastizität ist keine Frage der Laufzeitumgebung, sondern des Anwendungsdesigns.

Herausforderungen von Cloud-Umgebungen

Aber wie designt man eine elastische Anwendung? Um diese Frage zu beantworten, ist es sinnvoll, erst einen kurzen Blick auf die Besonderheiten von Cloud-Umgebungen und die daraus resultierenden Herausforderungen für das Anwendungsdesign zu werfen.

Die Basis von Cloud-Lösungen sind einfache Off-the-shelf-Server, die nach Bedarf hinzugefügt und entfernt werden. Diese Server verfügen über keine Spezialhardware zur Erhöhung der Robustheit wie z. B. redundante Netzteile oder RAID-Controller. Die verwendete Hardware in diesen Servern entspricht in etwa der Hardware, die man in den Desktops beim Discounter um die Ecke findet. Auch auf Betriebssystemebene wird in der Regel auf Speziallösungen zur Erhöhung der Robustheit wie z. B. spezielle HA-Lösungen verzichtet. Stattdessen werden typischerweise einfache Linux- oder Windows-Betriebssysteme ohne spezielle Erweiterungen eingesetzt. Durch den gezielten Verzicht auf spezielle, teure Spezialhardware und -software zur Erhöhung der Robustheit sind diese Server außerordentlich günstig in der Anschaffung.

Der Preis, den man dafür zahlt, ist ein erhöhtes Ausfallrisiko auf Hardwareebene gepaart mit dem Verlust von betriebssystemnahen Mechanismen zur Kompensation von Systemausfällen. Das Risiko, ungewollt einen Knoten zu „verlieren“, ist deutlich höher als bei klassischer Rechenzentrumshardware.

Außerdem hat man ein anderes Skalierungsprinzip. In klassischen Rechenzentrumsanwendungen werden Anwendungen meistens skaliert, indem man zusätzliche Hardware in die Server einbaut, auf denen die Anwendung läuft. Dieses Prinzip, einzelne Server aufzurüsten, nennt man „Scale-up“. Cloud-Lösungen werden anders skaliert. Statt einzelne Server zu skalieren, wenn man mehr Rechenleistung benötigt, wird ein zusätzlicher Knoten „neben“ die vorhandenen Anwendungsknoten gestellt. Dieses Prinzip, über Hinzunahme weiterer Anwendungsknoten zu skalieren, nennt man „Scale-out“.

Es wird ein Stück Individualisierbarkeit der Lösung auf Hardware- und Betriebssystemebene abgegeben. Dafür erhält man auf Basis der hohen Standardisierung deutlich vereinfachte Bereitstellungsprozesse, die sich zusätzlich fast vollständig automatisieren lassen. Das führt zu deutlich reduzierten Bereitstellungs- und Betriebskosten pro (Rechen-)Leistungseinheit. Außerdem steigen die Kosten pro Leistungseinheit bei Scale-up-Lösungen nicht linear. Die Kosten steigen in höheren Ausbaustufen deutlich überproportional, sodass schon die Anschaffungskosten pro Leistungseinheit für hohe Skalierungen bei Scale-up-Lösungen meist weit über denen von Scale-out-Lösungen liegen.

Man zahlt für Scale-out den Preis, dass man es mit echten verteilten Anwendungen zu tun hat. Man kann bei der Anwendungsentwicklung nicht mehr so tun, als würde die Anwendung auf einem einzelnen Server laufen (auch bei Cluster-Lösungen wird die Komplexität der Verteilung auf der Infrastrukturebene behandelt und bleibt vor der Anwendungsentwicklung verborgen). Man muss jetzt auf Anwendungsebene mit möglichen Ausfällen von Systemkomponenten umgehen, man muss Laufzeiteffekte, temporär inkonsistente Daten und all die anderen Komplexitäten verteilter Systeme berücksichtigen. Die Unterschiede zwischen klassischen Unternehmensanwendungen und Cloud-Lösungen sind noch einmal in Abbildung 1 zusammengefasst.

Abb. 1: Unterschiede zwischen klassischen Unternehmensanwendungen und Cloud-Anwendungen

Aufgrund dieser veränderten Eigenschaften von Cloud-Umgebungen steht man beim Design einer elastischen Cloud-Anwendung vor einer Reihe von Herausforderungen. Die wichtigsten davon sind:

  • Für den Anwender transparentes On-the-fly-Hinzufügen und -Entfernen von Knoten
  • Verfügbarkeit und Robustheit der Anwendung trotz Unzuverlässigkeit der einzelnen Knoten
  • Verfügbarkeit und Robustheit der Anwendung trotz Verteilung

Bei dem ersten Punkt geht es um die Elastizität einer Anwendung im engeren Sinne. Nur wenn man die eingesetzten Ressourcen transparent für den Anwender nach oben und unten skalieren kann, ist man in der Lage, die möglichen Kostenvorteile des Cloud-Betriebs zu realisieren. Der zweite Punkt verschärft den ersten Punkt in dem Sinne, dass die Anwendung nicht nur mit kontrolliertem Abschalten von Ressourcen umgehen können muss, sondern auch mit ungewollten Ausfällen zurechtkommen muss. Der dritte Punkt fügt die Herausforderungen verteilter Systeme hinzu, die in Cloud-Umgebungen auf Anwendungsebene adressiert werden müssen. Es gibt noch diverse weitere Herausforderungen wie z. B. die dynamische Skalierung von Datenbasen bei beschränkten Knotengrößen, die hier aber nicht näher betrachtet werden, um den Rahmen des Artikels nicht zu sprengen.

Design elastischer Anwendungen

Wie adressiert man jetzt diese Herausforderungen? Klassisches Anwendungsdesign bringt einen in der Regel nicht weiter, da dieses von nicht-fachlichen Anforderungen (Der englische Begriff „functional“ bedeutet im Deutschen „fachlich“ oder „funktional“. Im Kontext von „non-functional requirements“ ist „nicht-fachliche Anforderung“ die bessere Übersetzung, da z. B. Sicherheitsanforderungen sehr wohl funktional, nicht aber fachlich sind.) ausgeht, die im Cloud-Umfeld nicht gegeben sind. Ein einfaches Beispiel: Das klassische Design von Unternehmensanwendungen geht praktisch immer von ACID-fähigen Datenbanken aus. Verteilte Cloud-Anwendungen können aber vielfach keine ACID-fähige Datenbanken nutzen, was gravierende Auswirkungen auf das Anwendungsdesign hat, da man nicht mehr davon ausgehen kann, dass die Anwendung zu jedem Zeitpunkt eine konsistente Datensicht hat (dieser Aspekt wird im weiteren Verlauf des Artikels noch einmal aufgegriffen).

Stattdessen muss man die Prinzipien von verteilten, skalierbaren und fehlertoleranten Systemen im Anwendungsdesign berücksichtigen. Dazu gibt es – obwohl überraschend häufig selbst unter erfahrenen Entwicklern unbekannt – einiges an guter Literatur. Exemplarisch seien hier nur [1], [2], [3] und [4] genannt. Es würde den Rahmen dieses Artikels bei Weitem sprengen, auf alle Prinzipien einzugehen, die nur in den genannten vier Büchern zum Umgang mit den zuvor genannten Herausforderungen beschrieben sind, von weiteren, hier nicht aufgelisteten Quellen ganz zu schweigen. Aus dem Grund werden im weiteren Verlauf des Artikels nur einige ausgewählte Prinzipien vorgestellt (Abb. 2).

Abb. 2: Herausforderungen von Cloud-Anwendungen und resultierende Designprinzipien (Auszug)

Automatisierung

Das erste Prinzip ist Automatisierung. Man muss das Aufsetzen und Starten eines Anwendungsknotens komplett automatisieren. Das beginnt mit dem Installieren des Betriebssystems und den benötigten Infrastrukturkomponenten, geht über das Deployment der Anwendung bis hin zur automatisierten Konfiguration und zum automatisierten Start. Hierbei ist wichtig, dass wirklich kein manueller Schritt erforderlich ist. Denn nur so kann z. B. ein Monitoring-System je nach Lastsituation automatisch Anwendungsknoten hinzufügen oder entfernen.

Bei der Konfiguration ist zu beachten, dass eine statische Konfiguration in der Regel nicht ausreichend ist. Dem neuen Knoten müssen in der Regel die sich dynamisch ändernden Lokationen der laufenden Anwendungsknoten und die aktuell gültige Anwendungskonfiguration bekannt gegeben werden. Außerdem muss der Knoten seine Konfiguration zur Laufzeit aktualisieren können, z. B. wenn ein Anwendungsknoten, mit dem er bislang kommuniziert hat, nicht mehr zur Verfügung steht. Um dies zu ermöglichen, werden typischerweise Dienste wie z. B. Apache Zookeeper [5] eingesetzt.

Konsistenz nach Knotenstart

Beim Start eines Knotens muss sichergestellt werden, dass ein Anwendungsknoten immer in einem garantiert konsistenten Zustand startet, egal ob es sich um einen neuen Knoten handelt, oder ob der Knoten z. B. nach einem Absturz neu gestartet wird. Für zustandslose Knoten ist das einfach. Für Knoten, die Zustand verwalten, z. B. in Form von persistierten Daten, ist das aber häufig nicht trivial. Ist der Knoten zuvor mitten in einer Aktion abgestürzt, müssen ggf. unvollständige Transaktionen zurückgesetzt werden. (Hier ist mit dem Begriff „Transaktion“ keine Datenbanktransaktion gemeint, sondern eine fachliche Transaktion, d. h. eine Reihe fachlich zusammengehöriger Zustandsänderungen, die in Summe einen neuen, fachlich sinnvollen Zustand erzeugen.) Dafür benötigt man Informationen über einen gültigen Zustand, auf den man die Daten zurücksetzen kann (einen so genannten „Checkpoint“) oder aber man benötigt eine Logik, um die Datenkonsistenz zu überprüfen und bei Bedarf wiederherzustellen.

Außerdem muss man noch sicherstellen, dass der Startzustand des neuen Knotens nicht im Widerspruch zum Zustand der restlichen Anwendungsknoten steht. Dies erfordert knotenübergreifende Synchronisationsmechanismen. Viele verteilte Datenspeicher haben solche Mechanismen eingebaut, sodass man sich häufig darauf zurückziehen kann, einen geeigneten Datenspeicher auszuwählen, der die individuellen Anforderungen erfüllt. Dennoch garantieren die meisten verteilten Datenspeicher nicht, dass die Daten über mehrere Knoten hinweg zu jedem Zeitpunkt konsistent sind. Daher muss die Anwendung trotz des Einsatzes eines verteilten Datenspeichers mit Konsistenzmechanismen in der Regel mit temporär inkonsistenten Daten umgehen können (siehe weiter unten).

[ header = Designherausforderungen von Cloud-Anwendungen – Teil 2 ]

Zustandslosigkeit

Wie schon im letzten Abschnitt deutlich wurde, ist Zustandslosigkeit eine hilfreiche Eigenschaft, um einen konsistenten Startzustand für einen Knoten sicherzustellen. Aber auch darüber hinaus ist Zustandslosigkeit erstrebenswert: Zustandslosigkeit erhöht die Robustheit einer Anwendung, weil ein im Hauptspeicher gehaltener Zustand wie z. B. der Session-Zustand eines Servlets beim Absturz eines Knotens verloren geht. Das führt entweder zu fehlenden oder inkonsistenten Daten oder man muss alternativ relativ aufwändige Replikations- oder Persistenzmechanismen vorsehen. Replikationsmechanismen haben den Nachteil, dass sie die Skalierbarkeit einer Anwendung beschränken und Persistenzmechanismen haben in der Regel negative Auswirkungen auf die Performanz.

Daher sollte man auf die Verwaltung von dem transienten Zustand verzichten, wo immer es möglich ist. Der typische Trade-Off in einer Cloud-Lösung ist, den verwalteten Zustand zu minimieren und komplett persistent abzulegen. Die einzelnen (zustandslosen) Knoten lesen den Zustand vor jeder Aktion aus dem Datenspeicher und schreiben ihn am Ende wieder zurück in den Datenspeicher. Die entstehenden Performanzeinbußen (im Sinne der Antwortzeit) federt man über geeignete Caching-Strategien so weit wie möglich ab.

Lose Kopplung und Fehlerisolation

Ein weiteres wichtiges Prinzip zur Erhöhung von Verfügbarkeit und Robustheit ist die lose Kopplung. Das zugrunde liegende Problem ist, dass die Gesamtverfügbarkeit einer Anwendung das Produkt aus den Verfügbarkeiten aller Knoten ist, sofern diese eng gekoppelt sind. Man kennt dies aus klassischen Webanwendungen: Wenn die Datenbank nicht reagiert, bekommt der Anwender keine Antwort mehr, obwohl der Webserver voll funktionsfähig ist. Während eine solche enge Kopplung noch bei wenigen, hochverfügbar ausgelegten Komponenten vertretbar sein kann, ist sie in der Regel bei einem Verbund von vielen, vergleichsweise unzuverlässigen Knoten nicht mehr akzeptabel.

Typischerweise begegnet man diesem Problem, indem man versucht, die Abhängigkeiten der Komponenten so weit wie möglich aufzulösen (lose Kopplung) und Fehlerpropagationen durch die Knoten zu vermeiden (Fehlerisolation). Ein erster Ansatzpunkt ist die Verwendung asynchroner Kommunikation. Sie ist ein technisches Mittel, um die Abhängigkeiten von Komponenten zu reduzieren, da der Aufrufer nicht blockiert ist, solange er auf die Antwort seines Aufrufs wartet. Antwortet der Aufgerufene nicht, kann der Aufrufer so immer noch weiterarbeiten.

Das hilft aber nur, wenn auf fachlicher Ebene Mechanismen implementiert worden sind, um mit der Situation umzugehen, dass man keine Antwort erhält und sei es nur eine Anzeige für den Anwender „Ihre Anfrage konnte nicht bearbeitet werden. Versuchen Sie es später noch einmal“. Häufig werden solche Mechanismen aber nicht implementiert: Der aufrufende Knoten verlässt sich fachlich blind darauf, dass er auf jeden Fall eine Antwort erhält. Kommt keine Antwort, ist er handlungsunfähig. Hier ist fachliches Design erforderlich, damit die technische lose Kopplung nicht zur Makulatur verkommt.

Ergänzend macht es Sinn, weitere Mechanismen zur Fehlerisolation einzubauen, wie z. B. das Circuit-Breaker-Muster, ([2] für Details). Vereinfacht arbeitet ein Circuit Breaker (zu Deutsch: Schutzschalter) nach dem folgenden Prinzip: Wenn eine Schnittstelle zu einer entfernten Komponente mehrere Male in Folge keine Antwort geliefert hat, wird davon ausgegangen, dass die entfernte Komponente ein Problem hat und der Schutzschalter wird ausgelöst. In der Folge wird die entfernte Komponente nicht mehr angesprochen, sondern es wird direkt an der Schnittstelle ein Nichtverfügbarkeitsfehler zurückgegeben – mit dem die aufrufende Programmlogik natürlich umgehen können muss. Auf diese Weise kann z. B. verhindert werden, dass komplette Thread Pools blockieren, weil alle Threads auf eine Antwort der nicht funktionierenden entfernten Komponente warten. Von Zeit zu Zeit wird dann geprüft, ob die entfernte Komponente wieder verfügbar ist und man den Schutzschalter zurücksetzen kann.

Alternativ kann aber auch durch eine dynamische Rekonfiguration des Systems auf eine alternative, entfernte Komponente umgeschaltet werden (siehe weiter oben).

BASE-Fähigkeit

Die Verteilung der Daten auf viele vergleichsweise unzuverlässige, ressourcentechnisch limitierte Knoten führt zu verteilten Datenbasen. Die Stichworte dazu sind Replikation und Sharding. Die Daten werden auf mehrere Knoten repliziert, um einem potenziellen Datenverlust beim Ausfall eines Knotens entgegenzuwirken und aufgrund der Größenlimitierung der einzelnen Knoten werden die Daten in mehrere Teile (Shards) aufgeteilt, die auf unterschiedliche Knoten verteilt werden. Eine wichtige Konsequenz aus dieser Verteilung ist, dass ACID-Transaktionen in der Regel nicht mehr verfügbar sind, da sie über viele (unzuverlässige) Knoten nicht mehr mit ausreichend hoher Systemverfügbarkeit möglich sind.

Stattdessen arbeitet man in der Regel mit so genannten BASE-Transaktionen. Der Punkt, auf den in diesem Artikel eingegangen werden soll, ist das „E“ in BASE. Es bedeutet „eventually consistent“, also schlussendliche Konsistenz (und nicht „eventuell konsistent“, wie es gerne falsch übersetzt wird). Schlussendliche Konsistenz heißt, dass zwar sichergestellt ist, dass die Daten konsistent werden, dass aber nicht wie bei ACID-Transaktionen eine zu jedem Zeitpunkt konsistente Datensicht garantiert ist. Auch wenn man in der Regel in fast allen Fällen eine konsistente Datensicht erhält, kann man gelegentlich auch einmal eine inkonsistente Datensicht erhalten.

Das hat gravierende Auswirkungen auf das Anwendungsdesign, denn die Anwendung muss jetzt in der Lage sein, Inkonsistenzen zu erkennen und damit umzugehen. Es gibt keine Standardstrategie zum Umgang mit inkonsistenten Daten, die immer funktioniert. Stattdessen muss man von Fall zu Fall aus fachlicher Sicht entscheiden, wie man mit Inkonsistenzen umgeht. Manchmal kann es sinnvoll sein, dass die Informationen aus dem aktuellsten Datensatz verwendet werden, manchmal kann man die Daten einfach zusammenfügen. In anderen Fällen muss man ggf. aufwändige Mechanismen zur Herstellung der Konsistenz implementieren und manchmal muss man vielleicht tatsächlich einen transienten Fehler signalisieren – oder auch beim Anwender nachfragen.

Der wichtige Punkt ist, dass BASE-gestützte Anwendungen zusätzlichen Designaufwand benötigen, den man aus ACID-basierten Anwendungen nicht kennt. Das gilt letztlich auch für die anderen Prinzipien, die hier vorgestellt worden sind: Cloud-Anwendungen benötigen zusätzlichen Designaufwand, um den versprochenen Nutzen realisieren zu können.

PaaS

Dies waren einige ausgesuchte Prinzipien, die man beim Design Cloud-fähiger Anwendungen berücksichtigen muss – zumindest wenn die Anwendung wirklich Cloud-fähig und der initial beschriebene Business Case realisiert werden soll. Auch wenn eine ganze Menge weiterer sinnvoller Prinzipien hier aus Platzgründen nicht beschrieben werden konnten, wird bereits aus den beschriebenen ersichtlich, dass Cloud-Fähigkeit kein reines Infrastrukturthema ist, sondern bereits beim Anwendungsdesign beginnt.

PaaS-Umgebungen, die neben der Laufzeitumgebung zusätzlich höherwertige Infrastrukturkomponenten, eine Entwicklungsplattform und häufig auch ein den Cloud-Erfordernissen angepasstes Programmiermodell mitbringen, können einem einige der beschriebenen Probleme abnehmen.

Auf der anderen Seite ist man damit fest an eine Plattform gebunden und kann seine Anwendungen in der Regel nicht mit akzeptablem Aufwand auf eine andere Plattform portieren. Es ist der altbekannte Trade-Off aus Hersteller- bzw. Produktbindung und erhöhter Entwicklungsproduktivität für bestimmte Anwendungsfälle.

Aber auch beim Einsatz einer PaaS-Umgebung sollte man sich nicht der Illusion hingeben, dass damit alle Probleme automatisch gelöst sind. Man muss immer noch einiges an Hirnschmalz in das Anwendungsdesign stecken, um den Herausforderungen von Cloud-Anwendungen zu begegnen.

Zusammenfassung

„Cloud“ ist nicht nur ein Infrastrukturthema, sondern insbesondere auch ein Design- und Entwicklungsthema. Nur ein geeignetes Anwendungsdesign ermöglicht robuste und zuverlässige Anwendungen, die die Vorteile einer Cloud-Umgebung effektiv nutzen können. Der Artikel hat exemplarisch einige der zu berücksichtigenden Designprinzipien vorgestellt, um so ein Gefühl für die erforderlichen Änderungen im Anwendungsdesign zu vermitteln.

Zum Abschluss soll noch erwähnt werden, dass Cloud-fähige Anwendungen alleine nicht ausreichen, um den anfangs beschriebenen Business Case zu realisieren. Der Rest der Organisation muss auch seine Hausaufgaben machen: Die schönste Anwendung mit perfektem Cloud-fähigem Design hilft nichts, wenn der mögliche Nutzen z. B. durch mangelnde Automatisierung bei Provisioning und Betrieb oder ausufernde Controlling-Exzesse, die jede potenzielle Flexibilität im Keim ersticken, zunichte gemacht werden.

Cloud ist ein Thema, das sich letztlich durch alle Bereiche eines Unternehmens zieht, auch wenn es seine Wurzeln in der IT hat. Da es aber gegenüber dem klassischen Provisioning ein weit überlegenes Modell ist, das eine nie zuvor dagewesene Flexibilität und Elastizität in der IT und damit allen, durch die IT unterstützten Prozessen ermöglicht, ist es mehr als sinnvoll, die Cloud-Fähigkeit des Unternehmens aktiv voranzutreiben. Und dafür muss man auch lernen, wie man Cloud-fähige Anwendungen designt und entwickelt.

Geschrieben von
Uwe Friedrichsen
Uwe Friedrichsen
  Uwe Friedrichsen ist ein langjähriger Reisender in der IT-Welt. Als CTO der codecentric AG darf er seine Neugierde auf neue Ansätze und Konzepte sowie seine Lust am Andersdenken ausleben. Seine aktuellen Schwerpunktthemen sind agile Architektur und verteilte, hochskalierbare Systeme. Er ist außerdem Autor diverser Artikel und diskutiert seine Ideen regelmäßig auf Konferenzen.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.