Microservices Blog - Architektur, Skalierung und Best Practices https://jax.de/blog/microservices/ Java, Architecture & Software Innovation Wed, 27 Aug 2025 16:13:27 +0000 de-DE hourly 1 https://wordpress.org/?v=6.7.2 Microservices vs. Monolithen: Architektur-Strategien nach dem Hype https://jax.de/blog/microservices-architektur-nach-dem-hype-trends/ Wed, 27 Aug 2025 15:12:07 +0000 https://jax.de/?p=107721 Zehn Jahre Microservices führen zu den Fragen, ob es langsam an der Zeit für einen neuen Hype ist und ob von dem um Microservices noch etwas übrig ist. Schließlich wissen wir doch mittlerweile, dass Microservices viel zu komplex sind. Wie sollen wir also jetzt Architekturen aufbauen?

The post Microservices vs. Monolithen: Architektur-Strategien nach dem Hype appeared first on JAX.

]]>
Aber wenn nicht Microservices – was dann? Die wesentliche Alternative zu einem Microservices-System ist ein Monolith. Genau genommen gibt es verschiedene Arten von Monolithen (Abb. 1):

  • Ein Deployment-Monolith kann technisch nur als Ganzes deployt werden. Gerade ältere Enterprise-Java-Systeme zählen zu dieser Kategorie.
  • Bei einem Architekturmonolithen gibt es hingegen keine vernünftige Strukturierung des Systems in einzelne Module. Oder die Abhängigkeiten zwischen den Modulen sind so stark ausgeprägt, dass man einen Teil des Systems weder isoliert verstehen noch ändern kann. Eine andere Bezeichnung für diesen Architekturansatz ist „Big Ball of Mud“ [1]. Es gibt gute Indizien, dass er einer der populärsten Architekturansätze ist, weil man mit diesem Ansatz zumindest kurzfristig schnell Software entwickeln kann.

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Spring AI [4], das kürzlich das MCP integriert hat, bietet Entwicklerinnen und Entwicklern nun auf Java-Basis eine komfortable Möglichkeit, MCP-Server direkt in Spring-Boot-Anwendungen zu konsumieren und eigene MCP-Server zu implementieren.

Abb. 1: Microservices und die verschiedenen Spielarten von Monolithen

Es geht es also um zwei unterschiedliche Eigenschaften des Systems: Das Deployment und die Änderbarkeit. Ein gemeinsames Deployment kann sinnvoll sein, weil dazu eine weniger komplexe Infrastruktur notwendig ist – kein Kubernetes, nur eine Deployment Pipeline, einfachere Anforderungen bei Observability. Die Wahl eines Deployment-Monolithen kann daher eine sinnvolle Architekturentscheidung sein. Ein Architekturmonolith ist hingegen das Ergebnis, wenn man die Strukturierung der Software beispielsweise zugunsten von Investitionen in Features vernachlässigt.

Warum eigentlich Microservices?

Microservices sind ein Gegenentwurf zu Deployment-Monolithen. Typischerweise teilt man das System beispielsweise in einzelne Docker-Container auf, die eine Schnittstellen für ihre Funktionalitäten mit Technologien wie REST im Netz anbieten. So kann man technisch gesehen jeden dieser Microservices unabhängig voneinander deployen. Um tatsächlich unabhängig deployen zu können, muss es getrennte Deployment-Pipelines geben. Das ist nur möglich, wenn es beispielsweise auch unabhängige Tests gibt. So wird der Deployment-Prozess eines Deployment-Monolithen, der oft groß und komplex ist, durch viele kleine Deployment-Prozesse ersetzt, die einfacher sein sollten.

Das getrennte Deployment ist nicht der einzige Vorteil von Microservices. Es gibt weitere:

  • Microservices können auch getrennt skaliert werden. Man kann mehrere Instanzen jedes einzelnen Microservice starten und so jeden Microservice an die individuelle Last anpassen.
  • Der Absturz eines Prozesses führt nur zum Ausfall eines Microservice, die anderen laufen weiter. Bei einer geschickten Aufteilung des Systems bleiben die meisten Funktionalitäten erhalten. Microservices können also dabei helfen, die Resilience (Widerstandsfähigkeit) eines Systems zu verbessern.
  • Auch zugunsten der Sicherheit kann die Aufteilung in Docker-Container Vorteile bringen. Zwischen den Containern können beispielsweise Firewalls eingezogen werden, sodass ein Angreifer mehr Schwierigkeiten hat, Kontrolle über größere Teile des Systems zu erlangen.
  • Schließlich können unterschiedliche Microservices auf verschiedener Hardware laufen. o kann ein Teil des Systems, der beispielsweise GPUs benötigt, auf entsprechender Hochleistungshardware betrieben werden, während der restliche Systemteil auf weniger leistungsfähiger und damit kostengünstiger Hardware läuft.

Ein Argument für Microservices war, dass sie das Durchhalten einer Aufteilung einfacher machen. Man kann nicht einfach irgendeinen Bestandteil wie eine einzelne Klasse eines anderen Microservice nutzen, sondern nur die Schnittstelle, die beispielsweise über REST angeboten wird. In einem Deployment-Monolithen ist es hingegen leicht, auf irgendeine Klasse irgendwo im System zuzugreifen. Wenn das zunächst nicht geht, kann man auch schnell die Sichtbarkeit auf public umstellen – und schon kann man eine neue Abhängigkeit einbauen. So gehen die ursprünglich geplanten Strukturen verloren und am Ende steht ein Architekturmonolith. Microservices sollten also dieses Problem der Architekturmonolithen lösen, so die Hoffnung.

ALL ABOUT MICROSERVICES

Microservices-Track entdecken

 

Microservices = bessere Strukturierung?

In der Realität sind aber „verteilte Monolithen“ (Distributed Monoliths) entstanden. Sie sind Architekturmonolithen, weil sie eine schlechte Strukturierung des Systems aufweisen. Die schlechte Struktur hat bei Microservices-Systemen die Auswirkung, dass Änderungen mehrere Microservices umfassen und die Microservices auch sehr viel miteinander kommunizieren. Durch die starken Abhängigkeiten ist das System schwer modifizierbar und hat eine schlechte Performance. Dafür kann es verschiedene Gründe geben:

  • Der schon erwähnte Architekturmonolith bzw. Big Ball of Mud ist populär, weil man zunächst sehr einfach und schnell zu Ergebnissen kommt. Erst später tauchen die Probleme auf. So kann man mit einem Microservices-System sehr schnell bei einem verteilten Monolithen enden. Dazu kommt, dass in einem Microservices-System zwar die Änderungen in einem Microservice vielleicht einfacher sind – wenn man aber die Struktur des Gesamtsystems ändern will, muss dazu Code zwischen Docker-Containern verschoben werden. Das ist selbst dann aufwendig, wenn der Code aller Docker-Container mit derselben Programmiersprache geschrieben worden ist.
  • Wenn man einen Microservice so definiert, das er einfach und schnell ersetzbar sein soll, endet man bei sehr vielen Microservices. So ergibt sich leicht ein unübersichtliches System mit starken Abhängigkeiten. Die Priorisierung der Ersetzbarkeit gegenüber einer guten Strukturierung des Gesamtsystems ist eigentlich kaum sinnvoll. Die Nachteile der schlechten Struktur wiegen die Vorteile der besseren Ersetzbarkeit einzelner Microservices vermutlich mehr als auf. Bei solchen Entscheidungen kann auch eine Rolle spielen, Microservices ‚richtig‘ umzusetzen – also im Sinne gängiger Definitionen. Manche davon betonen beispielsweise die Austauschbarkeit einzelner Services als zentrale Eigenschaft. Allerdings ist das bloße Einhalten einer Definition wenig zielführend, wenn dadurch eine insgesamt schlechte Architektur entsteht.

Tatsächlich entstehen in einigen Situationen sogar Systeme mit hunderten Microservices. Das macht den Betrieb noch komplizierter und das System wird unübersichtlich, was es noch schwieriger macht, das System zu verstehen und zu ändern. Die Unübersichtlichkeit ist eigentlich erstaunlich: Ein System mit Hunderten von Packages ist nicht zwingend chaotisch. Wenn die Fachlichkeit eine solche Menge an Code erfordert, dann gibt es Wege, das System änderbar und verstehbar zu machen. Packages können in Packages zusammengefasst werden. Und natürlich kann das System in mehrere Maven-Projekte oder JAR-Dateien aufgeteilt werden. So ergeben sich grobgranulare Module, von denen es wenigere gibt, sodass das System verstehbar und änderbar wird. Durch eine solche Hierarchisierung kann man also komplizierte Systeme dennoch verstehen und entwickeln.

Wenn also ein Microservices-System zu viele Microservices enthält und daher unübersichtlich ist, ist die Reduktion der Anzahl nur eine Option. Für das Verständnis kann eine Hierarchie hilfreich sein – beispielsweise Cluster von Microservices, zum Beispiel durch eine Namenskonvention. Eine andere höhere hierarchische Ebene sind Teams: Sie sind für mehrere Microservices zuständig und haben in gewisser Weise auch eine Schnittstelle, nämlich alle Funktionalitäten, die sie anderen Teams anbieten. Wenn man die Schnittstelle eines Clusters oder der Microservices eines Teams beispielsweise an einem API Gateway als konsolidierte Schnittstelle nach außen anbietet, wird sich eine solche grobgranulare Strukturierung auch in der umgesetzten Architektur erkennbar machen und die Nutzung der Funktionalität vereinfachen.

Da Microservices über das Netz kommunizieren, kann eine große Zahl an Microservices auch zu Netzwerk-Overhead und schlechter Performance führen. Das Problem kann nur gelöst werden, indem die Anzahl der Microservices reduziert und die Aufteilung so angepasst wird, dass fachliche Verantwortung idealerweise in genau einem Microservice implementiert sind.

Bessere Struktur – aber wie?

Zu komplexe Abhängigkeiten von Microservices und zu viele Microservices zeigen dennoch deutlich: Microservices führen nicht immer zu einer besseren Strukturierung von Systemen. Das sollte eigentlich nicht überraschen. Sie sind nur ein Ansatz, um die Bestandteile eines Systems anders zu implementieren – eine Alternative zu Java Packages oder JARs. Wenn man die Aufteilung des Systems nicht gut hinbekommt, sind die Konsequenzen bei einer Microservices-Aufteilung sogar schwerwiegender. Aus dieser Beobachtung haben sich mehrere Konsequenzen entwickelt:

  • Sicher ist es eine häufige Schwäche von Deployment-Monolithen, dass ihre Strukturierung entweder zu wünschen übriglässt oder mit der Zeit schlechter wird. Ohne zusätzliche Maßnahmen ist es fast sicher, dass das System über die Zeit unwartbar wird. Werkzeuge zum Architekturmanagement [2] können aber Abhängigkeiten überwachen und vermeiden so, dass sie sich unbemerkt einschleichen. Außerdem wird die Struktur explizit und formal überprüfbar, was Diskussionen über dieses Thema vereinfacht. Wenn eine Regel durchbrochen wird, sollte man idealerweise über sie diskutieren: Entweder ist sie nicht sinnvoll oder unklar. In beiden Fällen ist Kommunikation zielführend.
  • Im Java-Universum erlaubt beispielsweise ArchUnit [3], die Struktur eines Systems mit einem Unit-Test zu überprüfen. Spring Modulith [4] kann bei der Strukturierung von Spring-Anwendungen helfen. jMolecules [5] gibt Regeln vor, mit denen ein System entsprechend taktischem Domain-Driven Design strukturiert werden kann.
  • Das Durchsetzen, Überprüfen und Weiterentwickeln der Strukturierung des Systems setzt voraus, dass man das System sinnvoll aufgeteilt hat. In diesem Zusammenhang ist ein neues Interesse an Modularisierung als grundlegendes Konzept zur Systemstrukturierung entstanden. Auch Domain-Driven Design (DDD) hat dabei an Relevanz gewonnen. Besonders die grobgranulare Modularisierung durch Bounded Contexts kann hier unterstützen – weniger das taktische DDD mit seiner feingranularen Aufteilung in einzelne Klassen.

Diese Entwicklung ist ein Fortschritt. So rücken grundsätzliche Herausforderungen in der Softwarearchitektur in den Fokus. Weil Themen wie die Strukturierung von Systemen so wichtig sind, ist das eine sehr gute Entwicklung. Scheinbar bedeutet das aber ein Scheitern von Microservices, denn sie haben anscheinend ihr Versprechen nicht eingelöst. Tatsächlich war es aber von Anfang an klar, dass Microservices nur dann Vorteile bringen, wenn es eine gute Aufteilung gibt. Und Microservices haben, wie schon erwähnt, einige Vorteile im Bereich Skalierung, Sicherheit, Resilience und der Flexibilität bezüglich der Hardware.

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Für solche Zwecke ist der Einsatz von Microservices also nach wie vor sinnvoll und es gibt kaum gute Alternativen. Oft ergibt sich ein Entwurf, der bestimmte Teile des Systems als eigene Docker-Container umsetzt, ganz natürlich aus bestimmten Anforderungen, zum Beispiel wenn man einen Teil des Systems isolieren will, der typischerweise unter hoher Last steht oder nicht besonders stabil ist.

Deployment

Es gibt noch einen weiteren Vorteil, den man eigentlich nur durch die Implementierung eines Systems mit Microservices erreichen kann: das unabhängige Deployment. In diesem Bereich gibt es nämlich kaum eine echte Alternative zu Microservices. Und die Vorteile können signifikant sein: Kleinere Deployment-Einheiten sind einfacher und mit weniger Risiko zu deployen. Und häufigere Deployments führen zu mehr Produktivität, weil Teams ihren gesamten Prozess beschleunigen und zuverlässiger machen müssen [6].

Aber auch beim Deployment wird nicht plötzlich alles gut, nur weil man Microservices nutzt: Die gesamte Deployment-Pipeline jedes Microservices muss unabhängig sein, wenn man die Microservices wirklich unabhängig deployen möchte. Das ist zum Beispiel nicht der Fall, wenn das gesamte System – also alle Microservices – gemeinsam End-to-End getestet werden sollen und diese Tests der hauptsächliche Mechanismus sind, um Fehler zu finden. Der Fokus auf Tests des Gesamtsystems kann gute Gründe haben. So kann es sein, dass es kaum sinnvoll ist, nur einen Teil des Systems zu testen, weil Fachlichkeiten nur im Zusammenspiel mehrerer Systeme funktionieren. Dann ist die schlechte Strukturierung des Systems aber die Quelle des Problems und müsste idealerweise beseitigt werden.

Consumer-driven Contract-Tests

Technisch lässt sich ein Integrationstest mehrerer Microservices durch Consumer-driven Contracts umsetzen [7] (Abb. 2). Statt die Microservices gemeinsam zu testen, ruft ein Microservice im Test eine Simulation der anderen Microservices auf, einen sogenannten Stub. So wird klar, welche Art von Interaktion der Microservices von anderen Microservices erwartet. Daraus wird ein Contract (Vertrag) generiert. Dieser Vertrag ist die Basis von Tests des aufgerufenen Microservices. So kann der aufrufende Microservice in seiner Continuous Delivery Pipeline einen formalen Vertrag erzeugen und der aufgerufene Microservice in seiner Continuous Delivery Pipeline mit diesem Vertrag getestet werden. So ist sichergestellt, dass beide Microservices zusammenarbeiten können, auch wenn sie nie zusammen in einer Umgebung liefen. Ein populäres Werkzeug für Consumer-driven Contract-Tests ist Pact [8], das solche Contract-Tests auch bei der Benutzung verschiedener Programmiersprachen in den Microservices unterstützt.

Abb. 2: Consumer-driven Contract-Tests können Integrationstests beseitigen

Organisation

Aber gemeinsame Tests sind nicht der einzige Grund, warum Microservices zusammen deployt werden. Es kann auch sein, dass ein Deployment einzelner Microservices untersagt wird, vielleicht weil ein Betriebsteam für das Management der Produktionsumgebung zuständig sein soll und nur alle Microservices gemeinsam deployen will. Bei der Technologieauswahl ist eine Einschränkung der technischen Möglichkeiten üblich. Rein technisch kann jeder Microservice in einer anderen Programmiersprache mit anderen Frameworks und Bibliotheken umgesetzt werden – und auch die Werkzeuge, beispielsweise für die Continuous Delivery Pipeline, können völlig unterschiedlich sein. Einige Projekte schränken daher die nutzbaren Technologien durch Regeln ein. Ein einheitlicher Technologiestack hat zweifellos Vorteile: Der Wissensaustausch zwischen den Teams ist einfacher und Menschen können einfacher zwischen den Teams wechseln. Auf der anderen Seite sind Teams dann nicht dazu in der Lage, die beste Technologie für die jeweilige Herausforderung zu nutzen.

Hier gibt es einen Widerspruch: Die bessere Austauschbarkeit von Wissen und Menschen kommt den Teams zugute – warum sollten sie sich gegen einen einheitlichen Stack entscheiden? Jedes Team kann Lösungen beispielsweise für die Continuous Delivery Pipeline einfach übernehmen und muss sich nicht selbst damit beschäftigen.

Am Ende ist ein wesentlicher Einflussfaktor dafür, wie viel man vorschreibt, Vertrauen: Wenn man die Entwickler:innen als verantwortungslose Spielkinder wahrnimmt, wird man ihnen mehr vorschreiben. Wenn man die Entwickler:innen als fähig und zuverlässig sieht, wird man ihnen mehr Freiheiten lassen. Übrigens kann es sein, dass Menschen mehr Verantwortung übernehmen, wenn man ihnen mehr Entscheidungskompetenzen gibt. Ein Mittel gegen als Spielkinder wahrgenommene Entwickler:innen kann also sein, ihnen mehr Freiheiten zu geben, damit sie daraufhin mehr Verantwortung übernehmen müssen.

Am Ende geht es hier also um organisatorische Themen und den menschlichen Faktor. Microservices können technische Freiheiten nur ermöglichen. Ob die Freiheiten wirklich umgesetzt werden, ist eine Frage, die organisatorisch entschieden werden muss.

Solche Freiheiten machen Teams autonomer, weil sie dann technische Entscheidungen unabhängig von anderen Teams oder Personen fällen können. Autonome Teams müssen weniger mit anderen kommunizieren und sollten daher produktiver sein – auch die Jobzufriedenheit wird dann höher sein. Agilität setzt auch auf autonome Teams. Da Agilität mittlerweile das typische Vorgehen in Projekten darstellt, müssten also die meisten Projekte in diese Richtung arbeiten. Dennoch setzen de facto viele Organisationen aber gerade nicht auf autonome Teams, auch wenn es Lippenbekenntnisse zu diesem Ideal gibt [9].

DIE KUNST DER SOTWARE-ARCHITEKTUR

Architecture & Design-Track entdecken

 

Fazit

Microservices sind nicht die endgültige Lösung für alle Architekturprobleme – genauso wenig wie alle anderen Architekturansätze auch. Dennoch haben sie eine wichtige Diskussion in Gang gesetzt:

  • Microservices sind nur eine weitere Möglichkeit, Systeme aufzuteilen. Die Aufteilung und damit die Architektur werden nicht automatisch besser, nur weil man beispielsweise statt Java Packages nun Microservices nutzt.
  • Daher rücken Strukturierung und Modularisierung stärker in den Fokus. Mit Ansätzen wie Domain-Driven Design wird die Orientierung an der Domäne wieder wichtiger. Diese Themen sind fundamental für Softwarearchitektur, aber auch für den Erfolg von Projekten, sodass sie nun zurecht wichtiger werden.
  • Technische Vorteile wie getrennte Skalierung, getrenntes Deployment, mehr Resilience oder unabhängige Technologieentscheidungen sprechen nach wie vor für Microservices – und sind in einigen Fällen alternativlos.
  • Schließlich wirft die möglicherweise größere technische Autonomie die Frage auf, wie viel Autonomie man den Teams wirklich zugestehen möchte.

Macht man also noch Microservices? Hoffentlich ja und hoffentlich nur da, wo es sinnvoll ist – aber das gilt für jeden Architekturansatz.


Links & Literatur

[1] Software Architektur im Stream zu Big Ball of Mud: https://software-architektur.tv/2023/03/31/folge159.html

[2] Software Architektur im Stream Folgen zu Architektur-Management-Werkzeugen: https://software-architektur.tv/tags.html#Architecture%20Management

[3] Software Architektur im Stream: „Peter Gafert zu ArchUnit“: https://software-architektur.tv/2021/04/09/folge55.html

[4] https://spring.io/projects/spring-modulith

[5] Software Architektur im Stream: „Taktisches Domain-Driven Design mit Java und jMolecules mit Oliver Drotbohm“: https://software-architektur.tv/2024/05/31/episode219.html

[6] Software Architektur im Stream: „Warum Continuous Delivery – Die DevOps Studie“: https://software-architektur.tv/2020/08/14/folge012.html

[7] https://martinfowler.com/articles/consumerDrivenContracts.html

[8] https://pact.io

[9] Software Architektur im Stream: „Autonome Teams – Wollen wir das wirklich?“: https://software-architektur.tv/2025/01/17/folge247.html

The post Microservices vs. Monolithen: Architektur-Strategien nach dem Hype appeared first on JAX.

]]>
Exactly Once in verteilten Systemen: Realität oder Utopie? https://jax.de/blog/exactly-once-idempotenz-java/ Mon, 10 Jun 2024 13:24:45 +0000 https://jax.de/?p=89825 Sind verteilte Systeme im Einsatz, wie es beispielsweise bei Microservices der Fall ist, ist eine verteilte Datenverarbeitung – häufig asynchron über Message Queues – an der Tagesordnung. Werden Nachrichten ausgetauscht, sollen diese häufig genau einmal verarbeitet werden – gar nicht so einfach, wie sich herausstellt.

The post Exactly Once in verteilten Systemen: Realität oder Utopie? appeared first on JAX.

]]>
Bei verteilten Systemen wird eine asynchrone Kommunikation häufig über einen Message Broker abgedeckt. Dadurch soll eine Entkopplung zwischen zwei Diensten erreicht werden, die unter Umständen separat skaliert werden können. Eine Kommunikation über einen Message Broker ist inhärent immer mindestens zweigeteilt, nämlich in Producer und Consumer.

Ein Producer erstellt dabei Nachrichten, wie in Abbildung 1 gezeigt, während ein Consumer sie verarbeitet.

Abb.1: Einfacher Nachrichtenaustausch

Der Message Broker ist häufig ein zustandsbehaftetes System – eine Art Datenbank – und vermittelt Nachrichten zwischen Producer und Consumer. Als zustandsbehaftetes System hat ein Message Broker die Aufgabe, Nachrichten vorzuhalten und abrufbar zu machen. Ein Producer schreibt also Nachrichten in den Broker, während ein Consumer sie zu einer beliebigen Zeit lesen kann. Exactly once, also einmaliges Ausliefern, bedeutet, dass der Producer genau eine Nachricht produziert und der Consumer diese genau einmal verarbeitet. Also ganz einfach, oder?

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Es kann so einfach sein

Da eine Kommunikation zwischen den Systemen über eine Netzwerkebene stattfindet, ist nicht gewährleistet, dass die Systeme über den gleichen Wissensstand verfügen. Es muss also einen Rückkanal geben, der einzelne Operationen bestätigt, um einen Zustand zu teilen. Nur so wird sichergestellt, dass Nachrichten, die erstellt wurden, auch korrekt angekommen und verarbeitet worden sind. Aktualisiert sieht der Fluss also eher so aus, wie es Abbildung 2 zeigt.

Abb. 2: Durch Bestätigungen wird der Zustand zwischen Systemen geteilt

Erstellt ein Producer eine Nachricht, die vom Consumer gelesen werden soll, benötigt dieser eine Bestätigung (Abb. 2, Schritt 2). Nur dadurch weiß der Producer, dass die Nachricht korrekt im Broker persistiert vorliegt und er sie nicht erneut übertragen muss.

Der Consumer wiederum liest die Nachricht und verarbeitet sie. Ist die Verarbeitung fehlerfrei abgeschlossen, bestätigt der Consumer das. Der Broker muss die Nachricht deshalb nicht noch einmal ausliefern.

Immer Ärger mit der Kommunikation

Bei einer verteilten Kommunikation über ein Netzwerk kann es leider immer passieren, dass diverse Kommunikationskanäle abbrechen oder Fehler entstehen – und zwar an vielen Stellen: beim Erstellen, vor dem Konsumieren und danach. Genau diese Eigenschaft macht es so schwer oder gar unmöglich, eine Exactly-once-Semantik zu erreichen.

Angenommen, ein Producer produziert eine Nachricht (Abb. 3). Um sicherzustellen, dass diese auch vom Broker gespeichert wurde, wartet der Producer auf eine Bestätigung. Bleibt sie aus, ist nicht garantiert, dass der Broker diese Nachricht wirklich ausliefern wird.

Abb. 3: Der Producer erhält keine Bestätigung und sendet die Nachricht erneut

Der Producer muss diese Nachricht folglich erneut ausliefern. Genau das ist in Schritt 2 der Abbildung 3 auch geschehen, weshalb der Producer in Schritt 3 eine weitere Nachricht mit demselben Inhalt sendet. Da jetzt zwei Nachrichten vorliegen, verarbeitet der Consumer in den Schritt 4 und 5 beide Nachrichten – wohl eher nicht „exactly once“. Die Nachricht wird durch den Retry-Mechanismus „at least once“ – mindestens einmal, nicht genau einmal – übertragen. Denn wie im Bild zu erkennen ist, überträgt der Producer dieselbe Nachricht zweimal, um sicherzustellen, dass sie mindestens einmal vom Broker bestätigt wurde. Nur so ist sichergestellt, dass die Nachricht nicht verloren geht.

ALL ABOUT MICROSERVICES

Microservices-Track entdecken

 

Natürlich kann die Bestätigung auch ignoriert werden. Schritt 2 kann also ausbleiben. Ein Retry-System würde folglich fehlen. Der Producer überträgt also eine Nachricht, ohne auf eine Bestätigung des Brokers zu warten. Kann der Broker die Nachricht selbst nicht verarbeiten oder wegspeichern, hat er keine Möglichkeit, das Fehlschlagen oder eine erfolgreiche Operation zu quittieren. Die Nachricht würde „at most once“ – maximal einmal oder eben keinmal – übertragen werden. Exactly once ist also grundsätzlich ein Problem verteilter Anwendungen, die mittels Bestätigungen funktionieren.

Leider ist das noch nicht das Ende der Fahnenstange, wenn die Nachricht Ende zu Ende, also vom Producer bis zum Consumer, betrachtet wird. Denn es existiert in einem solchen System zusätzlich ein Consumer, der die Nachrichten wiederum einmalig verarbeiten muss. Selbst wenn garantiert wird, dass der Producer eine Nachricht einmalig erzeugt, ist ein einmaliges Verarbeiten nicht garantiert.

Abb. 4: Ein Consumer verarbeitet die Nachricht und versucht diese danach zu bestätigen

Es kann passieren, dass der Consumer wie in Abbildung 4 gezeigt die Nachricht in Schritt 3 liest und in Schritt 4 korrekt verarbeitet. In Schritt 5 geht die Bestätigung verloren. Das führt dazu, dass die Nachricht mehrmals, aber mindestens einmal – at least once – verarbeitet wird.

Abb. 5: Ein Consumer bestätigt die Nachricht vor dem Verarbeiten

Es ist natürlich umgekehrt auch möglich, die Nachricht vor dem Verarbeiten zu bestätigen. Der Consumer lädt also die Nachricht und bestätigt sie direkt. Erst dann wird in Schritt 5 von Abbildung 5 die Bearbeitung der Nachricht erfolgen. Schlägt jetzt die Bearbeitung fehl, ist die Nachricht in Schritt 4 bereits bestätigt worden und wird nicht erneut eingelesen. Die Nachricht wurde wieder maximal einmal oder keinmal – at most once – verarbeitet.

Wie also zu erkennen, ist es leicht, At-most-once- und At-least-onceSemantiken in den verschiedenen Konstellationen sowohl auf Producer- als auch auf der Consumer-Seite herzustellen. Exactly once ist aber aufgrund der verteilten Systematik ein schwieriges Problem – oder gar unmöglich?

SIE LIEBEN JAVA?

Den Core-Java-Track entdecken

 

Lösungen müssen her

Für eine Möglichkeit, eine Exactly-once-Semantik zu erreichen, muss die Verarbeitung der Nachrichten einer Applikation eine bestimmte Eigenschaft unterstützen: Idempotenz. Idempotenz bedeutet, dass eine Operation, egal wie oft sie verarbeitet wird, immer dasselbe Ergebnis zur Folge hat. Ein Beispiel dieses Prinzips könnte das Setzen einer Variablen im Programmcode sein. Hier gibt es etwa die Möglichkeit, dies über Setter oder eben relative Mutationen zu implementieren.

Zum Beispiel setAge oder incrementAge. Die Operation person.setAge(14); kann beliebig oft nacheinander ausgeführt werden, das Ergebnis bleibt immer dasselbe, nämlich 14. Hingegen wäre person.incrementAge(1) nicht idempotent. Wird diese Methode unterschiedlich oft hintereinander ausgeführt, gibt es verschiedene Ergebnisse, nämlich nach jeder Ausführung ein Jahr mehr. Genau diese Eigenschaft der Idempotenz ist der Schlüssel, um eine Exactly-once-Semantik zu etablieren.

Angewandt auf die Systeme von zuvor bedeutet das, dass eine At-least-once-Semantik mit der Eigenschaft der Idempotenz zu einer Exactly-once-Verarbeitung führen kann. Wie eine At-least-once-Semantik umgesetzt werden kann, zeigt das zuvor beschriebene Bestätigungssystem. Was fehlt, ist also ein System von Idempotenz in der Verarbeitung. Aber wie kann eine Verarbeitung von Nachrichten idempotent gemacht werden?

Um das zu erreichen, muss der Consumer die Möglichkeit haben, einen lokalen, synchronisierten Zustand zu erhalten. Um den Zustand einer Nachricht zu erhalten, muss diese eindeutig identifizierbar sein. Nur so werden das Aufsuchen und eine Deduplizierung der Nachricht ermöglicht.

Abb. 6: Eine idempotente Verarbeitung

Anders als zuvor speichert der Consumer mit jedem Aufruf in Schritt 4 der Abbildung 6 die Nachricht zunächst in einer lokalen Zustandshaltung. An dieser Stelle kann, sofern die Nachricht bereits lokal vorhanden ist, ein erneutes Speichern vernachlässigt werden. In Schritt 5 wird die Nachricht bestätigt. Schlägt die Bestätigung fehl und wird die Nachricht folglich erneut übertragen, ist das kein Problem, da in Schritt 4 das erneute Speichern der Nachricht verhindert werden kann. An dieser Stelle lebt also die Idempotenz. Beim Bearbeiten kann der Consumer nun selbst entscheiden, ob eine Verarbeitung notwendig ist, z. B. indem zu einer Nachricht ein Status eingeführt und dieser in Schritt 6 lokal abgefragt wird. Steht dieser bereits auf Processed, muss nichts getan werden. Umgekehrt muss eine verarbeitete Nachricht den Status korrekt aktualisieren.

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Fazit

Verteilte Systeme haben ein grundsätzliches Problem, eine Exactly-once-Semantik herzustellen. Es kann auf Infrastrukturebene entweder zwischen at least once oder at most once gewählt werden. Erst durch die Eigenschaft der Idempotenz kann auf dem Applikationslevel sichergestellt werden, dass Nachrichten genau einmal von Ende zu Ende verarbeitet werden.

Natürlich ist das nicht kostenlos. Es bedeutet, dass die Applikation selbst eine Verwaltung von Nachrichten übernehmen muss und deren Zustand verwaltet – wirklich exactly once ist das natürlich auch nicht, es kommt diesem durch die Eigenschaft der Idempotenz jedoch im Ergebnis sehr nahe.

The post Exactly Once in verteilten Systemen: Realität oder Utopie? appeared first on JAX.

]]>
Resiliente Kafka Consumer bauen https://jax.de/blog/kafka-fehlerbehandlung-guide/ Mon, 04 Mar 2024 08:55:02 +0000 https://jax.de/?p=89539 Asynchrone Kommunikation ist der Königsweg für den Datenaustausch zwischen lose gekoppelten Systemen. Laut einer Erhebung von 6sense [1] ist der Marktführer für den Austausch asynchroner Nachrichten Apache Kafka. Wir beschreiben in diesem Artikel unseren Weg als agiles Team zu einer einfachen Catch-all-Fehlerbehandlung für Nachrichten, die wir via Kafka empfangen. Und so viel sei vorab verraten: Am Ende unserer Reise finden wir sogar die richtige Strategie zur Selbstheilung.

The post Resiliente Kafka Consumer bauen appeared first on JAX.

]]>
Wir berichten hier über die Erfahrungen, die wir in einem gemeinsamen Projekt im öffentlichen Sektor gesammelt haben. Dort haben wir verschiedene Strategien für die Fehlerbehandlung bei der Verarbeitung asynchroner Nachrichten in unseren Consumern erarbeitet und verglichen. Neben der Minimierung unserer Betriebsaufwände mussten wir dabei wegen notorisch enger Zeitpläne auch unseren Entwicklungsaufwand minimieren. Deswegen haben wir zunächst nach einer Strategie gesucht, die in einem ersten Schritt als einzige „Catch all“-Strategie für alle Fehlerszenarien anwendbar ist: eine „Minimum Viable“-Fehlerstrategie.

Bei der Bewertung der Strategien haben wir die möglichen Fehlerszenarien nach zwei Dimensionen unterschieden, der Fehlerquelle und der Fehlerhäufigkeit.

Fehlerquelle:

  • Der Producer erzeugt Nachrichten, die nicht dem Schnittstellenvertrag entsprechen, entweder syntaktisch oder semantisch.
  • Der Consumer ist nicht in der Lage, alle Nachrichten, die dem Schnittstellenvertrag entsprechen, zu verarbeiten, z. B., wirft er unerwartete Ausnahmen.
  • Die Infrastruktur arbeitet nicht erwartungsgemäß, z. B. fällt die Verbindung des Consumers zu seiner Datenbank aus.

Fehlerhäufigkeit:

  • Der Fehler tritt nur selten und vereinzelt auf, z. B. als sporadischer Edge Case.
  • Der Fehler ist ein generelles und länger andauerndes Problem, das viele Nachrichten betrifft.

Bei der schlussendlichen Bewertung der Strategien haben sich die Vor- und Nachteile dann oft als unabhängig von Fehlerquelle oder Fehlerhäufigkeit herausgestellt.

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Kafka-Grundlagen

Um die verschiedenen Strategien der Fehlerbehandlung zu verstehen, ist es notwendig, die grundlegenden Konzepte von Kafka zu kennen. Diese erläutern wir hier kurz. Für Details verweisen wir z. B. auf den Artikel „Kafka 101: Massive Datenströme mit Apache Kafka“ [2].

Broker

Kafka bezeichnet sich als Event-Streaming-Plattform, die als Ersatz für traditionelle Message-Broker benutzt werden kann. Der Message-Broker dient dazu, asynchrone Nachrichten in Topics zu veröffentlichen. Nach einer konfigurierbaren Zeit, der Retention Time, löscht der Broker die Nachrichten.

Producer

Der Producer veröffentlicht Nachrichten mit einem Schlüssel in einem Kafka Topic. Der Broker teilt ein Topic in mehrere Partitionen auf und speichert eine Nachricht in genau einer Partition des Topics. Dabei berücksichtigt der Broker den Schlüssel der Nachricht und schreibt alle Nachrichten mit demselben Schlüssel auch in dieselbe Partition. Je Partition garantiert der Broker, dass die Reihenfolge der Nachrichten erhalten bleibt. Der Producer muss die Schlüssel der Nachrichten also so wählen, dass alle Nachrichten, deren Reihenfolge untereinander erhalten werden muss, denselben Schlüssel haben.

Consumer

Die an den Nachrichten interessierten Consumer registrieren sich für das Topic und können so die Nachrichten vom Broker lesen und verarbeiten. Dabei geben die Consumer die Consumer Group an, zu der sie gehören. Der Broker stellt dann sicher, dass jede Partition genau einem Consumer einer Consumer Group zugewiesen ist.

Problembeschreibung

In unserem Projekt nutzen wir Kafka (Kasten: „Kafka-Grundlagen“), um Änderungen an den Daten, die ein System verantwortet, als Events dieses Systems zu veröffentlichen. Im Domain-Driven Design spricht man hier von den Zustandsänderungen eines Aggregats [3]. Wir erzeugen und verarbeiten die Events im Publish-Subscribe-Modus. Da die Retention Time mit fünf Tagen als endlich vorgegeben ist, enthalten die Events nicht nur die geänderten Daten, sondern immer den kompletten neuen Zustand des Aggregats.

Bezüglich der Performance haben wir recht entspannte Anforderungen: Aus fachlicher Sicht ist es ausreichend, wenn die Events vor Ablauf der Retention Time verarbeitet werden. Dabei ist es jedoch wichtig, dass die Reihenfolge der Events mit demselben Schlüssel erhalten bleibt und kein Event übersprungen oder anderweitig nicht vollständig verarbeitet wird. Events mit demselben Schlüssel nennen wir abhängig.

Nach Mathias Verraes [4] gibt es zwei schwierige Probleme in verteilten Systemen. Die Einhaltung der Reihenfolge ist das erste. Dieses Problem haben wir gelöst, indem die Nachrichten, deren Reihenfolge untereinander erhalten werden muss, denselben Schlüssel bekommen. Das zweite schwere Problem ist die Exactly-once Delivery. Wir können es umgehen, indem wir unsere Consumer idempotent implementieren.

Codebeispiele

Im Folgenden illustrieren wir jede betrachtete Strategie mit einem Codebeispiel. Diese nutzen die MicroProfiles Reactive Messaging und Fault Tolerance. Als Implementierung für beide MicroProfiles benutzen wir die für Quarkus üblichen Bibliotheken von SmallRye. Die Codebeispiele sind auch auf GitHub [5] zu finden.

Bei der Nutzung der Codebeispiele sind zwei Aspekte zu berücksichtigen: die Commit Strategy [6] und die Failure Strategy [7]. Als Commit Strategy haben wir latest gewählt, da beim Default throttled der Timeout throttled.unprocessed-record-max-age.ms [8] während länger dauernder Retries einer fehlerhaften Nachricht erreicht werden kann. Als Failure Strategy haben wir eine eigene Strategie implementiert, die davon ausgeht, dass der Code, der die Nachricht empfängt, die Fehler direkt behandelt und daher keine separate Failure Strategy mehr notwendig ist (Listing 1).

public class CustomKafkaFailureHandler implements KafkaFailureHandler {

  @ApplicationScoped
  @Identifier("custom")
  public static class Factory implements KafkaFailureHandler.Factory {

    @Override
    public KafkaFailureHandler create(
      KafkaConnectorIncomingConfiguration config,
      Vertx vertx,
      KafkaConsumer<?, ?> consumer,
      BiConsumer<Throwable, Boolean> reportFailure) {
        return new CustomKafkaFailureHandler();
    }
  }

  @Override
  public <K, V> Uni<Void> handle(IncomingKafkaRecord<K, V> record,
    Throwable reason, Metadata metadata) {
      return Uni.createFrom()
                .<Void>failure(reason)
                .emitOn(record::runOnMessageContext);
  }

Consumer herunterfahren

Der erste Halt auf unserer Reise führt uns zu einer äußerst einfach umzusetzenden Strategie: Consumer herunterfahren. Hierbei stoppt der Consumer die Verarbeitung und fährt den Prozess, in dem er läuft, herunter. Das macht er in dem Codebeispiel (Listing 2), indem er System.exit() aufruft. Alternativ könnte er z. B. in einer Kubernetes-Umgebung die Liveness-Probe [9] fehlschlagen lassen und so den Containermanager dazu veranlassen, den Prozess herunterzufahren. Diese Strategie, eingeschränkt auf den Kafka-Client des betroffenen Topics, bietet auch SmallRye Reactive Messaging an und nennt sie fail [10], [11].

@Incoming("aggregate-in")
public CompletionStage<Void> consume(Message<String> record) {
  LOGGER.trace("Aggregate received: {}", record.getPayload());
  try {
    controller.process(record.getPayload());
    return record.ack();
  } catch (Exception e) {
    LOGGER.error("Oops, something went terribly wrong with "
      + record.getPayload(), e);
    System.exit(1);
    return null; // never reached
  }
}

Um diese Strategie universell einsetzen zu können, ist es hilfreich, einen Containermanager zu haben, der den heruntergefahrenen Prozess neu startet. So bekommen wir den Retry in Form eines Restart Loops [7] geschenkt.

Der Weg zur Korrektur ist je nach Fehlerursache unterschiedlich: Bei einem Fehler im Producer sendet dieser die korrigierte Nachricht sowie alle abhängigen Nachrichten erneut. Der Consumer muss dann die Offsets der fehlerhaften Nachricht und aller ursprünglichen abhängigen Nachrichten überspringen.

Bei einem Fehler im Consumer ist lediglich der Bugfix zu deployen, dann läuft der Consumer einfach weiter. Bei einem Fehler in der Infrastruktur ist nichts zu tun, der Consumer läuft einfach weiter, sobald der Fehler behoben wurde.

Vorteile:

  • Die Strategie erhält die Reihenfolge.
  • Der manuelle Aufwand für die Fehlerbehebung ist beim Team des Consumers unabhängig von der Anzahl fehlerhafter Nachrichten.

Nachteile:

  • Der Microservice blockiert auch fehlerfreie Nachrichten.
  • Die Strategie führt in überwachten Umgebungen wie Kubernetes zu einer Restart-Schleife. Die wiederum führt je nach Timeout, Restart Backoff, Restart Cutoff und Start-up-Zeit dazu, dass der Broker die Partitionen auf die restlichen Consumer umverteilt, sodass am Ende jeder Consumer versuchen wird, die fehlerhafte Nachricht zu verarbeiten, und dann herunterfährt. Das wiederum verursacht vermutlich einen erhöhten Ressourcenverbrauch. Durch das Herunterfahren weiterer Consumer werden auch immer mehr Nachrichten in anderen Partitionen blockiert.
  • Die Strategie erfordert in nicht überwachten Umgebungen einen Mechanismus, um den Consumer nach der Fehlerbehebung wieder zu starten.
  • Die Strategie erfordert einen zusätzlichen Mechanismus zum Überspringen von fehlerhaften und ggf. auch abhängigen Nachrichten.
  • Bei Fehlern in der Nachricht müssen alle abhängigen Nachrichten vom Producer nochmals geschickt werden.

Dead Letter Queue

Die nächste Station unserer Reise ist ein Klassiker der Fehlerbehandlung von asynchronen Nachrichten: die Dead Letter Queue (DLQ). Im Zusammenhang mit Kafka wird diese Strategie oft auch als Dead Letter Topic bezeichnet, bei der Umsetzung mittels Datenbank auch als Dead Letter Table. Tritt ein Fehler beim Verarbeiten einer Nachricht im Consumer auf, sortiert der Consumer die betroffene Nachricht in die namensgebende Warteschlage für „tote“ Nachrichten aus. Anschließend macht er mit der Verarbeitung der nächsten Nachricht weiter (Listing 3). Als Alternative zu einer eigens implementierten Fehlerbehandlung bietet SmallRye die Dead Letter Queue auch als Failure Strategy an [10], [12].

@Incoming("aggregate-in")
public CompletionStage<Void> consume(Message<String> record) {
  LOGGER.trace("Aggregate received: {}", record.getPayload());
  try {
    controller.process(record.getPayload());
  } catch (Exception e) {
    LOGGER.error("Oops, something went terribly wrong with "
      + record.getPayload(), e);
    deadLetterEmitter.send(record);
  }
  return record.ack();
}

Der Weg zur Korrektur ist wieder von der Fehlerursache abhängig. Bei einer semantisch oder syntaktisch fehlerhaften Nachricht schickt der Producer eine korrigierte Nachricht. Die fehlerhafte Nachricht in der DLQ muss daraufhin übersprungen werden. Bei einem Fehler des Consumers muss der Bugfix deployt werden, anschließend müssen die Nachrichten der DLQ wieder eingelesen werden. Auch nach Behebung eines Infrastrukturproblems muss die DLQ wieder eingelesen werden.

Vorteile:

  • Blockiert nur Nachrichten, bei denen ein Fehler auftritt, d. h. keine Verzögerung nicht betroffener Nachrichten und maximaler Durchsatz.
  • Gute Skalierung auch bei vielen fehlerhaften Nachrichten.
  • Falls der Fehler nicht in der Nachricht lag, kann die DLQ nach dem Bugfix erneut eingelesen werden.
  • Der manuelle Aufwand für die Fehlerbehebung ist beim Team des Consumers unabhängig von der Anzahl fehlerhafter Nachrichten.
  • Bei einer Implementierung der DLQ mittels Datenbank kann man die Retention Time von Kafka umgehen, wodurch man mehr Zeit für die Fehlerbehebung hat.

Nachteile:

  • Erfordert einen separaten (u. U. manuellen) Mechanismus zum Einlesen der DLQ nach dem Bugfix.
  • Wirft die Reihenfolge der Nachrichten durcheinander.
  • DLQ muss eine Kapazität wie das ursprüngliche Topic haben, falls der Fehler einen Großteil der Nachrichten betrifft.
  • Die Strategie erfordert einen zusätzlichen Mechanismus zum Überspringen von fehlerhaften und ggf. auch abhängigen Nachrichten.
  • Bei Fehlern in der Nachricht müssen alle abhängigen Nachrichten vom Producer nochmals geschickt werden.

DLQ Advanced

Mit Blick auf unsere Problemstellung hat die einfache Dead Letter Queue ein entscheidendes Problem: Sie kann die Reihenfolge von Nachrichten nicht erhalten. Unsere Reise führt deswegen tiefer in die Welt der DLQs. Die Idee: Eine erweiterte Dead Letter Queue nimmt nicht nur Nachrichten auf, die bei der Verarbeitung Probleme machen, sondern auch alle von ihr abhängigen Nachrichten. Dazu muss lediglich der Schlüssel der fehlerhaften Nachricht gespeichert werden. Anschließend können alle abhängigen Nachrichten durch den gleichen Schlüssel identifiziert und ebenfalls in die DLQ geleitet werden (Listing 4).

@Incoming("aggregate-in")
public CompletionStage<Void> consume(KafkaRecord<String, String> record) {
  LOGGER.trace("Aggregate received: {}", record.getPayload());
  try {
    if(controller.shouldSkip(record.getKey())) {
      LOGGER.warn("Record skipped: " + record);
      deadLetterEmitter.send(record);
    } else {
      controller.process(record.getPayload());
    }
  } catch (Exception e) {
    LOGGER.error("Oops, something went terribly wrong with "
      + record.getPayload(), e);
    controller.addKeyToSkip(record.getKey());
    deadLetterEmitter.send(record);
  }
  return record.ack();
}

Der Weg zur Korrektur ist zunächst identisch mit dem der einfachen DLQ. Im Detail wird es jedoch kompliziert. Um die Reihenfolge beim Wiedereinlesen der DLQ weiterhin zu erhalten, muss ihr Einlesen mit dem Einlesen des regulären Topics koordiniert werden. Benutzt man ein Kafka Topic als DLQ, stellen sich weitere Probleme, wie die Verteilung von Schlüsseln in zwei Topics auf unterschiedliche Consumer und die Vermischung von korrigierten und nicht korrigierten Nachrichten in der DLQ. Die Realisierung als Dead Letter Table in der Datenbank löst manche dieser Probleme, verursacht aber auch neue. Wegen dieser steigenden Komplexität haben wir zu diesem Zeitpunkt ein Zwischenfazit gezogen, um nach weiteren Alternativen zu suchen.

Vorteile:

  • Die Strategie erhält die Reihenfolge.
  • Blockiert nur fehlerhafte und davon abhängige Nachrichten, d. h. keine Verzögerung nicht betroffener Nachrichten und maximaler Durchsatz.
  • Bei einer Implementierung der DLQ mittels Datenbank kann man die Retention Time von Kafka umgehen, womit man mehr Zeit für die Fehlerbehebung hat.

Nachteile:

  • Erfordert einen zusätzlichen Mechanismus zum Überspringen von fehlerhaften und ggf. auch abhängigen Nachrichten.
  • Erfordert komplexen (u. U. manuellen) Mechanismus zum Einlesen und Synchronisieren der DLQ nach dem Bugfix mit dem ursprünglichen Topic.
  • Die DLQ muss eine Kapazität wie das ursprüngliche Topic haben, falls der Fehler einen Großteil der Nachrichten betrifft.
  • Der manuelle Aufwand für die Fehlerbehebung ist beim Team des Consumers unabhängig von der Anzahl fehlerhafter Nachrichten.

Pausieren und Wiederholen

Die Dead Letter Queue ist eine naheliegende Lösung, wenn man von Fehlern ausgeht, die einzelne Nachrichten betreffen. Je mehr Nachrichten betroffen sind – egal, ob durch die Reihenfolge oder die gemeinsame Fehlerursache –, desto komplexer und aufwendiger wird die Lösung. Man kann sich dem Problem einer allgemeinen Fehlerbehandlung aber auch von einem anderen Szenario her nähern: dem Ausfall der Infrastruktur. Dabei spielt es keine Rolle, ob dieser Fehler den Ausfall einer notwendigen Datenbank, des Kafka-Clusters oder der Netzwerkverbindung betrifft. Entscheidend ist: Der Fehler betrifft vermutlich alle Nachrichten. Er lässt sich also nicht durch eine Quarantäne einzelner Nachrichten begrenzen. Die klassische Lösung für diesen Fall kennt jeder von automatischen Telefonansagen: „Bitte versuchen Sie es später noch einmal!“

Der erste Teil dieser Taktik besteht im Pausieren der Arbeit. Beim kompletten Herunterfahren des Consumers provozieren wir jedoch ein kostspieliges Rebalancing durch den Broker (siehe oben). Man kann aber auch das Konsumieren pausieren, solange man nicht den gesamten Consumer anhält. Das verhindert, dass der Broker die Pause als Ausfall des Consumers wertet und ein Rebalancing einleitet.

Der zweite Teil der Taktik ist das Wiederholen (engl. Retry) der Aktion, bei der ein Fehler aufgetreten ist. In unserem Fall bedeutet das auf oberster Abstraktionsebene immer das Konsumieren einer Nachricht. Wenn man außerdem die fehlerhafte Nachricht nicht committet, wird sie der Consumer nach der Pause einfach erneut einlesen und versuchen, sie zu verarbeiten.

Listing 5 zeigt, wie man beide Schritte mit der Retry-Annotation des MicroProfile Fault Tolerance umsetzt. Hiermit pausiert der Consumer für alle Partitionen, die ihm der Broker zugewiesen hat. Alternativ kann man dem Broker auch das Pausieren [13] einer einzelnen Partition direkt mitteilen und später die Verarbeitung mittels resume [14] wieder aufnehmen.

@Incoming("aggregate-in")
@Retry(delay = 2000L, jitter = 400L, maxRetries = -1, maxDuration = 0)
@ExponentialBackoff(maxDelay = 2, maxDelayUnit = ChronoUnit.HOURS)
public CompletionStage<Void> consume(Message<String> record) {
  LOGGER.trace("Aggregate received: {}", record.getPayload());
  try {
    controller.process(record.getPayload());
    return record.ack();
  } catch (Exception e) {
    LOGGER.error("Oops, something went terribly wrong with "
      + record.getPayload(), e);
    return record.nack€;
  }
}

Der Weg zur Korrektur ist wie immer abhängig von der tatsächlichen Fehlerursache. Da Infrastrukturprobleme meist externe Probleme sind, muss das Entwicklerteam bei der Lösung der Fehlerursache oft gar nicht aktiv werden. Tritt ein Infrastrukturproblem auf, können alle Consumer in einer Consumer Group unabhängig voneinander die Arbeit einstellen und in den Wiederholungsmodus wechseln. Sobald das Problem gelöst ist, sorgt das automatische Wiederholen für den Wiederanlauf aller Consumer.

Falls der Fehler beim Producer liegt, muss dieser wie bei allen anderen Strategien auch eine korrigierte Nachricht sowie eventuell weitere abhängige Nachrichten erneut schicken. Der Consumer muss alle vom Fehler betroffenen Nachrichten konsumieren und ignorieren. Bis dahin werden je nach gewählter Umsetzungsvariante entweder alle Nachrichten blockiert, die in der Partition mit der fehlerhaften Nachricht landen oder die in den Partitionen landen, die dem Consumer zugewiesen sind. Für abhängige Nachrichten wird die Partition quasi zu einer erweiterten Dead Letter Queue, die beim Wiedereinlesen allerdings keine Synchronisation benötigt. Ob und wie viele unabhängige Nachrichten vom Pausieren betroffen sind, hängt vom Verhältnis der Anzahl der Kafka-Schlüssel zur Anzahl der Partitionen und ggf. auch von der Anzahl der Consumer ab.

Falls der Fehler im Consumer liegt, blockieren die Partitionen, die von dem Fehler aktuell betroffen sind. Wie stark das Pausieren den Durchsatz bremst, hängt von der Schwere des Fehlers, möglichen Zusammenhängen mit den Kafka-Schlüsseln sowie vom Zufall ab. Sobald ein Bugfix deployt wurde, laufen die pausierten Partitionen durch das automatische Wiederholen ohne operativen Eingriff selbstständig wieder an.

Vorteile:

  • Pausiert nur betroffene Partitionen eines Consumers bzw. nur einen Consumer. Die Granularität hängt von der Anzahl der Consumer und der Anzahl der Partitionen ab.
  • Die Umsetzungsvariante mit der Retry-Annotation ist einfacher als das Pausieren einzelner Partitionen.
  • Verhindert Ressourcenverschwendung durch unnötiges Rebalancing.
  • Bei notwendigem Rebalancing (z. B. Consumer stürzt ab, neue Consumer kommen zwecks Skalierung hinzu) wird der neue Consumer automatisch die fehlerhafte Partition wieder pausieren.
  • Das System macht automatisch weiter, nachdem das Problem behoben ist, z. B. durch Deployment eines Bugfixes. Auch bei sporadischen Problemen, die sich selbst nach kurzer Zeit lösen, macht das System automatisch weiter. Das Team profitiert dabei vom Prinzip der Selbstheilung.
  • Der manuelle Aufwand für die Fehlerbehebung ist beim Team des Consumers unabhängig von der Anzahl fehlerhafter Nachrichten.

Nachteile:

  • Falls es mehr Kafka-Schlüssel als Partitionen gibt (was der Normalfall sein dürfte), werden auch Schlüssel angehalten, die sich zufällig in derselben Partition befinden, aber nicht betroffen sind.
  • Bei der Umsetzungsvariante mit der Retry-Annotation werden auch Nachrichten in Partitionen angehalten, die nicht die fehlerhafte Nachricht enthalten, falls es weniger Consumer als Partitionen gibt.
  • Erfordert zusätzlichen Mechanismus zum Überspringen von fehlerhaften und ggf. auch abhängigen Nachrichten.
  • Bei Fehlern in der Nachricht müssen alle abhängigen Nachrichten vom Producer nochmals geschickt werden.

Einfach loggen

Zunächst dachten wir nach dem produktiven Einsatz unseres Systems einige Wochen lang, wir könnten es uns auf der Insel „Pausieren und Wiederholen“ gemütlich machen. Dann mussten wir jedoch einen Abstecher zu einer weiteren Strategie unternehmen, die wir ursprünglich als absurd angesehen und daher direkt aussortiert hatten. Mittlerweile haben wir sie jedoch für spezielle Fälle zu schätzen gelernt. Diese Fälle zeichnen sich dadurch aus, dass ein Sachbearbeiter im ursprünglichen System Daten manuell korrigieren muss. Das verringerte den Durchsatz bei der Strategie „Pausieren und Wiederholen“ so deutlich, dass wir die Nachricht nun mittels einfachem Loggen behandeln. Dabei haben wir sichergestellt, dass nur einzelne Nachrichten so behandelt werden, und setzen diese Strategie somit nicht als Catch-all-Strategie ein.

Diese Strategie loggt die fehlerhafte Nachricht und bzw. oder ihre Metadaten, zählt gegebenenfalls eine Metrik hoch und verarbeitet dann die nächste Nachricht (Listing 6). Auch diese Strategie bietet SmallRye an und nennt sie ignore [10], [15].

@Incoming("aggregate-in")
public CompletionStage<Void> consume(Message<String> record) {
  LOGGER.trace("Aggregate received: {}", record.getPayload());
  try {
    controller.process(record.getPayload());
  } catch (Exception e) {
    LOGGER.error("Oops, something went terribly wrong with "
      + record.getPayload(), e);
  }
  return record.ack();
}

Der Weg zur Korrektur ist hier unabhängig von der Fehlerursache manuell: Die Logmeldung oder die erhöhte Metrik stößt einen manuellen Prozess an, der die Fehlerursache beheben und dann die geloggten und damit nicht verarbeiteten Nachrichten reproduzieren muss. Aufgrund des hohen manuellen Aufwands sollte diese Strategie nur für Fehlerursachen angewandt werden, die einzelne Nachrichten betreffen. Insbesondere scheidet sie damit für Infrastrukturprobleme aus.

In unserem Projekt setzen wir diese Strategie für Nachrichten ein, die der Producer fehlerhaft erstellt hat. Nachdem der Fehler im Producer behoben wurde, lässt der manuelle Prozess die Nachricht und alle davon abhängigen Nachrichten erneut erzeugen.

Vorteil:

  • Überspringt nur Nachrichten, bei denen ein Fehler auftritt, und vermeidet damit eine Verzögerung nicht betroffener Nachrichten. Die Strategie maximiert also den Durchsatz.

Nachteile:

  • Wirft die Reihenfolge der Nachrichten durcheinander.
  • Verliert Nachrichten, falls die Fehlerursache im Consumer oder in der Infrastruktur liegt.

Fazit

Unsere Reise durch die Untiefen der asynchronen Fehlerbehandlung zeigt, dass je nach konkreter Problemstellung verschiedene Optionen infrage kommen, die auf den ersten Blick absurd erscheinen. Ganz prinzipiell kann man die von uns gefundenen Optionen nach der Granularität des Eingriffs und der Komplexität ihrer Umsetzung sortieren (Abb. 1).

Abb. 1: Strategien im Überblick

Einfach loggen bedeutet de facto keinen Eingriff und ermöglicht so einen hohen Durchsatz. Zudem ist es bestechend simpel. Der Preis ist das Risiko des Verlusts der korrekten Reihenfolge und kompletter Nachrichten. Die klassische Dead Letter Queue verhindert den Datenverlust durch einen minimalen Eingriff, kann aber die Reihenfolge auch nicht sicherstellen. Sie ist dafür auch etwas komplexer, als nur zu loggen. Die erweiterte Dead Letter Queue greift exakt so viel ein wie notwendig, um Datenverlust zu verhindern und die Reihenfolge zu erhalten. Dafür muss man sich aber um eine Reihe von Detailproblemen kümmern. Diese Option ist deshalb die komplexeste. Bei Pausieren und Wiederholen werden u. U. auch unabhängige Nachrichten angehalten, allerdings selten das gesamte Topic. Datenverlust und Reihenfolge sind kein Thema. Die Komplexität ist moderat und beschränkt sich größtenteils auf das Wissen über die Konfiguration von Kafka selbst. Die einfachste Option, die Datenverlust vermeidet und die Reihenfolge einhält, ist das komplette Herunterfahren des Consumers. Das bedeutet aber in den meisten Fällen auch den niedrigsten Durchsatz während eines Fehlers.

Selbstverständlich kann man all diese Lösungen durch weitere kleinere Lösungen ergänzen. Auch wenn wir auf der Suche nach einer Catch-all-Strategie waren: Oft ist es besser, Probleme dort zu lösen, wo sie auftreten. Das heißt, dass z. B. eine Datenbankausnahme auch einfach das Wiederholen des Datenbankaufrufs auslösen kann, statt die Verarbeitung der Nachricht sofort abzubrechen. Für bestimmte fachliche Spezialfälle mögen sogar einfaches Loggen und eine manuelle Lösung durch den Fachbereich akzeptabel sein.

Für unser Projekt haben wir uns am Ende für Pausieren und Wiederholen entschieden. Die erweiterte Dead Letter Queue wurde uns zu komplex und wir waren uns nicht sicher, ob wir trotz dieser Komplexität nicht immer noch manuell in die Produktion würden eingreifen müssen. Pausieren und Wiederholen waren dagegen vergleichsweise simpel zu verstehen und dabei nicht zu restriktiv. Vor allem aber faszinierte uns, dass wir damit effektiv manuelle Eingriffe in der Produktion vermeiden konnten. Das automatische Wiederholen bedeutete letztlich den Beginn einer geradezu magischen neuen Eigenschaft unseres Systems: der Selbstheilung.


Links & Literatur

[1] https://6sense.com/tech/queueing-messaging-and-background-processing/apache-kafka-market-share

[2] https://entwickler.de/software-architektur/kafka-101/

[3] https://www.dddcommunity.org/library/vernon_2011/

[4] https://twitter.com/mathiasverraes/status/632260618599403520

[5] https://github.com/HilmarTuneke/kafka-fehlerbehandlung

[6] https://smallrye.io/smallrye-reactive-messaging/4.16.0/kafka/receiving-kafka-records/#acknowledgement

[7] https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy

[8] https://smallrye.io/smallrye-reactive-messaging/4.16.0/kafka/receiving-kafka-records/#configuration-reference

[9] https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/

[10] https://smallrye.io/smallrye-reactive-messaging/4.16.0/kafka/receiving-kafka-records/#failure-management

[11] https://quarkus.io/blog/kafka-failure-strategy/#the-fail-fast-strategy

[12] https://quarkus.io/blog/kafka-failure-strategy/#the-dead-letter-topic-strategy

[13] https://kafka.apache.org/37/javadoc/org/apache/kafka/clients/consumer/KafkaConsumer.html#pause(java.util.Collection)

[14] https://kafka.apache.org/37/javadoc/org/apache/kafka/clients/consumer/KafkaConsumer.html#resume(java.util.Collection)

[15] https://quarkus.io/blog/kafka-failure-strategy/#the-ignore-strategy

The post Resiliente Kafka Consumer bauen appeared first on JAX.

]]>
Und jetzt? Microservices nach dem Hype https://jax.de/blog/microservices-nach-dem-hype/ Tue, 09 Jun 2020 16:29:04 +0000 https://jax.de/?p=77928 Microservices haben jahrelang im Mittelpunkt des Interesses gestanden. Zeit, ein Fazit zu ziehen und aufzuzeigen, wie Microservices die Softwareentwicklung beeinflusst haben.

The post Und jetzt? Microservices nach dem Hype appeared first on JAX.

]]>
Natürlich sollten sich IT-Experten mit Hypes wie Microservices [1] [2] [3] [4] kritisch auseinandersetzen. Am Ende gibt es nur vorteilhafte oder weniger vorteilhafte Entscheidungen für ein bestimmtes Projekt. Die individuellen Vor- und Nachteile in dem jeweiligen Projekt sollten im Mittelpunkt stehen. Es ist schließlich kaum sinnvoll, coole Ansätze zu wählen, die dem Projekt nicht helfen. Genauso wenig sinnvoll ist es, uncoole Ansätze von vornherein auszuschließen, obwohl sie vielleicht ein konkretes Problem lösen.

Aber ein Hype entsteht nicht einfach so. Um die Vorteile besser zu verstehen, ist es sinnvoll, den Ursprung des Hypes nachzuvollziehen. Dazu zunächst ein Blick auf die Situation vor der Zeit der Microservices: Damals gab es nur Deployment-Monolithen. Einige dieser Monolithen hatten so große Mengen Code, dass allein das Kompilieren teilweise 10 oder 20 Minuten dauerte und nochmal so lange, bis die Anwendung gestartet war. Dazu kommen noch die Zeiten für Unit- und andere Tests. Und für die Tests muss die Anwendung möglicherweise mehrmals gestartet werden. Irgendwann ist dann eine produktive Arbeit kaum noch möglich, weil Programmierer so lange warten müssen, bis die Ergebnisse ihrer Arbeit sichtbar sind.

Wenn man den Code tatsächlich weiterentwickelt und testet, sind solche Zeiten kaum akzeptabel. Eine Änderung dauert einfach viel zu lange. Dazu kommen dann noch komplexe Integrationen mit anderen Systemen oder komplizierte Laufzeitumgebungen, wie beispielsweise Application Server. Sie gilt es, zu konfigurieren, was die Zyklen weiter verlängert und den Weg in die Produktion noch schwieriger macht.

Ich habe in meinen ersten Vorträgen über Microservices das Publikum gefragt, ob es Projekte mit zu großen Deployment-Einheiten kennen würde oder welche mit zu kleinen. Damals gab es scheinbar viele Projekte mit zu großen, aber praktisch kaum Projekte mit zu kleinen Deployment-Einheiten. Zumindest damals war eine Verkleinerung der Deployment-Einheiten sicher sinnvoll.

 

Der Ursprung

Eine gute Quelle zum Ursprung des Hypes und der Geschichte der Microservices ist Wikipedia [5]. Demnach gab es 2011 einen Softwarearchitekturworkshop, bei dem mehrere Teilnehmer einen neuen Architekturstil beschrieben. 2012 wurde dann im nächsten Workshop aus dieser Serie der Begriff „Microservice“ erfunden. Beteiligt waren dabei beispielsweise Adrian Cockcroft (Netflix), James Lewis (Thoughtworks) oder Fred George (Freelancer). Alle drei hatten bereits sehr viel Erfahrung und arbeiteten an wichtigen Projekten. Sie haben den Microservices-Ansatz sicher nicht gewählt, um einen neuen Hype zu erzeugen, sondern um reale Probleme zu lösen.

Die konkreten Probleme in ihren Projekten glichen sich, aber es gab auch Unterschiede: Während beispielsweise für Netflix eine Cloud-Strategie sehr wichtig war, ist das bei den anderen Projekten nicht so stark der Fall gewesen. Einige der Projekte müssen skalieren, aber teilweise in unterschiedlichen Bereichen: Skalierung der Teamgröße oder der Software auf mehrere Server.

Die Lösungen unterscheiden sich daher: Netflix setzt auf synchrone Kommunikation und Services, die so groß sind, dass ein Team mit der Entwicklung ausgelastet ist. Fred George hingegen empfiehlt asynchrone Kommunikation und Microservices, die höchstens einige hundert Zeilen groß sind und sehr einfach neu geschrieben werden können.

Es gab also nie das eine wahre Microservices-Konzept, sondern schon von Anfang an verschiedene Ansätze, die unterschiedliche Probleme lösen. Auch heute ist es noch so, dass der Microservices-Ansatz sehr unterschiedlich interpretiert und umgesetzt wird.

 

Module

Aber worum geht es bei Microservices überhaupt? Im Kern sind Microservices eine andere Art von Modulen und stehen damit in Konkurrenz zu Modularisierungsansätzen wie JARs, Maven-Projekten oder Java Namespaces. Eine Aufteilung wie in Abbildung 1 kann also als Microservices umgesetzt werden – oder mit einem anderen Modularisierungsansatz.

Abb. 1: Module einer Anwendung können Microservices sein – oder andere Arten von Modulen

 

Zwischen Microservices und anderen Optionen gibt es wesentliche Unterschiede:

  • In einem Deployment-Monolith kann es schnell passieren, dass man in neuem Code irgendeine vorhandene Klasse nutzt, die in irgendeinem Package abgelegt ist. Dadurch kann eine Abhängigkeit zwischen Packages entstehen, die eigentlich nicht gewünscht ist. Diese Abhängigkeit ist nicht beabsichtigt. Sie fällt vielleicht noch nicht einmal auf, weil sie im Code versteckt ist und Abhängigkeiten zwischen Packages nicht offensichtlich sind. Das führt dazu, dass ein Deployment-Monolith nach einiger Zeit eine Vielzahl von unerwünschten Abhängigkeiten hat. Natürlich kann z. B. ein Architekturmanagementwerkzeug das vermeiden. Wenn aber keine Vorkehrungen getroffen worden sind, ist das Abhängigkeitschaos in der Architektur unaufhaltsam. Sind die Module hingegen Microservices, kommunizieren sie untereinander durch eine explizite Schnittstelle z. B. per REST. Also muss man eine Abhängigkeit von einem Microservice zu einem anderen explizit einführen, indem man die Schnittstelle nutzt. Dadurch können sich Abhängigkeiten nicht einfach so einschleichen, weil man irgendeine Klasse aus Versehen nutzt.
  • Für den Betrieb sind die einzelnen Module sichtbar, weil sie getrennte Prozesse sind. Bei einem Deployment-Monolithen sind hingegen alle Module in einem einzigen Prozess untergebracht. Das bedeutet, dass neben Deployment auch andere Betriebsaspekte wie Metriken und Sicherheit sich an einzelnen Modulen orientieren können.

Weil Microservices die Frage nach einer sinnvollen Aufteilung eines Systems stellen, sind durch Microservices Module wieder in den Kern der Diskussion zurückgekehrt. Daraus ist die Idee des modularen Monolithen entstanden, denn ein Deployment-Monolith kann natürlich auch in Module aufgeteilt sein. Er sollte sogar modularisiert sein, schließlich wäre ein System ohne Module kaum zu warten. Wenn der Microservices-Hype dazu beigetragen hat, dass Module als grundlegendes Architekturkonzept wieder mehr beachtet werden, ist das schon viel wert. Und wenn es dann noch eine Abwägung zwischen verschiedenen Modularisierungsansätzen gibt, ist das super.

Damit ergibt sich eine wichtige Frage: Wenn so viele Deployment-Monolithen schlecht strukturiert sind – warum sollte dann der nächste Deployment-Monolith besser strukturiert sein? Es geht dabei nicht darum, Deployment-Monolithen generell als schlecht abzuqualifizieren. Schließlich gibt es gut strukturierte Deployment-Monolithen. Aber bei der Vielzahl schlecht strukturierter Deployment-Monolithen muss man die Frage stellen und auch beantworten – beispielsweise durch den Einsatz eines Architekturmanagementwerkzeugs. Microservices haben zumindest den Vorteil, dass die Aufteilung erzwungen wird.

 

Domain-driven Design

Natürlich stellt sich die Frage, wie eine vernünftige Aufteilung in Module erreicht werden kann. Merkmale einer guten Aufteilung wie lose Kopplung sind jedem bekannt – aber sie zu erreichen, ist gar nicht so einfach. Eine Möglichkeit ist DDD.

Domain-driven Design (DDD) [6] hat seine erste Blüte circa 2005 erlebt. Damals ist es als eine Anleitung zum Entwurf objektorientierter Systeme wahrgenommen worden. Klassen wurden Repositories, Services, Entitys oder Aggregates. DDD hat so beim feingranularen Entwurf der Systeme auf Klassenebene geholfen.

Dieser Bereich ist aber nur ein Teil von DDD, das taktische Design. Heutzutage steht das strategische Design viel mehr im Mittelpunkt. Dabei geht es um die Aufteilung von Systemen in Bounded Contexts. Ein Bounded Context hat ein eigenes Domänenmodell, das von den anderen Domänenmodellen getrennt ist. So könnte es ein Domänenmodell für die Lieferung von Bestellungen geben und ein anderes für die Bezahlung. Diese beiden Domänenmodelle sind völlig unterschiedlich: So geht es bei der Bezahlung um Bezahlmöglichkeiten, die Bonität eines Kunden oder die Preise und Steuersätze von Waren. Bei der Lieferung stehen Logistikdienstleister, die Lieferadresse des Kunden oder die Größe oder das Gewicht der Waren im Mittelpunkt. Am Beispiel der Ware wird klar, dass die Domänenmodelle zwar Domänenobjekte mit derselben Bezeichnung haben, aber unterschiedliche Facetten dieser Domänenobjekte modellieren – Preise und Steuersätze in dem einen Bounded Context, Größe und Gewicht in dem anderen.

Diese grobgranulare Modularisierung und Entkopplung ist vielleicht der wichtigste Trend, den Microservices in Gang gesetzt haben. Datenbanken mit Hunderten von Tabellen, die jede auch noch eine Vielzahl von Spalten haben, ist ein Hinweis, dass ein Domänenmodell viel zu komplex geworden ist und aufgeteilt werden muss. Da können Bounded Contexts helfen. Natürlich gilt das auch, wenn gar keine Microservices genutzt werden. Also haben auch in diesem Bereich Microservices eine Diskussion in Gang gesetzt, die über Microservices hinaus relevant ist.

 

Langfristige Architektur

Viele Systeme überleben länger als ursprünglich geplant. Systeme müssen daher so aufgebaut werden, dass sie auch langfristig wartbar und erweiterbar bleiben. Üblicherweise versuchen Teams daher, eine „saubere“ Architektur zu definieren und durchzusetzen. Sehr viele Projekte fangen mit solchen Überlegungen an. Die Anzahl von Projekten, die am Ende mit den Konzepten auch erfolgreich ein langfristig wartbares System erstellt haben, ist allerdings gering. Daher wäre es vielleicht sinnvoll, einen anderen Ansatz auszuprobieren.

Manchmal versuchen Architekten daher, Änderungsschwerpunkte zu identifizieren, die dann besonders flexibel umgesetzt werden. Aber eine Abschätzung der Änderungshäufigkeiten kann nur auf historischen Daten beruhen. Die Zukunft ist jedoch prinzipiell schwer vorhersagbar. Oft haben Architekturen daher genau an der falschen Stelle Flexibilitäten, die dann nur die Komplexität des Systems unnötig erhöhen. Und an den Stellen, die tatsächlich geändert werden, fehlt die Flexibilität dann. Am Ende ist das System sogar noch schwieriger zu ändern.

Mit Microservices kann ein anderer Ansatz umgesetzt werden: Die fachliche Aufteilung nach Bounded Contexts ist fundamental. Bezahlung und Lieferung von Waren wird immer ein Teil eines E-Commerce-Systems sein. Und wenn das nicht mehr der Fall sein sollte, ist die Umstellung der Software vermutlich noch das kleinste Problem. Fachlich kann diese Aufteilung also auch langfristig stabil bleiben. DDD zielt zwar gar nicht auf langfristige Stabilität, sondern auf eine fachlich korrekte Aufteilung. Aber genau das ist vielleicht der beste Weg, um langfristig eine gute fachliche Architektur zu erreichen.

Für Technologien muss es aber auch einen Weg geben, das System langfristig anpassbar zu halten. Schließlich wird jede Technologie, für die sich ein Projekt entscheidet, früher oder später veraltet sein. Und irgendwann gibt es keine Sicherheitsupdates mehr. Spätestens dann ist eine Migration auf eine neue Technologie zwingend. Das System sollte in kleine Einheiten aufgeteilt werden, die unabhängig voneinander auf eine neue Version einer Technologie oder eine neue Technologie migriert werden können. So können große und risikoreiche Technology-Updates vermieden werden.

In anderen Bereichen gibt es erfolgreiche Ansätze, um alte und neue Technologien miteinander zu kombinieren. Modulare Synthesizer bestehen aus Modulen, die kombiniert werden können, um Töne zu erzeugen. Ein Standard für die Module ist Eurorack [7]. Er definiert die Kommunikation zwischen den Modulen zum Beispiel zur Kontrolle der Module und für das Timing sowie natürlich für das Audiosignal. Außerdem definiert der Standard Aspekte für den Betrieb wie die Größe der Module und die Versorgungsspannungen. Den Standard gibt es seit 1996. Mittlerweile gibt es 5 000 teilweise radikal unterschiedliche Module, die alle miteinander kombiniert werden können. Natürlich werden in den Modulen teilweise Technologien eingesetzt, die es 1996 – vor fast 25 Jahren – noch nicht gab. So können also moderne und alte Technologien problemlos kombiniert werden.

Microservices erlauben einen ähnlichen Ansatz: Ein Microservices-System muss lediglich die Kommunikation standardisieren – beispielsweise mit REST oder mit einem Messagingsystem. Außerdem muss der Betrieb durch entsprechende Regeln sichergestellt werden: So können die Microservices als Docker-Container umgesetzt werden und standardisierte Schnittstellen für Metriken oder Logging haben.

So ermöglicht ein Microservices-System heterogene Technology-Stacks, denn in den Docker-Containern können beliebige Technologien genutzt werden. Das unterstützt das Update auf eine neue Technologie: Das Update kann schrittweise für jeden einzelnen Microservice erfolgen. Das Vorgehen verringert das Risiko: Wenn es Schwierigkeiten mit der neuen Technologie gibt, tritt sie nur bei den bereits aktualisierten Microservices auf und man kann zunächst mit einem einzigen Microservice beginnen. Microservices, bei denen ein Update gar nicht lohnt, müssen nicht auf die aktuelle Technologie migriert werden, was Aufwand spart.

Eine solche schrittweise Migration kann nur funktionieren, wenn heterogene Technologiestacks möglich sind. Das ist aber eigentlich nur bei Microservices der Fall, sodass die bessere Unterstützung von Technologieupdates ein entscheidender Unterschied ist.

 

Continuous Delivery

Die kontinuierliche Auslieferung von Software (Continuous Delivery) [8] hat offensichtliche Vorteile. So ist eine Änderung viel schneller in Produktion, wenn die Software regelmäßig ausgeliefert wird. Daher verbessert sich die Time to Market. Mittlerweile belegt aber eine Studie [9], dass es noch viele weitere Vorteile gibt. So können Teams, die oft deployen, den Ausfall eines Service schneller beheben. Da ein Deployment auch einen erneuten Aufbau eines Service darstellt, ist dieses Ergebnis nicht so überraschend. Ebenso wenig überraschend ist es, dass Deployments weniger häufig fehlschlagen, wenn man oft deployt. Schließlich sind die deployten Änderungen nicht nur kleiner, sondern die Teams haben auch mehr Übung beim Deployment.

Aber Continuous Delivery hat weitere Vorteile: Teams, die oft deployen, investieren 50 Prozent ihrer Zeit in die Arbeit an neuen Dingen, während das bei anderen Teams nur 30 Prozent sind. Dafür arbeiten sie weniger an Sicherheitsproblemen, Fehlern oder der Unterstützung von Endnutzern. Continuous Delivery verbessert also die Produktivität der Teams. Die Studie belegt sogar, dass Unternehmen mit Continuous Delivery erfolgreicher am Markt sind und weniger mit Burn-out zu kämpfen haben. Der Grund für diese Vorteile ist vermutlich, dass durch die Erhöhung der Deployment-Geschwindigkeit die aktuellen Probleme im Softwareentwicklungsprozess offensichtlich werden und dann wegoptimiert werden können. Außerdem muss bei einem häufigen Deployment klar sein, unter welchen Bedingungen Änderungen in Produktion kommen. Zusätzlich muss der Prozess einfach ausführbar und zuverlässig sein. Eine solche Umgebung ist sicher für Mitarbeiter ebenfalls angenehmer.

Die Studie zeigt auch, wie oft Teams deployen sollten: Low Performer deployen zwischen einmal im Monat und einmal alle sechs Monate, während Elite Performer mehrmals pro Tag deployen.

Ein Projekt, das einmal im Quartal deployt, sollte also so umgestellt werden, dass es mehrfach täglich deployt, um so die vielen positiven Effekte von Continuous Delivery auszunutzen. In einem beispielhaften Szenario folgten auf die Entwicklungsphase zehn Wochen lang Tests und dann ein Release über das Wochenende. Wenn man in diesem Szenario die Tests beispielsweise durch Automatisierung um den Faktor 100 beschleunigt, dauern sie noch vier Stunden. Das Deployment würde nach einer Beschleunigung um den Faktor vier noch zwei Stunden dauern. Dann wäre man bei sechs Stunden für ein Deployment. Um mehrmals pro Tag zu deployen, müsste man noch einen Faktor von zwei oder drei erreichen. Abbildung 2 zeigt die benötigte Zeit in Relation.

Abb. 2: Die Releasegeschwindigkeit muss massiv erhöht werden

 

Wegen der erforderlichen extremen Beschleunigungsfaktoren ist es kaum vorstellbar, dass eine Strategie, die nur auf die Optimierung und Automatisierung der vorhandenen Prozesse setzt, zum gewünschten Erfolg führt. Tatsächlich zeigt die schon zitierte Studie, dass Verbesserungen in unterschiedlichen Bereichen notwendig sind, um die Geschwindigkeit der Deployments zu erhöhen. Zu den Maßnahmen zählt auch eine entkoppelte Architektur. Ein Ansatz dafür sind Microservices. Ohne eine Aufteilung in getrennt deploybare Module scheint das Ziel von mehreren Deployments pro Tag kaum erreichbar, obwohl es derart viele positive Auswirkungen hat. Durch Automatisierung und Optimierung allein kann man Tests kaum so stark beschleunigen. Wenn man aber die Architektur so ändert, dass ein Teil des Systems getrennt getestet und deployt werden kann, wird das Problem einfacher lösbar.

Dieses Szenario zeigt außerdem, dass die komplette Deployment-Pipeline der Microservices unabhängig sein muss – also auch und insbesondere die Tests. Ersetzt man in dem Szenario die Architektur durch Microservices, bleibt aber beim monolithischen Testansatz, ändert sich nichts.

Auf der einen Seite zeigt Continuous Delivery, dass Microservices in Isolation nicht alle Probleme lösen, sondern viele Optimierungen beispielsweise durch Automatisierungen möglich sind. Auf der anderen Seite sind hohe Deployment-Geschwindigkeiten bei komplexen Systemen wohl nur erreichbar, wenn man das System in getrennt deploybare Einheiten wie Microservices aufteilt.

 

Organisation

Microservices sind zwar ein Architekturansatz, aber sie können auch Auswirkungen auf die Organisation haben. Klassische Organisationen bilden oft Teams nach technischer Qualifikation, also beispielsweise ein UI-Team und ein Backend-Team. Das Gesetz von Conway besagt allerdings, dass die Architektur des Systems die Kommunikationsstrukturen kopiert. Demnach würde diese Aufteilung zur Folge haben, dass es eine UI-Komponente und eine Backend-Komponente gibt. Das passt nicht zu der fachlichen Aufteilung, die DDD predigt. Daher gibt es in der Microservices-Welt das Inverse Conway Maneuver (etwa: umgekehrtes Conway-Manöver). Fachliche Komponenten wie ein Bounded Context werden dann einem Team zugewiesen. So folgt die Organisation in Teams der angestrebten fachlichen Architektur. Natürlich wäre ein solches Vorgehen auch ohne Microservices denkbar. Aber durch Microservices kommt zu der fachlichen Unabhängigkeit der Bounded Contexts eine technische Unabhängigkeit hinzu: Jeder Microservice kann getrennt von den anderen Microservices deployt werden und andere Technologien nutzen (Abb. 3).

Abb. 3: Microservices ergänzen die fachliche Unabhängigkeit von Bounded Context mit technischer Unabhängigkeit

 

Es gibt neben Microservices und dem Inverse Conway Maneuver mehrere Ansätze, die im Kern alle dasselbe aussagen:

  • DDD fordert, dass ein Bounded Context von einem Team entwickelt werden soll. Strategic Design beschreibt nicht nur, wie Bounded Contexts zusammenhängen, sondern auch mögliche Teambeziehungen.
  • Agilität fordert crossfunktionale Teams. Die Teams sollen möglichst viele Fähigkeiten haben, um so möglichst unabhängig voneinander arbeiten zu können. Außerdem sollen sie sich selbst organisieren, also möglichst viele Entscheidungen selbst treffen.
  • Das auf der DevOps-Studie basierende Buch „Accelerate“ [10] empfiehlt eine lose gekoppelte Architektur, um die Teams zu skalieren. Die Teams sollten die Werkzeuge wählen. Der Fokus muss auf Entwicklern und Ergebnissen liegen, nicht auf Technologien oder Werkzeugen. Microservices können das unterstützen, weil sie lose gekoppelt sind und in jedem Microservice andere Technologien genutzt werden können. Andere Ansätze wären aber auch denkbar.
    Microservices haben also die Idee, dass Organisation und Architektur zusammengehören, nicht eingeführt, sondern diese Idee wird von unterschiedlichen Bereichen ins Spiel gebracht.

Microservices können unabhängige und selbstorganisierte Teams ermöglichen. Die Teams können trotz Microservices in ihrer Handlungsfähigkeit eingeschränkt sein. Wichtig ist, den Teams zu vertrauen. Nur dann wird man ihnen zugestehen, Entscheidungen selbst zu treffen. Dieser Aspekt ist vielleicht sogar wichtiger als die Umstellung der Architektur auf Microservices.

Natürlich gibt es neben der Skalierung der Organisation genügend andere Gründe für die Nutzung von Microservices. Es ist also auf keinen Fall so, dass Microservices nur für große, komplexe Systeme sinnvoll sind, sondern sie können, beispielsweise wegen der Vorteile beim Continuous Delivery, auch in kleineren Projekten sinnvoll sein.

 

Fazit

Microservices sind ein Hype, der mittlerweile aber abgeklungen ist und sich eher ins Gegenteil verkehrt hat. Unabhängig davon, ob man Microservices nutzt oder nicht, hat sich durch Microservices die Diskussion über Architektur geändert:

  • Module und Domain-driven Design sind wieder zu wichtigen Themen geworden. Das ist sicher gut, weil eine vernünftige Modularisierung zentral für eine gute Wartbarkeit eines Systems ist.
  • Für eine langlebige Architektur bieten Microservices wegen der heterogenen Technologiestacks eine gute Alternative.
  • Continuous Delivery ist eine wichtige Möglichkeit, um Softwareentwicklung zu optimieren. Zumindest in einigen Fällen kann die notwendige Deployment-Geschwindigkeit nur erreicht werden, wenn man Microservices als Architekturkonzept nutzt; aber Microservices sind nur eine von vielen Optimierungen, die man ergreifen sollte.
  • Die Beziehung zwischen Organisation und Architektur wird nicht nur durch Microservices in den Mittelpunkt gestellt.

So geben Microservices einige interessante Denkanstöße, die auch ohne eine vollständige Microservices-Architektur sinnvoll sein können. Denn am Ende geht es nie darum, einem Hype hinterherzulaufen, sondern immer nur um sinnvolle Architekturentscheidungen.

————-

Links & Literatur

[1] Wolff, Eberhard: „Microservices: Grundlagen flexibler Softwarearchitekturen“, dpunkt, 2015.
[2] Wolff, Eberhard: „Microservices. Ein Überblick“, https://microservices-buch.de/ueberblick.html
[3] Wolff, Eberhard: „Das Microservices-Praxisbuch: Grundlagen, Konzepte und Rezepte“, dpunkt, 2018.
[4] Wolff, Eberhard: „Microservices Rezepte – Technologien im Überblick“, https://microservices-praxisbuch.de/rezepte.html
[5] https://en.wikipedia.org/wiki/Microservices#History
[6] Evan, Eric: „Domain-Driven Design Referenz“, https://ddd-referenz.de
[7] https://en.wikipedia.org/wiki/Eurorack
[8] Wolff, Eberhard: „Continuous Delivery: Der pragmatische Einstieg“, 2. Aufl., dpunkt, 2016.
[9] https://cloud.google.com/devops/state-of-devops/
[10] Forsgren, Nicole; Humble, Jez; Kim, Gene: „Das Mindset von DevOps. Accelerate: 24 Schlüsselkompetenzen, um leistungsstarke Technologieunternehmen zu entwickeln und zu skalieren“, Vahlen, 2019.

 

The post Und jetzt? Microservices nach dem Hype appeared first on JAX.

]]>
Microservices: „Jedes größere Projekt wird ohne ein passendes Service-Mesh-Werkzeug das Geflecht an Services nicht mehr beherrschen“ https://jax.de/blog/microservices-services-mesh-interview-hofmann/ Thu, 31 Oct 2019 11:00:52 +0000 http://new.jax.de/?p=73289 Je größer und verflechteter eine Microservice-Architektur wird, desto unübersichtlicher wird es. In der Entwickler-Welt kommt hier oftmals ein "Service Mesh" zum Einsatz. Während Istio lange als Platzhirsch unter den Service Mesh Tools galt, machen ihm Mitbewerber wie MicroProfile und Linkerd den Platz streitig. Doch worin liegen eigentlich die Unterschiede und welches ist das passende Services Mesh Tool für meine Anwendung? Diesen Fragen widmet sich Michael Hofmann in seinem Talk auf der W-JAX 2019.

The post Microservices: „Jedes größere Projekt wird ohne ein passendes Service-Mesh-Werkzeug das Geflecht an Services nicht mehr beherrschen“ appeared first on JAX.

]]>
Wer Michael Hofmann einmal live erleben möchte, der hat bei der diesjährigen W-JAX 2019 in München wieder Gelegenheit dazu. Dort wird er mit gleich zwei Talks vertreten sein. Michael wird dort zum einen eine Session zum Thema, „Service Mesh mit Istio und MicroProfile“ halten und zeigen, wie diese beiden Welten in einer Cloud-native-Anwendung am besten miteinander kombiniert werden können.

Wie Service Mesh Tools dabei helfen können, die Komplexität von Services Meshes in den Griff zu bekommen, vermittelt der zweite Vortrag von Michael zum Thema “Service Mesh — Kilometer 30 im Microservices-Marathon“.

Redaktion: Hallo Michael und danke, dass du dir die Zeit für das Interview genommen hast. In deinen Sessions auf der W-JAX 2019 sprichst unter anderem über Microservices. Haben Microservices eigentlich tatsächlich einen Vorteil gegenüber großen Monolithen?

Michael Hofmann: Will man im Projekt eine höhere Release-Frequenz erreichen, so bieten die Microservices durch ihre Größe einen klaren Vorteil. Es werden wiederholt nur kleine Teile des gesamten Systems in Produktion genommen, was mit einem Monolithen nur schwer möglich ist. Auch der Wechsel auf einen neuen Technologie-Stack ist in den einzelnen Microservices einfacher zu realisieren, da jeder Service für sich allein, ohne große Auswirkungen auf andere Services, auf den neuen Stack umgestellt werden kann.

Redaktion: Istio ist relativ neu, aber gewinnt zunehmend an Aufwind. Was ist der Grund für das Wachstum?

Hinter Istio steht mit Google und IBM eine mächtige Open-Source-Community, was Nachhaltigkeit und Fortschritt zugleich verspricht.

Michael Hofmann: Zum einen steht hinter Istio mit Google und IBM eine mächtige Open-Source-Community (ca. 430 Contributors laut CNCF Landscape) was Nachhaltigkeit und Fortschritt zugleich verspricht. Damit werden mehr Features pro Release veröffentlicht aber auch die Reaktion der Istio-Community auf Bugs erfolgt sehr zeitnah. Darüber hinaus beginnen nun verschiedene Hersteller oder Cloud-Betreiber, Istio teilweise oder sogar komplett in Ihre Produkte zu integrieren. Das Spektrum reicht von CloudFoundry, über Google und IBM bis hin zu OpenShift. Weitere Cloud-Betreiber (wie beispielsweise Azure) beschreiben zumindest sehr ausführlich, wie Istio in ihre Cloud-Umgebungen integriert werden kann.

Redaktion: Wo von Istio die Rede ist, ist Kubernetes meist nicht weit: Hat Istio das Zeug dazu, der de facto Service Mesh für Kubernetes zu werden?

Michael Hofmann: Fairerweise muss man sagen, dass es auch noch weitere Mitbewerber im Service-Mesh-Markt gibt. Von daher ist es schwer zu sagen, wer am Ende das Rennen machen wird. Bei Istio sind meiner Meinung nach viele Vorraussetzungen gegeben, die dazu notwendig sind, um im Markt einen gewichtigen Anteil zu erreichen. Zur Zeit sehe ich neben Istio und Linkerd auch noch kleinere Mitbewerber, die eine Alternative bieten. Dies erfolgt jedoch meist mit einem reduzierten und somit spezialisierteren Funktionsumfang.

Redaktion: Was ist Dein Lieblingsfeature in Istio?

Michael Hofmann: Das ist schwer zu sagen, da Istio eine Menge nützlicher Features bietet. Aktuell bin ich davon begeistert, wie einfach es ist, mit Istio die Resilienz in der Service-Kommunkikation zu testen. Die Simulierung einer verzögerten Antwortzeit oder der sporadische Ausfall von Kommunikations-Partnern ist ohne Istio, nur sehr schwer zu automatisieren. Meist sind Code-Anpassungen in den Services notwendig, was aber in meinen Augen unbedingt vermieden werden sollte. Mit Istio habe ich hierfür eine sehr elegante Möglichkeit: Durch Aktivieren einer entsprechenden Istio-Regel kann ich jedes beliebige Fehlerverhalten hervorrufen, ohne irgendeine Zeile Code im Service zu ändern. Nach dem Test kann diese Istio-Regel einfach wieder gelöscht werden.

Redaktion: Wie spielen Istio und MicroProfile zusammen?

Michael Hofmann: Grundsätzlich ist Istio mit seiner Architektur technologie-neutral und somit unabhängig von der eingesetzten Programmiersprache der Services. Aber wie so oft liegt der Teufel im Detail. Es gibt mehrere Anforderungen, wie zum Beispiel Tracing oder Resilienz, wo Istio und die Services eng miteinander verzahnt sind. Auch durch die Laufzeit-Umgebung, in welcher Istio eingesetzt wird, wie beispielsweise Kubernetes, ergeben sich Anforderungen an die Services, die ein Entwickler beachten muss. Anfängliche Abstimmungs-Probleme wurden von der MicroProfile-Community erkannt und nach und nach in den jeweiligen Spezifikationen behoben.

Die derzeitige Skepsis, “jetzt schon” Istio zu verwenden, wird durch den Druck der Notwendigkeit verschwinden.

Redaktion: Wie sieht, Deiner Meinung nach, die Zukunft des Service Meshs aus bzw. in welche Richtung wird sich das ganze entwickeln?

Michael Hofmann: Meiner Meinung nach wird jedes größere Projekt ohne ein passendes Service-Mesh-Werkzeug das Geflecht an Services nicht mehr beherrschen und somit betreiben können. Die derzeitige Skepsis, “jetzt schon” Istio zu verwenden, wird durch den Druck der Notwendigkeit verschwinden. Erste Projekte in meinem Umfeld beginnen damit, Teile von Istio zu verwenden und ich denke, der eingesetzte Funktionsumfang wird sich stetig erweitern. Von seitens der Tool-Hersteller wird intensiv daran gearbeitet, die Komplexität, welche in den Tools selbst steckt, zu reduzieren. Auch die Fehlermöglichkeiten, welche durch den falschen Einsatz der Service Mesh Tools entstehen können, wird werkzeug-gestützt verringert. Damit wird insgesamt die Einstiegshürde für die Projekte reduziert.

Redaktion: Vielen Dank für das Interview!

The post Microservices: „Jedes größere Projekt wird ohne ein passendes Service-Mesh-Werkzeug das Geflecht an Services nicht mehr beherrschen“ appeared first on JAX.

]]>
Einführung in Quarkus: Heiliger Gral der Entwicklerproduktivität? https://jax.de/blog/heiliger-gral-der-entwicklerproduktivitaet/ Tue, 22 Oct 2019 11:00:20 +0000 http://new.jax.de/?p=73284 Anfang dieses Jahres ist Red Hat mit Quarkus in die Arena eingetreten, in der Spring Boot und Micronaut bereits darum kämpfen, das beliebteste Full-Stack Framework für die Erstellung von Microservice- und Serverless-Apps zu werden. Was genau unterscheidet Quarkus von seinen Mitbewerbern?

The post Einführung in Quarkus: Heiliger Gral der Entwicklerproduktivität? appeared first on JAX.

]]>
In diesem Artikel lernen Sie die Grundlagen zum Erstellen von Anwendungen mit Quarkus kennen, indem Sie Code aus Spring PetClinic konvertieren, um eine cloud-native Quarkus-Anwendung mit den besten Java-Bibliotheken und -Standards wie Hibernate Panache, RESTEasy und GraalVM zu erstellen.

Was ist Quarkus?

Container First und Cloud Native: Bei Quarkus handelt es sich um ein Kubernetes-natives Java Framework, das Java in der neuen Welt von Serverless-Apps, Microservices, Containern und Cloud zu einer führenden Plattform machen soll. Indem Quarkus Oracles GraalVM nutzt, um native Apps zu erstellen, kann es überaus schnelle Startzeiten in der Größenordnung von Millisekunden sowie eine geringe Speichernutzung für Apps erreichen. Diese Eigenschaften ermöglichen automatisches Scale up und Scale down für Microservices in Containern sowie Function-as-a-Service(FaaS-)-Apps.

Imperative und Reactive: Obwohl Java-Entwickler schnell ein Cloud-natives, ereignisgesteuertes, asynchrones und reaktives Modell einführen können, um Geschäftsanforderungen für die Erstellung von hochkonkurrierenden und reaktionsschnellen Anwendungen zu erfüllen, sind die meisten unter ihnen eher mit dem imperativen Programmiermodell vertraut und möchten diese Erfahrung nutzen, um eine neue Plattform wie Quarkus einzuführen. Quarkus unterstützt sowohl imperative als auch reaktive Programmierparadigmen für Microservices, indem MicroProfile 2.2, die Reactive-Streams-Operators-Spezifikation und sogar Reactive Messaging für die Interaktion mit Apache Kafka vollständig unterstützt werden.

Optimiert auf die Freude des Entwicklers

Die Vision hinter Quarkus strebt mehr als nur Produktivität an: Die Nutzung soll Spaß machen! Deshalb hat das Team dahinter viel Aufmerksamkeit darauf verwendet, dass Livecodierung, Extensions und Unified Configuration gut funktionieren.

  • Im Entwicklungsmodus, den Sie mit mvn compile quarkus: devstarten können, unterstützt Quarkus Livecodierung, indem geänderte Dateien transparent kompiliert werden, wenn eine HTTP-Anfrage eingeht.
  • Das Extension-System soll dazu beitragen, ein lebendiges Ökosystem rund um Quarkus zu schaffen. Extensions, die im Grunde nichts anderes als Projektabhängigkeiten sind, konfigurieren, booten und integrieren ein Framework oder eine Technologie in eine Quarkus-App. Dazu stellen sie GraalVM die richtigen Informationen zur Verfügung, damit Ihre App nativ kompiliert werden kann.
  • Eine einzige Konfigurationsdatei (application.properties) genügt, um Quarkus sowie alle Extensions zu konfigurieren. Um die Größe dieser Datei zu verringern, sollte jede Extension sinnvolle Standardkonfigurationseigenschaften bereitstellen.

Wie teste ich meine Apps mit Quarkus?

Bevor wir uns näher mit dem Code befassen, ist es sinnvoll, einen ersten Blick auf den Testansatz von Quarkus zu werfen. Mit Quarkus können Sie Tests in zwei verschiedenen Modi ausführen, nämlich JVM Mode und Native Mode.

Gemeinhin erweitern die Testklassen im Native Mode die Tests im JVM Mode und werden in einem Docker-Container unter Verwendung der von GraalVM erstellten Native App ausgeführt. Der Vorteil der Wiederverwendung derselben Testklasse für JVM- und native Tests besteht darin, dass wir direkt zu Beginn eines Projekts Tests schreiben können.

Es hat sich als nützlich erwiesen, mit HTTPie die Integrität neuer REST Services zu überprüfen, obwohl Sie auch curloder wgetverwenden können, wenn Sie sich damit besser auskennen. HTTPie (http) ist ein mächtiges Kommandozeilenprogramm mit JSON-Unterstützung, Plug-ins und vielem mehr.

Erste Schritte mit Quarkus

Zum Zeitpunkt der Niederschrift dieses Artikels (August 2019) ist die neueste Version von Quarkus Version 0.21.1. Aus dem Versionsnummernschema können Sie ableiten, dass Quarkus derzeit als Beta eingestuft wird. Es ist wichtig zu wissen, dass zum jetzigen Zeitpunkt jede neue Quarkus-Version wahrscheinlich ihre Abhängigkeiten und Bibliotheken auf die neuesten Versionen aktualisieren wird. Daher habe ich für diesen Artikel die neuesten verfügbaren Versionen von Java, Maven und GraalVM verwendet: GraalVM Community Edition Version 19.2.0, AdoptOpenJDK 8u222 und Maven 3.6.1.

Erstellen einer Quarkus-App aus der Spring-PetClinic-Demo-App

Um ein Gefühl für Quarkus zu entwickeln, konvertieren wir den Code aus der Spring-PetClinic-Demo-App, um eine Cloud-native Quarkus-App zu erstellen. Beginnen wir mit der Ausführung der Spring-PetClinic-App:

git clone https://github.com/spring-projects/spring-petclinic.git
cd spring-petclinic

mvn clean package
mvn spring-boot:run

Abschließend stellen wir sicher, dass die Spring-PetClinic-App ordnungsgemäß funktioniert, indem wir entweder http://localhost:8080in unserem bevorzugten Browser öffnen oder den REST Service mit HTTPie anrufen: http :8080/vets.

Wie zu erwarten war, wird eine JSON-ähnliche Liste aller Tierärzte in der PetClinic-App zurückgegeben. Nun erstellen wir diesen REST Service in Quarkus neu.

Ähnlich wie bei Spring Boot und Micronaut können wir das Quarkus-Maven-Plug-in verwenden, um unsere erste „Hallo-Welt“-App mit Quarkus zu erstellen und auszuführen, wie in Listing 1 gezeigt.

Listing 1

mvn io.quarkus:quarkus-maven-plugin:0.21.1:create \
  -DprojectGroupId=com.github.acme \
  -DprojectArtifactId=quarkus-petclinic \
  -DclassName="com.github.acme.quarkus.petclinic.web.resource.VetResource" \
  -Dpath="/vets"

cd quarkus-petclinic
./mvnw compile quarkus:dev

Wenn wir den resultierenden Dienst testen, indem wir http :8080/vetsaufrufen, wird Hallo im Klartext zurückgegeben. Nicht wirklich spannend, aber was kann man von einer „Hallo-Welt“-App noch erwarten? Wir fahren fort, indem wir das Domänenmodell hinzufügen.

Implementierung des Domänenmodels

JPA, der De-facto-Standard im Object Relational Mapping, wird in Quarkus mit Hibernate ORM vollständig unterstützt. Um Hibernate zu konfigurieren, benötigen wir auch eine Datenquelle, um die Verbindungen zu einer Datenbank herzustellen. In Quarkus ist Agroal die Standardimplementierung für Datenquellen und Connection Pools. Wir können Unterstützung für Hibernate und Agroal hinzufügen, indem wir ihre Quarkus-Erweiterungen zu unserem Projekt hinzufügen:

./mvnw quarkus:add-extension -Dextensions="agroal, hibernate-orm, jdbc-h2, jdbc-mariadb"

Da diese Aktion die Maven-pom.xml-Datei ändert, müssen wir den quarkus:dev-Build stoppen. Gleichzeitig können wir das Modell (Entity Classes) aus der Spring PetClinic in unsere neu erstellte Quarkus PetClinic kopieren.

Da die Domänenklassen Spring-spezifischen Code verwenden, müssen wir etwas mehr als die Import- und Paketnamen korrigieren. Glücklicherweise können wir von Lambdamethoden in Java 8 profitieren, was diese Änderungen fast trivial macht.

Nachdem die Kompilierungsfehler behoben wurden, schlägt die Ausführung von mvn compile quarkus:devleider noch fehl, wie in Listing 2 zu sehen.

Listing 2

  ERROR [io.qua.dev.DevModeMain] Failed to start quarkus: 
java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
     [error]: Build step 
io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#build threw an exception: 
io.quarkus.deployment.configuration.ConfigurationError: Hibernate extension cannot
 guess the dialect as no JDBC driver is specified by 'quarkus.datasource.driver'

Betrachtet man die Fehlermeldung, so ist der Grund, warum Quarkus nicht startet, offensichtlich. Wir haben noch keine Datenbank konfiguriert.

Konfiguration der Datenbanken in Quarkus

Derzeit bietet Quarkus Erweiterungen für die folgenden Datenbanken an:

  • H2
  • PostgreSQL
  • MariaDB (und MySQL)
  • Microsoft SQL Server

Um die Konsistenz mit Spring PetClinic zu gewährleisten, benötigen wir H2-In-Memory-Datenbanken für die Entwicklung und MariaDB (MySQL) für die Produktion. Ähnlich wie bei Spring und Micronaut können Sie Quarkus-Konfigurationsprofile verwenden, um zwischen verschiedenen Laufzeitumgebungen zu unterscheiden. Voreingestellt versteht Quarkus Entwickler(dev)- und Produktionsprofile (prof). Im Moment müssen wir in der Konfigurationsdatei zumindest H2 konfigurieren (Listing 3).

Listing 3: Kofigurationsdatei: „src/main/resources/application.properties“

%dev.quarkus.datasource.url=jdbc:h2:mem:default
%dev.quarkus.datasource.driver=org.h2.Driver
%dev.quarkus.datasource.username=username-default
%dev.quarkus.datasource.min-size=3
%dev.quarkus.datasource.max-size=13
%dev.quarkus.hibernate-orm.database.generation=drop-and-create
%dev.quarkus.hibernate-orm.sql-load-script=db/hsqldb/data-and-schema.sql

%prod.quarkus.hibernate-orm.database.generation=none
%prod.quarkus.datasource.driver=org.mariadb.jdbc.Driver

Jetzt müssen wir nur noch die SQL-Datei für HSQLDB aus der Spring PetClinic in unsere neue Quarkus PetClinic kopieren. Wenn wir jetzt versuchen, unsere Quarkus-PetClinic-App erneut mit mvn compile quarkus:devauszuführen, sollte sie fehlerfrei starten. Beim Testen sehen wir, dass der Aufruf von http :8080/vetsimmer noch Hallo im Klartext zurückgibt. Wir beheben das, indem wir die Klasse VetResource implementieren.

Implementierung der VetResource-Klasse

Um die Klasse VetResource ähnlich wie Spring PetClinic zu implementieren, werden wir das Repository-Pattern verwenden und die Klasse VetRepository mit der Quarkus-Erweiterung Hibernate Panache erneut implementieren. Bei Hibernate Panache handelt es sich um eine Quarkus-Erweiterung, die das Schreiben von Entitäten zugleich simpel und unterhaltsam machen soll. Sie müssen Ihre Entitäten lediglich die Klasse PanacheEntity erweitern lassen, die Spalten von privaten Feldern in öffentliche Felder ändern und können dann alle Getter und Setter, automatisch generierte IDs usw. entfernen.

Außerdem muss unsere Quarkus-App natürlich auch noch in der Lage sein, JSON zu konsumieren und zu produzieren. Dazu stehen zwei Erweiterungen zur Verfügung, die die JSON-Bindung unterstützen: Jackson und RESTEasy. Da RESTEasy die ausgereifteste zu sein scheint, werden wir die RESTEasy-JSON-Bindungserweiterung verwenden.

Die beiden Abhängigkeiten können wir in einer einzigen Anweisung hinzuzufügen: ./mvnw quarkus:add-extension -Dextensions=”hibernate-orm-panache, resteasy-jsonb”. Standardmäßig wird eine listAll()-Methode schon von der PanacheRepository-Klasse bereitgestellt. Wir werden auch die Methode find by last name in unsere VetRepository-Klasse aufnehmen und dabei die von der PanacheRepository-Superklasse bereitgestellte find()-Methode nutzen (Listing 4).

Listing 4: „VetRepository“

@ApplicationScoped
public class VetRepository implements PanacheRepository<Vet> {
  public Vet findByName(String name) {
    return find("lastName", name).firstResult();
  }
}

Jetzt können wir die VetResource-Klasse wie in Listing 5 implementieren.

Listing 5: „VetResource“

@Path("/vets")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class VetResource {
  @Inject
  VetRepository vetRepository;

  @GET
  public List<Vet> list() {
    return vetRepository.listAll();
  }

  @GET
  @Path("/name/{name}")
  public Vet findByName(@PathParam("name") String name) {
    return vetRepository.findByName(name);
  }
}

Wenn wir jetzt versuchen, unsere Quarkus-PetClinic-App erneut mit mvn compile quarkus:dev auszuführen und den Aufruf von http :8080/vets zu testen, erhalten wir die gleiche JSON-Antwort wie mit der Spring-PetClinic-App!

Erstellen einer nativen App

Eine der besten Sachen an Quarkus ist die hervorragende Unterstützung für die Erstellung nativer Images. Um sie einzurichten, müssen wir die Datei application.properties mit einem Produktionsprofil (prod) aktualisieren, um MariaDB zu unterstützen (ähnlich wie bei Spring PetClinic). Vorausgesetzt, dass GraalVM bereits installiert ist, können wir die native App auf folgende Weise testen und ausführen:

gu install native-image
./mvnw clean package -Pnative
./target/quarkus-petclinic-1.0-SNAPSHOT-runner

Wenn ich auf meinem Computer (MacBook Pro 2017) das Timing von Spring Boot mit Quarkus-Dev- und Quarkus-Native-Modus vergleiche, erhalte ich die in Tabelle 1 aufgeführten Ergebnisse.

Anlaufen time http :8080/vets
Spring Boot 5.654 s – real 0m0.270s
– user 0m0.215s
– sys 0m0.045s
Quarkus Dev 3.430 s – real 0m0.278s
– user 0m0.218s
– sys 0m0.047s
Quarkus Native 0.019 s – real 0m0.265s
– user 0m0.210s
– sys 0m0.045s
Tabelle: Vergleich von Spring Boot mit Quarkus-Dev-Modus und Quarkus-Native-Modus

Wir sehen, dass der Start unserer nativen Quarkus-App viel schneller läuft als Spring Boot und Quarkus-Dev-Modus. In diesem Fall dauert die Bearbeitung der Anfrage in allen Varianten ungefähr gleich lang.

Zusammenfassung

Wie wir gesehen haben, ist die Konvertierung vorhandener JSON REST Services von Spring Boot zu Quarkus ein Kinderspiel. Wir erhalten eine Anwendung mit weniger Code, die viel schneller startet, und mit dem zusätzlichen Bonus einer nativ aktivierten, ausführbaren Kubernetes-Datei!
Der Quellcode ist bei GitHub verfügbar. Dort finden Sie auch Testklassen, mit denen Sie die Quarkus-App sowohl im JVM- als auch im nativen Modus testen können.

The post Einführung in Quarkus: Heiliger Gral der Entwicklerproduktivität? appeared first on JAX.

]]>
Microservices-Architektur: ein Fazit https://jax.de/blog/microservices-architektur-ein-fazit/ Wed, 11 Sep 2019 07:51:09 +0000 https://jax.de/?p=72782 Die Diskussion um Microservices-Architekturen ist keine theoretische mehr. Mittlerweile wurden zahlreiche Projekte in die Praxis umgesetzt. Zeit für eine Bilanz, meint Eberhard Wolff, Software-Architekt bei INNOQ und Sprecher auf der W-JAX 2019. Wir haben ihn nach seinen Erfahrungen mit Microservices-Projekten gefragt. Was haben wir bisher gelernt?

The post Microservices-Architektur: ein Fazit appeared first on JAX.

]]>
Redaktion: “Microservices: Ein Fazit” – so lautet der Titel einer deiner Talks auf der W-JAX. Das klingt ja fast nach einem Nachruf. Ist das Microservices-Konzept gescheitert?

Der Hype rund um Microservices geht  zurück.

Eberhard Wolff: Mittlerweile beschäftige ich mich mehr als 5 Jahre mit Microservices und finde daher ein Fazit angemessen. Es gibt zahlreiche erfolgreiche Microservices-Projekte und sicher auch Fehlschläge, so wie mit jeder anderen Architektur auch.
Der Hype rund um Microservices geht allerdings zurück. Da ist es sicher sinnvoll, inne zu halten und zu reflektieren.

Redaktion: Kannst du einmal ein Beispiel aus deiner Praxis-Erfahrung beschreiben, wo (und v.a. warum) ein Microservices-Projekt den Bach runter gegangen ist?

Eberhard Wolff: Die Faktoren, die Projekte typischerweise zum Scheitern bringen, sind vor allem die mangelnde Einbeziehung der Fachbereiche, andere soziale Faktoren oder unklare Ziele. Das gilt für Microservices-Projekte wie auch für andere Projekte. Sicher muss man sich bei Microservices mit anderen Architektur-Ansätzen beschäftigen und gegebenenfalls andere Technologien einführen. Daran scheitert aber ein Projekt eher selten.

Und wenn es daran scheitert, finde ich es wichtiger, in Zukunft bessere Entscheidungen zu treffen anstatt auf eine bestimmte Architektur zu schimpfen.

Redaktion: Momentan reden einige vom Modulith als (bessere?) Alternative zu einer Microservices-Architektur. Was ist damit eigentlich genau gemeint? In welchen Szenarien ist der Modulith-Ansatz sinnvoll?

Der Begriff “Modulith” zeigt, dass Microservices die Software-Architektur-Diskussion auf jeden Fall bereichert haben.

Eberhard Wolff: Das ist eine gute Frage. Modularisierung ist ein wesentliches Konzept, um große Systeme überhaupt implementieren zu können. Module gibt es seit 50 Jahren. Ein Modulith ist ein System, das modular ist, aber nur als Ganzes deployt werden kann. Wenn Microservices das Konzept der Module wieder in den Fokus gebracht haben und man sich auch bei Monolithen darauf besinnt, dann ist das auf jeden Fall super!

Sicher haben Microservices auch dazu geführt, dass Domain-driven Design wieder im Fokus steht, weil so grobgranulare Module gebildet werden können. So zeigt der Begriff „Modulith“, dass Microservices die Software-Architektur-Diskussion auf jeden Fall bereichert haben.

Redaktion: Du sagst ja, dass sich das Microservices-Konzept vor allem auch für die Modernisierung von Legacy-Systemen eignet. Wie können Legacy-Anwendungen von Microservices profitieren?

Eberhard Wolff: Legacy-Systeme zeichnen sich meistens dadurch aus, dass die Technologien, die Code-Qualität und die Architektur des Systems suboptimal sind. Microservices ermöglichen es, neben den Monolithen neue Microservices zu implementieren und mit dem Legacy-System zu integrieren. Die Microservices können andere Technologien und eine andere Architektur nutzen. Sie müssen den vorhandenen Code auch nicht wiederverwenden. So gibt es die Möglichkeit, das Legacy-System mit Greenfield-Microservices schrittweise abzulösen. Welche Funktionalitäten man ablöst, kann man je nach aktuellen Prioritäten entscheiden und so nur die Teile zu Microservices migrieren, bei denen das sinnvoll ist.

Redaktion: Und nochmal zurück zum Titel – Microservices: Ein Fazit. Was können wir denn aus den bisherigen Erfolgen und Misserfolgen mit Microservices-Architekturen lernen? Wie kann Softwarearchitektur jenseits von Microservices weitergedacht werden?

Ich würde mir wünschen, dass man dem Hype nicht traut.

Eberhard Wolff: Ich würde mir wünschen, dass man dem Hype nicht traut. Gleichzeitig wäre es schön, wenn man sich mit neuen Ideen unvoreingenommen beschäftigt und den Kern des Neuen erkennt. Denn meiner Meinung nach nutzen zu viele Projekte
Microservices einfach unreflektiert, und zu viele Projekte schließen sie von vorneherein aus.

Redaktion: Vielen Dank für dieses Interview!

Die Fragen stellte Hartmut Schlosser.

 

Quarkus-Spickzettel


Quarkus – das Supersonic Subatomic Java Framework. Wollen Sie zeitgemäße Anwendungen mit Quarkus entwickeln? In unserem brandaktuellen Quarkus-Spickzettel finden Sie alles, was Sie zum Loslegen brauchen.

 

Jetzt herunterladen!

The post Microservices-Architektur: ein Fazit appeared first on JAX.

]]>
Micronaut – effiziente und performante Microservices für die Cloud https://jax.de/blog/micronaut-framework/ Wed, 21 Aug 2019 08:23:27 +0000 https://jax.de/?p=72438 Den Chancen, die der Microservices-Ansatz bietet, stehen auch einige Herausforderungen gegenüber, die man aber gut mit Frameworks handhaben kann. Mit Micronaut hat nun ein ganz neuer Vertreter die Bühne mit dem Versprechen betreten, modulare, leicht testbare und sehr performante Anwendungen in Java, Kotlin oder Groovy entwickeln zu können. Ob dem wirklich so ist, zeigt Falk Sippach in seiner Session von der JAX 2019.

The post Micronaut – effiziente und performante Microservices für die Cloud appeared first on JAX.

]]>
Auch wenn Micronaut dem Platzhirsch Spring Boot ähnlich sieht, wurde es von Grund auf explizit für die Erstellung von Microservices im Cloud-Computing-Umfeld erstellt. Dank extrem kurzer Startzeiten, einem enorm niedrigen Speicherverbrauch und sehr kleinen JAR-Größen wird es die Microservices-Welt umkrempeln.

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Ermöglicht wird das neuartige Programmiermodell mittels Compile-Zeit-Metaprogrammierung, wodurch die Metadaten für beispielsweise Dependency Injection und die aspektorientierte Programmierung bereits beim Kompilieren erzeugt werden. Reflection, Proxy-Generierung und Data Caching zur Laufzeit entfallen dadurch. Zur Verwendung in der Cloud oder Serverless-Umgebungen gibt es zudem bereits zahlreiche fertig gestellte oder geplante Anbindungen an Service-Discovery-Dienste, Configuration Sharing, Load Balancing und Serverless Computing.

Im Rahmen seiner Session von der JAX 2019 bringt uns Falk Sippach (OIO GmbH) die Funktionsweise von Micronaut näher und stellt sie anhand von realistischen Codebeispielen und Performancemessungen im Vergleich zu anderen JVM-basierten Microservices-Frameworks auf den Prüfstand.

The post Micronaut – effiziente und performante Microservices für die Cloud appeared first on JAX.

]]>
Microframeworks unter der Lupe: Javalin vs. Ktor vs. Spring Fu vs. Micronaut https://jax.de/blog/core-java-jvm-languages/microframeworks-unter-der-lupe-javalin-vs-ktor-vs-spring-fu-vs-micronaut/ Wed, 17 Apr 2019 16:11:09 +0000 https://jax.de/?p=67665 In letzter Zeit gewinnen in der Java-Welt Microframeworks wie Javalin, Ktor, Spring Fu oder Micronaut an Bedeutung. Christian Schwörer (Novatec Consulting GmbH) stellt die Frameworks in seiner kommenden JAX-Session eingesetzt vor. Wir haben im Vorfeld um eine kurze Einschätzung gebeten.

The post Microframeworks unter der Lupe: Javalin vs. Ktor vs. Spring Fu vs. Micronaut appeared first on JAX.

]]>

Was sind Microframeworks eigentlich?

JAX: In der letzten Zeit kommen Microframeworks wieder in Mode. Beispiele in der Java-Welt sind Javalin, Ktor, Spring Fu und Micronaut. Worauf zielen diese Frameworks ab?

Christian Schwörer: Unter „Microframeworks“ versteht man minimalistische Web-Frameworks zum Bau von modularen Anwendungen. Wesentlicher Bestandteil ist die Möglichkeit, einen Webserver wie zum Beispiel Netty zu konfigurieren und zu starten. Darüber werden dann für gewöhnlich REST-Endpunkte bereitgestellt oder Webinhalte ausgeliefert.

Das Besondere bei Microframeworks ist, dass sie sich auf die zentralen Konzepte bei der Anwendungsentwicklung fokussieren. Durch diese Vereinfachung steht die Developer Experience klar im Vordergrund: Es ist möglich, sehr schnell eine Web-Anwendung zu erstellen.

Ebenso zeichnen sich alle genannten Frameworks durch ihre klare Cloud-Ausrichtung und die Eignung für die leichtgewichtige Erstellung von Microservices aus.

JAX: Für die Java-Plattform haben wir mit Spring Boot und dem Eclipse MicroProfile zwei prominente Frameworks für Microservices. Wie unterscheiden sich die Microframeworks von diesen beiden?

Christian Schwörer: Wie erwähnt, konzentrieren sich Microframeworks auf die wesentlichen Bestandteile zur Erstellung von Microservices. Daher haben sie üblicherweise einen geringeren Funktionsumfang als Fullstack-Frameworks wie Spring, MicroProfile oder Grails. Allerdings gibt es auch bei Microframeworks eine große Bandbreite: von Frameworks, die sich wirklich auf das Elementare beschränken, bis hin zu welchen, die so gut wie alle von anderen Frameworks bekannten Features bieten.

Verschaffen Sie sich den Zugang zur Java-Welt mit unserem kostenlosen Newsletter!

Unabhängig vom Funktionsumfang zeichnen sich jedoch alle Microframeworks durch einen schnellen Applikationsstart und einen kleinen Memory Footprint aus. Das ist vor allem beim Einsatz in geclusterten Docker-Containern oder in Serverless-Architekturen ein entscheidender Vorteil.

Das Besondere bei Microframeworks ist, dass sie sich auf die zentralen Konzepte bei der Entwicklung fokussieren.

 

Javalin, Ktor, Spring Fu, Micronaut

JAX: Bleiben wir einmal bei den vier genannten Microframeworks und beginnen bei Javalin. Wie kann man Javalin einordnen – wo liegen die Stärken?

Christian Schwörer: Die Stärke von Javalin liegt ganz klar in seiner Einfachheit. Es ist sehr leicht zu verstehen und handzuhaben, und dementsprechend einfach ist der Code zu lesen. Dies erreicht das Framework, indem es sich auf wenige wesentliche Konzepte beschränkt, die erlernt werden müssen. Daher eignet es sich auch am ehesten für die schnelle Erstellung von überschaubar kleinen (Micro-)Services.

JAX: Ktor beschreibt sich selbst als asynchrones Web Framework für Kotlin. Für welchen Einsatzzweck ist Ktor aus deiner Sicht besonders interessant?

Christian Schwörer: Hinter Ktor steht maßgeblich JetBrains, das Unternehmen, das die meisten vermutlich von der Entwicklungsumgebung IntelliJ und der JVM-Sprache Kotlin kennen.

Es verwundert daher wenig, dass Ktor – anders als die anderen genannten Microframeworks – ausschließlich Kotlin unterstützt. Diese Einschränkung ermöglicht es allerdings, die Kotlin-Sprachfeatures ideal zu nutzen. So setzt Ktor beispielsweise intensiv auf Coroutines, der leichtgewichtigen Kotlin-Lösung für nebenläufige Programmierung. Dadurch ergibt sich ein asynchrones Framework, das sich etwa für den Bau von API-Gateways eignet.

Nebenbei erwähnt, arbeite ich persönlich seit Längerem in der Backend-Entwicklung ausschließlich mit Kotlin, so dass ich eine gewisse Affinität zu verwandten Technologien habe.

Die Stärke von Javalin liegt ganz klar in seiner Einfachheit.

 

JAX: Relativ neu ist auch das Projekt Spring Fu. Damit lassen sich Spring Boot-Anwendungen mittels einer Kotlin DSL oder einer Java DSL konfigurieren. Kannst du das einmal anhand eines Beispiels demonstrieren?

Christian Schwörer: Spring Fu bietet eine funktionale Alternative zur Annotation-basierten, deklarativen Spring Boot-Konfiguration. In folgendem Kotlin-Beispiel wird anhand der @Bean-Annotation eine Spring-Bean definiert:

@Configuration
class MyConfiguration() {
    @Bean
    fun mySpringBean() = MySpringBean()
}

Mit der Kotlin-DSL von Spring Fu sieht dies wie folgt aus:

configuration {
    beans {
        bean< MySpringBean >()
    }
}

Durch die explizite, funktionale Konfiguration wird der Overhead auf ein Minimum reduziert, der sich bei der deklarativen Nutzung von Spring Boot durch Reflection und Classpath Scanning ergibt. Dies führt zu einem schnelleren Applikationsstart und weniger Speicherverbrauch.

Allerdings gibt es Spring Fu erst als Incubator in der Version 0.0.5. Das heißt, auch wenn es sich lohnt, das Projekt im Blick zu behalten, ist es für einen Einsatz in einem produktiven Szenario meines Erachtens noch zu früh.

JAX: Und schließlich Micronaut: Was sind die Eckdaten dieses Frameworks?

Christian Schwörer: Micronaut ist sicherlich das Feature-kompletteste der erwähnten Frameworks. Neben Dependency Injection, Anbindung unterschiedlichster Datenbanken und zahlreicher Security Features bietet es Cloud-native Module wie etwa Service Discovery, Circuit Breakers und Distributed Tracing. Es positioniert sich somit am klarsten als Alternative zu Spring Boot oder Eclipse MicroProfile.

Dennoch weist es Eigenschaften von Microframeworks auf, insbesondere kurze Startzeiten und geringer Speicherverbrauch. Dies wird erreicht, da weitestgehend auf Reflection, Proxies und Classpath Scanning zur Start- und Laufzeit verzichtet wird. Ermöglicht wird dies, indem die benötigten Informationen mittels Annotation Processing und Ahead-of-Time-Compilation bereits zur Compilezeit ermittelt werden.

Ich sollte mir im Vorfeld über den groben Scope meiner Anwendung bewusst sein.

 

Microframeworks in der Praxis

JAX: Du kennst die Microframeworks ja aus der Praxis. Wie kann man am besten loslegen? Hast du einen besonderen Tipp für die Leser, der für dich persönlich gut funktioniert hat?

Christian Schwörer: Einfach selbst ausprobieren! Für alle genannten Frameworks gibt es gute Tutorials, anhand derer man sehr schnell ein Gefühl für die Entwicklung damit bekommt und einen ersten Eindruck, ob das Framework für die angedachte Aufgabe geeignet ist.

Ich sollte mir im Vorfeld aber auch über den groben Scope meiner Anwendung bewusst sein. Gehe ich davon, dass sie in Zukunft stark erweitert werden muss? Brauche ich deshalb beispielweise Features wie Dependency Injection oder explizites Transaktionsmanagement? Dann eignen sich nicht alle der angesprochenen Frameworks, da sich einige – wie bereits erwähnt – zugunsten der schnellen Erlernbarkeit und Einfachheit bewusst auf Kernfunktionen beschränken.

JAX: Vielen Dank für dieses Interview!

Die Fragen stellte Hartmut Schlosser.

 

Quarkus-Spickzettel


Quarkus – das Supersonic Subatomic Java Framework. Wollen Sie zeitgemäße Anwendungen mit Quarkus entwickeln? In unserem brandaktuellen Quarkus-Spickzettel finden Sie alles, was Sie zum Loslegen brauchen.

 

Jetzt herunterladen!

The post Microframeworks unter der Lupe: Javalin vs. Ktor vs. Spring Fu vs. Micronaut appeared first on JAX.

]]>
Spring Boot vs. Eclipse MicroProfile: Microservices-Frameworks im Vergleich https://jax.de/blog/microservices/spring-boot-vs-eclipse-microprofile-microservices-frameworks-im-vergleich/ Mon, 08 Apr 2019 10:51:54 +0000 https://jax.de/?p=67592 Microservices werden im Java-Umfeld immer öfter mit Spring Boot gebaut. Wer aus dem Java-EE- bzw. Jakarta-EE-Lager kommt, hat mit dem Eclipse MicroProfile eine Alternative zur Hand. Wo liegen die Gemeinsamkeiten, wo die Unterschiede?

The post Spring Boot vs. Eclipse MicroProfile: Microservices-Frameworks im Vergleich appeared first on JAX.

]]>

Wir haben mit Tim Zöller, Entwickler bei der ilum:e informatik ag und Sprecher auf der JAX 2019, über den alten Gegensatz „Spring versus Java EE“ gesprochen. Kommt es im Zeichen der Microservices zu einer Neuauflage unter dem Banner „MicroProfile versus Spring Boot“?

Spring Boot oder Eclipse MicroProfile: Microservices-Frameworks im Vergleich

JAX: Hallo Tim, vielen Dank, dass du dir die Zeit für dieses Interview genommen hast! Im Enterprise-Bereich war die Java-Welt ja viele Jahre lang von zwei Frameworks dominiert: Spring und Java EE. Beide hatten den möglichst kompletten Java-Application-Server als zentrale Metapher. Hat diese Metapher in Zeiten von Microservices & Serverless ausgedient?

Tim Zöller: Ich glaube nicht, dass wir die Application Server in näherer Zukunft verschwinden sehen werden. Sie vereinen eine Vielzahl erprobter APIs und Implementierungen in sich, die man aus sehr schlank gebauten WARs heraus benutzen kann. Das ermöglicht beispielsweise auf effiziente Art und Weise, Docker Images aufzubauen und zu pushen, was einigen Unternehmen wichtig ist.

JAX: Wie bewertest du die jüngsten Entwicklungen um Java EE, d.h. den Umzug zur Eclipse Foundation unter dem neuen Namen Jakarta EE? Wird der Java-Enterprise-Standard dadurch beflügelt? Oder handelt es sich eher um ein Auslaufmodell?

Tim Zöller: Ich sehe es als einen positiven Schritt, und die meisten Menschen, mit denen ich bisher darüber gesprochen habe, ebenfalls. Es ist momentan sehr spannend zu sehen, wie die Spezifikation in die Hände der Community übergeht. Die rechtlichen Probleme, die mit der Übertragung von Oracle an die Eclipse Foundation einhergehen, werfen momentan einen Schatten auf den Umzug.

Es gibt beispielsweise immer noch Unklarheiten, wenn es um Namensrechte geht. Offensichtlich wurde das durch die notwendige Umbenennung von Java EE zu Jakarta EE. Im Detail stellt es aber auch ein Problem dar, dass Klassen im Package javax.* von der Übergabe an die Eclipse Foundation betroffen sind. Diese Unsicherheiten sorgen dafür, dass sich einige Mitglieder der Community noch nicht trauen, ihre Mitarbeit zu starten.

Ich glaube nicht, dass die Application Server in näherer Zukunft verschwinden werden.

 

Microservices: Spring Boot versus Eclipse MicroProfile

JAX: Wer neue Java-Anwendungen im Cloud- und Microservices-Kontext baut, greift heute immer mehr zu Spring Boot. Weshalb eigentlich? Wo liegen die Stärken von Spring Boot im Vergleich zum Ansatz des klassischen Spring Frameworks und Java EE?

Tim Zöller: Mit dem Konzept der Spring Boot Starter und dem einfachen Aufsetzen einer neuen Anwendung wurde zunächst einmal ein gewaltiger Konfigurations-Overhead abgeschafft. Die Möglichkeit, ein einzelnes, ausführbares JAR zu erzeugen und die Applikation extern zu konfigurieren, erhöht die Flexibilität in der Entwicklung und im Deployment. Eingebaute Health-Checks und Metriken machen den Betrieb leichter.

Verschaffen Sie sich den Zugang zur Java-Welt mit unserem kostenlosen Newsletter!

JAX: Mit dem Eclipse MicroProfile hat sich ein eher wieder an Java EE bzw. Jakarta EE angelehnter Konkurrent zu Spring Boot herausgebildet. Was haben die beiden Projekte gemeinsam?

Tim Zöller: Enorm viel. Zum einen versucht MicroProfile, genau die Konzepte abzudecken, die Spring Boot erfolgreich machen: geringe Konfiguration, Health Checks, Metriken, Resilience-Maßnahmen – viele Features, die den Betrieb von mehreren, vernetzten Services in der Cloud vereinfachen. Wenn man die gleichen Dinge mit beiden Frameworks umsetzt, fällt sehr schnell auf, dass auch die Konzepte ähnlich umgesetzt sind: REST Clients in MicroProfile sehen beispielsweise OpenFeign REST Clients aus Spring Boot sehr ähnlich, Spring REST Controller werden fast gleich aufgebaut wie REST Services mit JAX-RS. Das setzt sich in den meisten Konzepten fort.

Eclipse MicroProfile und Spring Boot haben enorm viel gemeinsam.

 

JAX: Und wo liegen die Unterschiede?

Tim Zöller: Technisch liegen die Unterschiede darin, dass MicroProfile lediglich das API definiert, welches von den verschiedenen Herstellern umgesetzt wird, etwa von OpenLiberty, Thorntail oder Payara Micro. Das API versucht, hierbei sämtliche Themen zu erfassen: REST Clients, REST Services, Fault Tolerance, Configuration, Health, Metrics, usw. Bei Spring Boot existieren diese Konzepte auch, aber als Bibliotheken im Spring-Boot- oder Spring-Cloud-Umfeld, die Entwickler bei Bedarf in die Anwendung einbinden können. Ein weiterer Unterschied ist, dass MicroProfile komplett im Besitz einer offenen Community ist, welche den Standard vorantreibt.

Spring oder Java EE – der alte Kampf?

JAX: Zurück zum alten Gegensatz Spring versus Java EE: Läuft es aus deiner Sicht nun auf eine Neuauflage unter dem Banner Spring Boot versus Eclipse MicroProfile hinaus?

Tim Zöller: Ich glaube nicht, dass diese Diskussion wieder so grundsätzlich aufkommt. Anwendungslandschaften sind heute homogener als damals. Wenn man 25 Services in Produktion laufen hat, spricht nichts dagegen, die einen in Spring Boot und die anderen mit MicroProfile umzusetzen. Die Kommunikation erfolgt ohnehin über definierte Protokolle.

Spring Boot hat bereits ein gigantisches Ökosystem, welches fertige Lösungen für viele Anwendungsfälle, gerade in der Cloud, bereitstellt. Diese haben sich oft schon in ganz großen Maßstäben bewiesen, z.B. im Einsatz bei Netflix. Wenn Service Discovery, clientseitiges Loadbalancing und eine Einbindung in spezielle Monitoring-Systeme gefragt sind, ist Spring Boot nahezu konkurrenzlos.

Allerdings verlagern sich ebendiese Funktionen zunehmend in die Infrastruktur und werden, z.B. bei einem Einsatz auf Kubernetes, seltener in die Applikation eingebaut. Die Stärke von MicroProfile liegt darin, dass es nur einen Standard beschreibt, keine Implementierung. Neben den „üblichen Verdächtigen“ wie OpenLiberty, Thorntail oder Payara Micro ist es auch möglich, eine Applikation, die gegen diesen Standard entwickelt wurde, auf Implementierungen ausführen zu lassen, die speziell für kleine und schnelle Programme entwickelt wurden. Beispiele sind hier KumuluzEE oder Quarkus.

Sowohl Spring Boot als auch Eclipse MicroProfile stellen großartige Tools bereit.

 

JAX: In deiner JAX-Session Eclipse MicroProfile vs. Spring Boot: getting back in the Ring! nimmst du beide Frameworks genauer unter die Lupe. Was ist die zentrale Botschaft, die du den Teilnehmern mit auf den Weg geben möchtest?

Tim Zöller: Sowohl Spring Boot als auch Eclipse MicroProfile stellen großartige Tools bereit, um die Entwicklung von schlanken, leicht konfigurier- und wartbaren Anwendungen zu ermöglichen. Es ist eher eine Geschmacksfrage als ein „richtig“ oder „falsch“.

JAX: Vielen Dank für dieses Interview!

Die Fragen stellte Hartmut Schlosser

Java-Dossier für Software-Architekten 2019


Mit diesem Dossier sind Sie auf alle Neuerungen in der Java-Community vorbereitet. Die Artikel liefern Ihnen Wissenswertes zu Java Microservices, Req4Arcs, Geschichten des DevOps, Angular-Abenteuer und die neuen Valuetypen in Java 12.

Java-Wissen sichern!

The post Spring Boot vs. Eclipse MicroProfile: Microservices-Frameworks im Vergleich appeared first on JAX.

]]>