jax blog - JAX https://jax.de/tag/jax-blog/ Java, Architecture & Software Innovation Fri, 18 Oct 2024 13:05:23 +0000 de-DE hourly 1 https://wordpress.org/?v=6.5.2 Ihre W-JAX-Wissensreise https://jax.de/blog/ihre-w-jax-wissensreise/ Tue, 06 Aug 2019 11:33:45 +0000 https://jax.de/?p=71114 Sie streben danach, neue Ideen zu finden, sich mit gleichgesinnten Entwicklern zu verbinden und Ihren Horizont zu erweitern? Dann ist die W-JAX Ihr Place to be.

The post Ihre W-JAX-Wissensreise appeared first on JAX.

]]>
Die W-JAX 2019 mit 140+ Sessions, Workshops und Keynotes steht an, aber Sie haben noch kein Ticket?

Sie stehen immer noch vor der Entscheidung, ob Sie an der W-JAX teilnehmen wollen? Wir haben für Sie hier Top-Argumente zusammengetragen, um Sie für die Konferenz zu begeistern. Es fehlt Ihnen noch das Go von Ihrem Chef? Punkten Sie mit der ultimativen E-Mail-Vorlage! Diese benötigt nur minimale Anpassungen Ihrerseits, um einen Ausflug zur W-JAX zu ermöglichen.


E-Mail-Vorlage für Ihren Chef:

Lieber Herr / Frau (…),

Ich möchte Sie gerne um Erlaubnis bitten, an der W-JAX, die vom 4. – 8. November in München stattfindet, teilnehmen zu dürfen.

Als eine der renommiertesten Konferenzen in ihrem Bereich, vermittelt die W-JAX wertvolles Know-how für moderne Java- und Web-Entwicklung, für Software-Architektur und innovative Infrastruktur. Besonderer Fokus wird zudem auf Java Core, Spring, Enterprise-Technologien, Microservices, JavaScript, Continuous Delivery und DevOps gelegt.

Die Highlights der W-JAX sind:

  • 3 Konferenz-Tage
  • 2 Power-Workshop-Tage.
  • 140+ Sessions, Workshops und Keynotes
  • 130+ internationale erfahrene Java-Experten
  • Speaker von führenden Tech-Unternehmen wie IBM, Siemens, RedHat, codecentric, Innoq, Pivotal uvm.
  • Die Inhalte der Sessions sind zum Download verfügbar.
  • Best-Practices und Lessons-Learned zu neuen Trends und Tools, die Ideen für die tägliche Arbeit liefern.
  • Möglichkeit die Besten der Branche zu treffen und sich mit ihnen zu vernetzen.

Alle Infos zur Konferenz und zu Frühbucher-Preisen finden Sie hier.

 

Wenn ich an der Konferenz teilnehmen darf, würde ich gerne im Nachgang meinem Team eine Zusammenfassung der Konferenz geben und von meinen Erfahrungen berichten.
Viele Grüße

(Ihr Name)


The post Ihre W-JAX-Wissensreise appeared first on JAX.

]]>
Das namespace-Desaster: Jakarta EE ohne javax! https://jax.de/blog/das-namespace-desaster-jakarta-ee-ohne-javax/ Fri, 17 May 2019 16:01:08 +0000 https://jax.de/?p=68096 In Zukunft wird Jakarta EE höchstwahrscheinlich nicht mehr mit Namespace javax Hand in Hand gehen - zumindest wenn man von der Entwicklung der API spricht. Das schlug wie eine Bombe bei der Java Enterprise Community ein auf der Jax-Woche. Was sind die alternativen zu Namespace? Wie geht es weiter? Viele Fragen sind offen. Tanja Obradovic, Program Manager von der Eclipse Foundation, gab uns hierzu ein Interview.

The post Das namespace-Desaster: Jakarta EE ohne javax! appeared first on JAX.

]]>
Obwohl die Einschränkung von javax-Namespace ursprünglich anders geplant war, erzeugte dies dennoch keine größeren Aufregungen bei den Entwicklern. Fast schon gegenteilig war die Stimmung (JAXenter berichtete)Wir trafen Tanja Obradovic auf der Jax 2019 an, und führten ein Interview mit ihr um alle auftretenden Fragen zu beantworten. Wie sieht es mit der Zukunft um Jakarta EE aus? Findet es im Video heraus.

 

The post Das namespace-Desaster: Jakarta EE ohne javax! appeared first on JAX.

]]>
Das Gehirn – eine Gebrauchsanweisung (nicht nur) für Software-Entwickler https://jax.de/blog/core-java-jvm-languages/das-gehirn-eine-gebrauchsanweisung-nicht-nur-fuer-software-entwickler/ Mon, 13 May 2019 14:45:58 +0000 https://jax.de/?p=68081 Lernen, Probleme lösen und Softwareentwicklung haben eine ganze Reihe von gemeinsamen Eigenschaften. All diese Dinge benötigen eine ordentliche Portion Gehirnschmalz. Wie wir es schaffen, mit gehirngerechten Methoden unser (Entwickler-)Leben einfacher zu machen, verrät uns Jens Bendisposto (Senior Consultant bei INNOQ und Sprecher auf der JAX 2019) im Interview.

The post Das Gehirn – eine Gebrauchsanweisung (nicht nur) für Software-Entwickler appeared first on JAX.

]]>

Gebrauchsanweisung für das Gehirn

JAX: Du gibst auf der JAX – eine Konferenz für Software-Entwickler – eine Gebrauchsanweisung für das Gehirn. Eine Provokation! Weshalb brauchen gerade Software-Entwickler eine Gebrauchsanweisung fürs Hirn?

Kreative Ideen kommen selten am Schreibtisch.

Jens Bendisposto: Wie in jedem Beruf sollten wir die Werkzeuge kennen, mit denen wir arbeiten ;-). Nur, weil wir alle unser Hirn täglich benutzen, heißt es noch lange nicht, dass wir das auch effizient machen!

Versuch’ dich mal daran zu erinnern, wo du das letzte Mal einen kreativen Gedanken hattest. Ich vermute, die Antwort ist sowas wie “beim Joggen”, “unter der Dusche”, “im Auto auf dem Weg ins Büro” oder vielleicht sogar “nachts im Traum”. Es ist verhältnismäßig selten der Fall, dass kreative Ideen am Schreibtisch kommen und schon mal gar nicht, wenn wir am Rechner arbeiten.

Dafür gibt es einen Grund: Kreative Ideen entstehen nicht beim bewußten, analytischen Denken. So komisch es klingt: Um ein schwieriges Problem zu lösen, müssen wir es zuerst verstehen und dann loslassen und unserem Gehirn Zeit geben. Wenn wir uns bei unserer Arbeit nicht die Zeit nehmen (oder einfordern), um ein Problem auch mal sacken zu lassen, dann vergeuden wir einiges an Potential.

JAX: Du stellst in deiner JAX-Session gehirngerechte Methoden und Techniken vor, mit deren Hilfe wir uns unseren Entwickleralltag vereinfachen können. Kannst du einmal ein Beispiel geben?

Jens Bendisposto: Ich möchte nicht meinen Vortrag spoilern, aber ich glaube, eine Technik kann ich dir schon mal verraten. Es gibt die Redensart “Wer schreibt, der bleibt”. Und es ist wirklich faszinierend, wie sehr uns das Aufschreiben helfen kann.

Du kannst das sehr einfach ausprobieren. Schreib’ einfach für ein paar Wochen “Morning Pages”. Dazu schreibst du jeden Morgen direkt nach dem Aufwachen noch vor dem Morgenkaffee drei Seiten handschriftlich auf. Denk nicht zu sehr darüber nach, ob das, was du schreibst, gut oder schlecht ist. Lass einfach den Stift gleiten und schreib auf, was dir durch den Kopf geht. Die Morning Pages sind nur für dich, du brauchst nicht zu filtern, was du schreibst. Wenn du das zwei oder drei Wochen lang konsequent machst, wirst du feststellen, dass kreative Ideen auftauchen.

Mittel gegen die Aufschieberitis

JAX: Du sprichst auch das Thema Prokrastination an. Sind Software-Entwickler hier besonders anfällig?

Prokrastination führt dazu, dass wir nicht mehr genug Zeit haben, kreative Lösungen zu finden.

Jens Bendisposto: Ich weiß nicht, ob wir als Softwareentwickler besonders anfällig sind, aber ich bin es auf jeden Fall. Und ich kenne wahnsinnig viele andere, die auch von Aufschieberitis geplagt werden. Prokrastination ist ganz besonders dann ein Problem, wenn der verursachte Zeitdruck dazu führt, dass wir tatsächlich nicht mehr genug Zeit haben, kreative Lösungen für Probleme zu finden.

Ich beobachte das an der Uni: Studierende setzen sich häufig nicht rechtzeitig mit ihren Aufgaben auseinander. Das führt dann zu durchgearbeiteten Nächten kurz vor der Deadline und viel Frustration. Auch die Ergebnisse sind dann oft nicht so toll, weil die Kreativität dabei zu kurz kommt.

JAX: Welche Mittel gibt es dagegen?

Jens Bendisposto: Ultimativ müssen wir uns überwinden, es gibt aber ein paar Techniken, die es leichter machen. Ein Grund für Prokrastination ist das Gefühl der Überforderung mit einer Aufgabe. Wir haben diesen Riesenberg Arbeit vor uns und wissen gar nicht, wie wir den überwinden sollen. Hier kann die Pomodoro-Technik helfen.

Ein Grund für Prokrastination ist das Gefühl der Überforderung mit einer Aufgabe.

Die Technik ist super einfach: Alle Ablenkungen (Mail, Handy, Social Networks) ausstellen und 25 Minuten am Stück konzentriert und ablenkungsfrei an einer Sache arbeiten. Nach ein paar Minuten konzentrierter Arbeit an einem Thema stellt man fest, dass es gar nicht so schlimm ist.

JAX: Du hältst deinen Talk ja im Rahmen des Agile Day. Was hat agile Softwareentwicklung mit dem Thema zu tun?

Jens Bendisposto: Bei agiler Softwareentwicklung geht es meiner Ansicht nach darum, Lernen als Teil des Entwicklungsprozesses zu akzeptieren. Insofern glaube ich, dass es ziemlich nützlich ist, wenn wir uns ein wenig mit effizientem Lernen auseinandersetzen.

JAX: Was ist die Kernbotschaft deiner Session, die jeder mit nach Hause nehmen sollte?

Jens Bendisposto: Gebt Eurem Gehirn auch einmal die Zeit, ein Problem kreativ anzugehen und schreibt mehr auf!

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 Das Gehirn – eine Gebrauchsanweisung (nicht nur) für Software-Entwickler 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.

]]>
Von Monolithen über modulare Architekturen zu Microservices mit DDD https://jax.de/blog/microservices/von-monolithen-ueber-modulare-architekturen-zu-microservices-mit-ddd/ Mon, 25 Mar 2019 16:06:38 +0000 https://jax.de/?p=67483 In jedem Unternehmen gibt es große Softwaresysteme, die über viele Jahre weiterentwickelt wurden und deren Wartung Jahr für Jahr immer zäher und teurer wird. Vor dem Hintergrund neuer Architekturparadigmen wie Microservices sollen diese Systeme nun modern, skalierbar und flexibel werden. Dabei ist die Hoffnung, dass man sich der großen, schwerfälligen Monolithen entledigen kann, indem man sie in kleinere, besser zu beherrschende Microservices zerlegt.

The post Von Monolithen über modulare Architekturen zu Microservices mit DDD appeared first on JAX.

]]>

von Dr. Carola Lilienthal

Dieses Heilsversprechen klingt gut, beinhaltet aber viele Fallstricke, Missverständnisse und Herausforderungen. Ein Umbau hin zu Microservices ist aufwendig und kann, falsch eingeleitet, zu einem schlechteren Ergebnis führen als es die ursprüngliche Architektur einmal war. Auf Basis der Erfahrungen aus Kundenprojekten der letzten Jahre werde ich in diesem Artikel sinnvolle von unsinnigen Maßnahmen trennen und pragmatische Lösungen vorstellen.

Microservices: Warum?

Microservices sind in den letzten Jahren als neues Architekturparadigma aufgekommen. Viele Entwickler und Architekten dachten zuerst, es ginge bei Microservices nur darum, Softwaresysteme in voneinander unabhängig deploybare Services aufzuteilen. Aber eigentlich haben Microservices einen anderen Sinn: Bei Microservices geht es darum, Software so auf Entwicklungsteams aufzuteilen, dass die Teams unabhängig und eigenständig schneller entwickeln können als vorher. Bei Microservices geht es also zuerst einmal nicht um Technik, sondern um den Menschen.

Ein schlagkräftiges Entwicklungsteam hat eine Größe, bei der Urlaub und Krankheit nicht zu Stillstand führen (also ab drei Personen) und bei der die Kommunikation beherrschbar bleibt (also nicht mehr als sieben bis acht Personen, die sich gegenseitig auf dem Stand der Dinge halten müssen). Ein solches Team soll nun ein Stück Software, ein Modul zum (Weiter-)Entwickeln bekommen, das unabhängig vom Rest des Systems ist. Denn nur, wenn das Modul unabhängig ist, kann das Team eigenständig Entscheidungen treffen und sein Modul weiterentwickeln, ohne auf andere Teams und deren Zulieferungen zu warten.

Diese Unabhängigkeit von anderen Teams ist nur dann möglich, wenn das Softwaresystem nach fachlichen Kriterien zerlegt wird. Technische Kriterien führen dazu, dass es irgendwelche Arten von Frontend- und Backend-Teams gibt (Abb. 1). In so einer technischen Teamaufteilung ist mindestens das Frontend-Team davon abhängig, dass das Backend-Team die Frontend-Schnittstelle um die benötigten Features erweitert. Gibt es noch ein Datenbankteam, so hat das Backend-Team auch keine Freiheit und muss seinerseits auf Anpassungen durch das Datenbankteam warten. Neue Features betreffen in einer solchen Teamstruktur fast immer mehrere Teams, die bei der Umsetzung voneinander abhängig sind und viel miteinander kommunizieren müssen, damit die Schnittstellen stimmen.


Abbildung 1: Technische Aufteilung von Teams

Eine Aufteilung von Teams nach fachlichen Kriterien macht es im Gegensatz dazu möglich, dass ein Team für ein fachliches Modul in der Software zuständig ist, das sich durch alle technischen Schichten von der Oberfläche bis zur Datenbank zieht (Abb. 2).


Abbildung 2: Fachliche Aufteilung von Teams

Neue Features sollten, wenn der Schnitt in fachliche Module gut gelungen ist, jeweils einem Team und seinem Modul zugeordnet werden können. Natürlich ist das erst einmal eine Idealvorstellung – in der Praxis können neue Features dazu führen, dass der Modulschnitt überdacht werden muss, weil das neue Feature die aktuelle fachliche Zerlegung über den Haufen wirft. Oder sie führen dazu, dass das wahrscheinlich etwas zu große Feature so geschickt in kleinere Features zerlegt werden muss, dass verschiedene Teams ihren jeweiligen fachlichen Anteil an dem großen Feature unabhängig von den anderen Teams umsetzen können. Die jeweiligen Teilfeatures sind dann hoffentlich auch allein sinnvoll und können unabhängig voneinander ausgeliefert werden. Der Mehrwert des großen Features wird dem Anwender allerdings erst am Ende zur Verfügung stehen, wenn alle beteiligten Teams fertig sind.

Fachliche Zerlegung: Wie geht das?

Strebt man eine fachliche Aufteilung seines großen Monolithen an, stellt sich die Frage: Wie findet man stabile, unabhängige fachliche Schnitte, entlang derer eine Zerlegung möglich wird? Das relativ vage beschriebene Konzept von Microservices gibt darauf keine Antwort. Deshalb hat in den letzten Jahren Domain-driven Design (DDD) von Eric Evans an Bedeutung gewonnen. DDD bietet neben vielen anderen Best Practices eine Anleitung, wie Domänen fachlich aufgeteilt werden können. Diese fachliche Aufteilung in Subdomänen überträgt Eric Evans auf Softwaresysteme. Das Äquivalent zu Subdomänen ist in der Software der Bounded Context. Sind die Subdomänen gut gewählt und die Bounded Contexts entsprechend in der Software umgesetzt, dann entsteht eine gute fachliche Zerlegung.

Um eine gute fachliche Zerlegung zu finden, hat es sich in unseren Projekten als sinnvoll erwiesen, den Monolithen und die in ihm möglicherweise vorhandene Struktur erst einmal beiseite zu legen und sich noch einmal grundlegend mit der Fachlichkeit, also der Aufteilung der Domäne in Subdomänen, zu beschäftigen. Wir fangen in der Regel damit an, uns zusammen mit den Anwendern und Fachexperten einen Überblick über unsere Domäne zu verschaffen. Das kann entweder mittels Event Storming oder mittels Domain Storytelling geschehen – zwei Methoden, die für Anwender und Entwickler gleichermaßen gut verständlich sind.

In Abbildung 3 ist eine Domain Story zu sehen, die mit den Anwendern und Entwicklern eines kleinen Programmkinos erstellt wurde. Die grundsätzliche Frage, die wir uns bei der Modellierung gestellt haben, ist: Wer macht was womit wozu? Nimmt man diese Frage als Ausgangspunkt, so lässt sich in der Regel sehr schnell ein gemeinsames Verständnis der Domäne erarbeiten.


Abbildung 3: Überblicks-Domain-Story für ein Programmkino

Als Personen bzw. Rollen oder Gruppen sind in dieser Domain Story erkennbar: die Werbeagentur, der Kinomanager, der Verleiher, der Kassenmitarbeiter und der Kinobesucher. Die einzelnen Rollen tauschen Dokumente und Informationen aus, wie den Buchungsplan der Werbung, die Vorgaben für Filme und die Verfügbarkeit von Filmen. Sie arbeiten aber auch mit „Gegenständen“ aus ihrer Domäne, die in einem Softwaresystem abgebildet sind: dem Wochenplan und dem Saalplan. Diese computergestützten Gegenstände sind mit einem gelben Blitz in der Domain Story markiert. Die Überblicks-Domain-Story beginnt links oben mit der Ziffer 1, wo die Werbeagentur dem Kinomanager den Buchungsplan mit der Werbung mitteilt, und endet bei der 16, wenn der Kassenmitarbeiter den Saalplan schließt.

An diesem Überblick lassen sich verschiedene Indikatoren erklären, die beim Schneiden einer Domäne helfen:

Abteilungsgrenzen oder verschiedene Gruppen von Domänenexperten deuten darauf hin, dass die Domain Story mehrere Subdomänen enthält. In unserem Beispiel könnte man sich eine Abteilung Kinomanagement und eine Abteilung Kartenverkauf vorstellen (Abb. 4).

Werden Schlüsselkonzepte der Domäne von den verschiedenen Rollen unterschiedlich verwendet oder definiert, deutet das auf mehrere Subdomänen hin. In unserem Beispiel wird das Schlüsselkonzept „Wochenplan“ vom Kinomanager deutlich umfangreicher definiert als der ausgedruckte Wochenplan, den der Kinobesucher zu Gesicht bekommt. Für den Kinomanager enthält der Wochenplan neben den Vorstellungen in den einzelnen Sälen auch die geplante Werbung, den Eisverkauf und die Reinigungskräfte. Diese Informationen sind für den Kinobesucher irrelevant (gestrichelte Kreise in Abb. 4).

Enthält die Überblicks-Domain-Story Teilprozesse, die von verschiedenen Triggern ausgelöst werden und in unterschiedlichen Rhythmen ablaufen, dann könnten diese Teilprozesse eigene Subdomänen bilden (durchgezogene Kreise in Abb. 4).

Gibt es im Überblick Prozessschritte, an denen Information nur in eine Richtung läuft, könnte diese Stelle ein guter Ansatzpunkt für einen Schnitt zwischen zwei Subdomänen sein (hellblauer Pfeil in Abb. 4).


Abbildung 4: Überblicks-Domain Story mit Subdomänengrenzen

Für echte große Anwendungen in Unternehmen sind die Überblicks-Domain-Stories in der Regel deutlich größer und umfassen mehr Schritte. Sogar bei unserem kleinen Programmkino fehlen im Überblick die Eisverkäufer und das Reinigungspersonal, die sicherlich auch mit der Software interagieren werden. Die Indikatoren, nach denen man in seiner Überblicks-Domain-Story suchen muss, gelten allerdings sowohl für kleine als auch für größere Domänen.

Übertragung auf den Monolithen

Mit der fachlichen Aufteilung in Subdomänen im Rücken können wir uns nun wieder dem Monolithen und seinen Strukturen zuwenden. Bei dieser Zerlegung setzen wir Architekturanalysetools ein, die es uns erlauben, die Architektur im Tool umzubauen und Refactorings zu definieren, die für den echten Umbau des Sourcecodes notwendig sind. Hier eignen sich unterschiedliche Tools: der Sotograph, der Sonargraph, Structure101, Lattix, Teamscale, Axivion Bauhaus Suite und andere.


Abbildung 5: Mob Architecting mit dem Team

In Abbildung 5 sieht man, wie die Zerlegung des Monolithen mit einem Analysetool durchgeführt wird. Die Analyse wird von einem Tool-Pilot, der sich mit dem jeweiligen Tool und der bzw. den eingesetzten Programmiersprachen auskennt, gemeinsam mit allen Architekten und Entwicklern des Systems in einem Workshop durchgeführt. Zu Beginn des Workshops wird der Sourcecode des Systems mit dem Analysewerkzeug geparst (Abb. 5, 1) und so werden die vorhandenen Strukturen erfasst (zum Beispiel Build Units, Eclipse-/VisualStudio-Projekte, Maven-Module, Package-/Namespace-/Directory-Bäume, Klassen). Auf diese vorhandenen Strukturen werden nun fachliche Module modelliert (Abb. 5, 2), die der fachlichen Zerlegung entsprechen, die mit den Fachexperten entwickelt wurde. Dabei kann das ganze Team sehen, wo die aktuelle Struktur nahe an der fachlichen Zerlegung ist und wo es deutliche Abweichungen gibt. Nun macht sich der Tool-Pilot gemeinsam mit dem Entwicklungsteam auf die Suche nach einfachen Lösungen, wie vorhandene Struktur durch Refactorings an die fachliche Zerlegung angeglichen werden kann (Abb. 5, 3). Diese Refactorings werden gesammelt und priorisiert (Abb. 5, 4). Manchmal stellen der Tool-Pilot und das Entwicklungsteam in der Diskussion fest, dass die im Sourcecode gewählte Lösung besser oder weitergehender ist als die fachliche Zerlegung aus den Workshops mit den Anwendern. Manchmal ist aber auch weder die vorhandene Struktur noch die gewünschte fachliche Zerlegung die beste Lösung, und beides muss noch einmal grundsätzlich überdacht werden.

Erst modular, dann Micro

Mit den so gefundenen Refactorings kann die Arbeit am Monolithen beginnen. Endlich können wir ihn in Microservices zerlegen. Doch halt! Spätestens hier sollte man sich die Frage stellen, ob man sein System tatsächlich in einzelne deploybare Einheiten zerlegen will oder ob nicht ein gut strukturierter Monolith ausreicht. Durch das Aufsplitten des Monolithen in einzelne Deployables kauft man sich eine weitere Stufe von Komplexität ein, nämlich die Verteilung. Braucht man Verteilung, weil der Monolith nicht mehr performant genug ist, dann muss man diesen Schritt gehen. Unabhängige Teams kann man mit den heute sehr weit entwickelten Build-Pipelines aber auch in einem wohlstrukturierten Monolithen bekommen.

Ein wohlstrukturierter Monolith, also ein modularer Monolith oder (wie Dr. Gernot Starke einmal sagte) „ein Modulith“, besteht aus einzelnen fachlichen Modulen, die in einem Deployable existieren (Abb. 6). In manchen Architekturen sind die User Interfaces der einzelnen fachlichen Module hochintegriert. Details zu dieser Variante sprengen diesen Artikel und finden sich in [1].


Abbildung 6: Der Modulith, ein wohlstrukturierter Monolith

 

Ein solcher Modulith setzt durch seine Aufteilung in möglichst unabhängige Module ein grundlegendes softwaretechnisches Prinzip guter Softwarearchitektur um: hohe Kohäsion und lose Kopplung. Die Klassen in den einzelnen fachlichen Modulen gehören jeweils zu einer Subdomäne und sind als Bounded Context in der Software zu finden. Das heißt, diese Klassen setzen gemeinsam eine fachliche Aufgabe um und arbeiten dafür umfassend zusammen. Innerhalb eines fachlichen Moduls herrscht also hohe Kohäsion. Um ihre fachliche Aufgabe zu erledigen, sollten die Klassen in einem Modul nichts von Klassen aus anderen fachlichen Modulen brauchen.

Maximal sollten fachliche Updates, zum Beispiel als Events, über für andere fachliche Module möglicherweise interessante Änderungen zwischen den Modulen ausgetauscht werden und Arbeitsaufträge, die in einen anderen Bounded Context gehören, als Command an andere Module weitergegeben werden. Allerdings sollte man hier immer darauf achten, dass diese Benachrichtigungen nicht überhandnehmen oder als verkappte direkte Aufrufe an andere fachliche Module missbraucht werden. Denn lose Kopplung zwischen fachlichen Modulen bedeutet, dass es so wenig Beziehungen wie möglich gibt. Lose Kopplung lässt sich niemals durch den Einsatz eines technischen Event-Mechanismus erreichen. Denn technische Lösungen schaffen eine technische Entkopplung, aber keine fachlich lose Kopplung.

So ein wohlstrukturierter Modulith ist hervorragend auf eine möglicherweise später notwendige Zerlegung in Microservices vorbereitet, weil er bereits aus fachlich möglichst unabhängigen Modulen besteht. Wir haben also alle Vorteile auf der Hand: Unabhängige Teams, die in den Grenzen ihres Bounded Contexts schnell arbeiten können, und eine Architektur, die auf Zerlegung in mehrere Deployables vorbereitet ist.

Der Knackpunkt: Das Domänenmodell

Wenn ich mir große Monolithen anschaue, dann finde ich dort in der Regel ein kanonisches Domänenmodell. Dieses Domänenmodell wird aus allen Teilen der Software verwendet, und die Klassen im Domänenmodell haben im Vergleich zum Rest des Systems sehr viele Methoden und viele Attribute. Die zentralen Domänenklassen, wie zum Beispiel Produkt, Vertrag, Kunde etc., sind dann meist auch die größten Klassen im System. Was ist passiert?

Jeder Entwickler, der neue Funktionalität in das System eingebaut hat, hat dafür die zentralen Klassen des Domänenmodells gebraucht. Allerdings musste er diese Klassen auch ein bisschen erweitern, damit seine neue Funktionalität umgesetzt werden konnte. So bekamen die zentralen Klassen mit jeder neuen Funktionalität ein bis zwei neue Methoden und Attribute hinzu. Genau! So macht man das! Wenn es schon eine Klasse Produkt im System gibt und ich Funktionalität entwickle, die das Produkt braucht, dann verwende ich die eine Klasse Produkt im System und erweitere sie so, dass es passt. Ich will nämlich die vorhandene Klasse wiederverwenden und nur an einer Stelle suchen müssen, wenn beim Produkt ein Fehler auftritt. Schade ist nur, dass diese neuen Methoden im Rest des Systems gar nicht benötigt, sondern nur für die neue Funktionalität eingebaut werden.

Domain-driven Design und Microservices gehen an dieser Stelle den entgegengesetzten Weg. In einem Modulithen, der fachlich zerlegt ist, oder in einer verteilten Microservices-Architektur gibt es in jedem Bounded Context, der die Klasse Produkt braucht, eine eigene Klasse Produkt. Diese kontextspezifische Klasse Produkt ist auf ihren Bounded Context zugeschnitten und bietet nur die Methoden an, die in diesem Kontext benötigt werden. Den Wochenplan aus unserem Kinobeispiel in Abbildung 3 und 4 gibt es im fachlich zerlegten System zweimal. Einmal im Bounded Context Kinomanagement mit einer sehr reichhaltigen Schnittstelle, über die man Werbung zu Vorstellungen einplanen und den Eisverkauf und die Reinigungskräfte einteilen kann. Und zum anderen im Bounded Context Kartenverkauf, wo die Schnittstelle lediglich das Suchen von Filmen und die Abfrage des Filmangebots zu bestimmten Zeiten ermöglicht. Werbung, Eisverkauf und Reinigungskräfte sind für den Kartenverkauf irrelevant und werden in diesem Bounded Context in der Klasse Wochenplan also auch nicht benötigt.

Will man einen Monolithen fachlich zerlegen, so muss man das kanonische Domänenmodell zerschlagen. Das ist in den meisten großen Monolithen eine Herkulesaufgabe. Zu verwoben sind die auf dem Domänenmodell aufsetzenden Teile des Systems mit den Klassen des Domänenmodells. Um hier weiterzukommen, kopieren wir zuerst die Domänenklassen in jeden Bounded Context. Wir duplizieren also Code und bauen diese Domänenklassen dann jeweils für ihren Bounded Context zurück. So bekommen wir kontextspezifische Domänenklassen, die von ihrem jeweiligen Team unabhängig vom Rest des Systems erweitert und angepasst werden können.

Selbstverständlich müssen bestimmt Eigenschaften von Produkt, beispielsweise die Produkt-ID und der Produktname, in allen Bounded Contexts gleich gehalten werden. Außerdem müssen neue Produkte in allen Bounded Contexts bekanntgemacht werden, wenn in einem Bounded Context ein neues Produkt angelegt wird. Diese Informationen werden über Updates von einem Bounded Context an alle anderen Bounded Contexts gemeldet, die mit Produkten arbeiten.

SOA ist keine Microservices-Architektur

Wenn ein System auf diese Weise zerlegt wird, entsteht eine Struktur, die einer IT-Landschaft aus dem Anfang der 2000er Jahre überraschend ähnelt. Verschiedene Systeme (möglicherweise von verschiedenen Herstellern) werden über Schnittstellen miteinander verbunden, arbeiten aber weiterhin autark auf ihrem eigenen Domänenmodell. In Abbildung 7 sieht man, dass es den Kunden in jedem der vier dargestellten Systeme gibt und die Kundendaten über Schnittstellen zwischen den Systemen ausgetauscht werden.


Abbildung 7: IT-Landschaft mit dem Kunden in allen Systemen

Anfang der 2000er wollte man dieser Verteilung der Kundendaten entgegenwirken, indem man Service-orientierte Architekturen (SOA) als Architekturstil verfolgt hat. Das Ergebnis waren IT-Landschaften, in denen es einen zentralen Kundenservice gibt, den alle anderen Systeme über einen Service Bus nutzen. Abbildung 8 stellt eine solche SOA mit einem Kundenservice schematisch dar.

So eine Service-orientierte Architektur mit zentralen Services hat im Lichte der Diskussion um Microservices den entscheidenden Nachteil, dass alle Entwicklungsteams, die den Kundenservice brauchen, nicht mehr unabhängig arbeiten können und damit an individueller Schlagkraft verlieren. Gleichzeitig ist es vermessen zu erwarten oder zu fordern, dass alle Großrechnersysteme, in denen in vielen Unternehmen die zentrale Datenhaltung sichergestellt wird und die als SOA-Services ansprechbar sind, auf Microservices-Architekturen umgebaut werden. In solchen Fällen treffe ich häufig eine Mischung aus SOA- und Microservices-Architektur an, was durchaus gut funktionieren kann.


Abbildung 8: IT-Landschaft mit SOA und Kundenservice

Standortbestimmung

Um für Monolithen eine Bewertungsmöglichkeit zu schaffen, wie gut der Sourcecode auf eine fachliche Zerlegung vorbereitet ist, haben wir in den vergangenen Jahren den Modularity Maturity Index (MMI) entwickelt. In Abbildung 9 ist eine Auswahl von 21 Softwaresystemen dargestellt, die in einem Zeitraum von fünf Jahren analysiert wurden (X-Achse). Für jedes System ist die Größe in Lines of Code dargestellt (Größe des Punktes) und die Modularität auf einer Skala von 0 bis 10 (Y-Achse).


Abbildung 9: Modularity Maturity Index (MMI)

Liegt ein System in der Bewertung zwischen 8 und 10, so ist es im Inneren bereits modular aufgebaut und wird sich mit wenig Aufwand fachlich zerlegen lassen. Systeme mit einer Bewertung zwischen 4 und 8 haben gute Ansätze, jedoch sind hier einige Refactorings notwendig, um die Modularität zu verbessern. Systeme unterhalb der Marke 4 würde man nach Domain-driven Design als Big Ball of Mud bezeichnen. Hier ist kaum fachliche Struktur zu erkennen, und alles ist mit allem verbunden. Solche Systeme sind nur mit sehr viel Aufwand in fachliche Module zerlegbar.

Fazit

Microservices sind als Architekturstil noch immer in aller Munde. Inzwischen haben verschiedene Organisationen Erfahrungen mit dieser Art von Architekturen gemacht und die Herausforderungen und Irrwege sind deutlich geworden.

Um im eigenen Unternehmen einen Monolithen zu zerlegen, muss zuerst die fachliche Domäne in Subdomänen zerlegt und diese Struktur im Anschluss auf den Sourcecode übertragen werden. Dabei sind insbesondere das kanonische Domänenmodell, unsere Liebe zur Wiederverwendung und das Streben nach Service-orientierten Architekturen Hindernisse, die überwunden werden müssen.

Links & Literatur

[1] Lilienthal, Carola: „Langlebige Softwarearchitekturen – Technische Schulden analysieren, begrenzen und abbauen“; dpunkt.verlag, 2017.

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 Von Monolithen über modulare Architekturen zu Microservices mit DDD appeared first on JAX.

]]>
Java 12 Tutorial: So funktionieren die neuen Switch Expressions https://jax.de/blog/core-java-jvm-languages/wechselhaft-switch-expressions-in-java-12/ Tue, 19 Mar 2019 11:09:04 +0000 https://jax.de/?p=67427 Java verharrte bislang beim switch-case-Konstrukt sehr bei den uralten Wurzeln aus der Anfangszeit der Programmiersprache und den Kompromissen im Sprachdesign, die C++-Entwicklern den Umstieg erleichtern sollten. Relikte wie das break und bei dessen Fehlen das Fall-Through waren wenig intuitiv und luden zu Fehlern ein. Zudem war man beim case recht eingeschränkt bei der Notation der Werte. Das alles ändert sich glücklicherweise mit Java 12. Dazu wurde die Syntax leicht modifiziert und erlaubt nun die Angabe einer Expression sowie mehrerer Werte beim case. Auf diese Weise lassen sich Fallunterscheidungen deutlich eleganter formulieren.

The post Java 12 Tutorial: So funktionieren die neuen Switch Expressions appeared first on JAX.

]]>

Die neuen Switch Expressions in Java 12

Als Beispiel für dieses neue Sprachfeature in Java 12 dient die Abbildung von Wochentagen auf deren textuelle Länge. Analysieren wir zunächst, wieso eine Erweiterung und Modifikation in der Syntax und dem Verhalten von switch sinnvoll ist. Was waren die bisherigen Schwachstellen beim switch? Zur Verdeutlichung schauen wir uns in Listing 1 an, wie man die genannte Abbildung vor Java 12 formulieren würde.

Stay tuned

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

 

Listing 1

DayOfWeek day = DayOfWeek.FRIDAY;

int numLetters = -1;

switch (day)
{
  case MONDAY:
  case FRIDAY:
  case SUNDAY:
    numLetters = 6;
    break;
  case TUESDAY:
    numLetters = 7;
      break;
  case THURSDAY:
  case SATURDAY:
    numLetters = 8;
    break;
  case WEDNESDAY:
    numLetters = 9;
    break;
};

 

Betrachten wir den Sourcecode kritisch. Zunächst einmal ist das gezeigte Konstrukt nicht wirklich elegant und ziemlich lang. Auch die Mehrfachangabe von Werten ist gewöhnungsbedürftig. Schlimmer noch: Es wird ein break benötigt, damit die Verarbeitung überraschungsfrei abläuft und es zu keinem Fall-Through kommt. Zudem müssen wir die (künstliche) Hilfsvariable in jedem Zweig korrekt setzen. Wie geht es also besser?

Syntaxerweiterungen mit Java 12: In Java 12 ist mit „Switch Expressions“ ein als Preview gekennzeichnetes Sprachfeature aufgenommen worden, was die Formulierung von Fallunterscheidungen deutlich erleichtert. In Listing 2 ist die intuitive Schreibweise gut ersichtlich:

Listing 2

public static void switchWithReturnAndMultipleValues();
{
  DayOfWeek day = DayOfWeek.FRIDAY;

  int numLetters = switch (day)
  {
    case MONDAY, FRIDAY, SUNDAY -&gt; 6;
    case TUESDAY -&gt; 7;
    case THURSDAY, SATURDAY -&gt; 8;
    case WEDNESDAY -&gt; 9;
};
}

An diesem Beispiel erkennen wir folgende syntaktischen Neuerungen: Zunächst ändert sich die Schreibweise beim case. Neben dem offensichtlichen Pfeil statt des Doppelpunkts können nun auch mehrere Werte aufgeführt werden. Bemerkenswert ist vor allem, dass wir kein break mehr benötigen: Die hinter dem Pfeil angegebenen Anweisungen werden jeweils nur spezifisch für das case ausgeführt, und es existiert bei dieser Syntax kein Fall-Through. Schließlich kann das switch nun einen Wert zurückgeben, wodurch sich die Definition von Hilfsvariablen vermeiden lässt.

Aktivierung der Java-12-Syntaxerweiterungen: Weil es sich bei den Switch Expressions leider nicht um ein finales Feature handelt, muss man beim Kompilieren und Ausführen des Programms den Kommandozeilenparameter –enable-preview angeben. Weitere Hinweise finden Sie weiter unten im Text.

 

Java 12: Syntaxvarianten bei „switch“

Während allein schon die zuvor vorgestellten Syntaxänderungen eine tolle Erweiterung darstellen, darf man sich doch an weiteren verbesserten Varianten von switch erfreuen: Zuweisungen im Lambda und break mit Rückgabewert.

Zuweisungen im Lambda: Im ersten Beispiel zu der Java-12-Syntax wurde auf der rechten Seite lediglich ein Wert zurückgegeben. Es ist aber weiterhin problemlos möglich, dort eine Zuweisung oder Methodenaufrufe vorzunehmen, selbstverständlich nach wie vor ohne die Notwendigkeit für ein break (Listing 3).

Listing 3

public static void switchAssignment()
{
  final int value = 2;
  String numericString;

  switch (value)
  {
    case 1 -&gt; numericString = "one";
    case 2 -&gt; numericString = "two";
    case 3 -&gt; numericString = "three";
    default -&gt; numericString = "N/A";
  }

  System.out.println("value:" + value + " as string: " + numericString);
}

„break“ mit Rückgabewert: Alle, die einmal eine Java-Zertifizierung machen möchten, freuen sich bestimmt über eine weitere Variante der Syntax. Kommen wir dazu nochmals auf die bisherige Syntax zurück und bilden Namen von Farben auf deren Anzahl an Buchstaben ab – hier bewusst mit einem kleinen Flüchtigkeitsfehler zur Demonstration von Fall-Through versehen (Listing 4).

Listing 4

Color color = Color.GREEN;
int numOfChars;

switch (color)
{
  case RED: numOfChars = 3; break;
  case BLUE: numOfChars = 4; break;
  case GREEN: numOfChars = 5; /* break; UPS: FALL-THROUGH */
  case YELLOW: numOfChars = 6; break;
  case ORANGE: numOfChars = 6; break;
  default: numOfChars = -1;
}

System.out.println("OLD: " + numOfChars);

In diesem Beispiel erkennt man die Nachteile der alten Syntax, die doch etwas sperrig in der Handhabung ist und ohne explizite Kennzeichnung den Fall-Through kaum ersichtlich macht. Da ist der Zwang zur Definition der künstlichen Hilfsvariable eher nur ein Schönheitsmakel. Schauen wir, wie es mit Java 12 besser geht.

Umsetzung mit Java 12: Die nachfolgend gezeigte Variante mit Java 12 ist recht nah an der alten Syntax, jedoch mit dedizierten Verbesserungen: Als eher minimale Abwandlung kann man hinter dem break einen Rückgabewert notieren. Zudem können mehrere Werte in case aufgeführt werden. Insgesamt ergibt sich damit die in Listing 5 dargestellte, besser lesbare Variante.

Listing 5

public static void switchBreakReturnsValue()
{

  Color color = Color.GREEN;

  int numOfChars = switch (color)
  {
    case RED: break 3;
    case BLUE: break 4;
    case GREEN: break 5;
    case YELLOW, ORANGE: break 6;
    default: break -1;
  };
  System.out.println("color:" + color + " ==&gt; " + numOfChars);
}

In diesem Beispiel werden die Vorteile der Syntax deutlich. Diese ist zwar sehr ähnlich zu der bisherigen Variante, doch gibt es einige Erleichterungen und Verbesserungen. Zum einen benötigt man keine künstliche Hilfsvariable mehr, sondern kann mit break einen Wert zurückgeben, wodurch die Abarbeitung dort gestoppt wird, also ähnlich zu dem return einer Methode. Schließlich kann man nun mehrere Werte in einem case angeben, wodurch sich eine kompaktere Schreibweise erzielen lässt.

SIE LIEBEN JAVA?

Den Core-Java-Track entdecken

 

Anpassungen für Build-Tools und IDEs

Zur Demonstration der Auswirkungen von Java 12 auf Build-Tools und IDEs nutzen wir eine einfache Beispielapplikation mit folgenden Verzeichnisaufbau:

Java12Examples
|-- build.gradle
|-- pom.xml
‘-- src
  ‘-- main
    ‘-- java
      ‘-- java12
        ‘-- SwitchExample.java
 

Die Methoden der Klasse SwitchExample haben wir bereits in den vorherigen Abschnitten kennengelernt. Dort finden wir einige Varianten der neuen Syntax der switch-Anweisung.

Java 12 mit Gradle: Für Java 12, insbesondere zum Ausprobieren der neuen Syntax bei switch, benötigt man ein aktuelles Gradle 5.1 (oder neuer) und ein paar passende Angaben in der Datei build.gradle (Listing 6).

Listing 6

apply plugin: ’java’
apply plugin: ’eclipse’

sourceCompatibility=12
targetCompatibility=12

repositories
{
  jcenter();
}

// Aktivierung von switch preview
tasks.withType(JavaCompile) {
  options.compilerArgs += ["--enable-preview"]
}

Danach können wir wie gewohnt mit gradle clean assemble ein korrespondierendes JAR bauen und die Klasse SwitchExample wie folgt daraus starten: java –enable-preview -cp build/libs/Java12Examples.jar java12.SwitchExample. In beiden Fällen ist wichtig, den Kommandozeilenparameter –enable-preview anzugeben, denn nur dadurch lässt sich das Programm auch starten.
Java 12 mit Maven: Für Java 12 benötigt man ein aktuelles Maven 3.6.0 und das Maven Compiler Plugin in der Version 3.8.0. Dann kann man die in Listing 7 dargestellten Angaben in der pom.xml vornehmen:

Listing 7

&lt;plugins&gt;
  &lt;plugin&gt;
    &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
    &lt;version&gt;3.8.0&lt;/version&gt;
    &lt;configuration&gt;
      &lt;source&gt;12&lt;/source&gt;
      &lt;target&gt;12&lt;/target&gt;

      &lt;!- - Wichtig für Java 12 Syntax-Neuerungen -&gt;
      &lt;compilerArgs&gt;--enable-preview&lt;/compilerArgs&gt;
    &lt;/configuration&gt;	
  &lt;/plugin&gt;
&lt;/plugins&gt;

Mit diesen Modifikationen lässt sich ein Maven-Build mit mvn clean package ausführen. Nun wollen wir die neue switch-Funktionalität in Aktion erleben und geben dazu Folgendes ein:

java --enable-preview -cp target/SimpleJdk12Application-1.0.0-SNAPSHOT.jar
java12.SwitchExample

Dadurch startet die Applikation erwartungskonform.

Java 12 mit Eclipse: Eclipse bietet Stand Ende Februar 2019 noch keinen Support für Java 12. Es ist aber zu vermuten, dass dieser mit dem neuen Märzrelease von Eclipse bereitstehen wird.

Java 12 mit IntelliJ: Um mit Java 12 zu experimentieren, nutzen Sie bitte das aktuelle IntelliJ 2018.3. Allerdings sind noch ein paar Einstellungen vorzunehmen:

 

  • Im Project Structure-Dialog muss man im Bereich Project SDK den Wert 12 auswählen und im Bereich Project language level den Wert 12 – No new language features einstellen.
  • Zusätzlich ist im Preferences-Dialog im Feld Additional command line parameters der Wert –enable-preview anzugeben. Danach kompiliert der Sourcecode, allerdings moniert der Editor derzeit noch verschiedene Dinge fälschlicherweise als „Unexpected Token“. Das kann man aber einfach ignorieren.
  • Zum Ausführen des Beispiels ist es erforderlich, in der Run Configuration auch noch –enable-preview einzustellen.

Stay tuned

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

 

Fazit

Die Freude über die Neuerungen in Java 12 ist getrübt, weil es die Raw String Literals nicht ins Release geschafft haben. Dafür sind die Verbesserungen im Bereich switch-case sehr angenehm, aber fast schon überfällig. Leider sind auch diese lediglich als Preview integriert und müssen über –enable-preview freigeschaltet werden.

The post Java 12 Tutorial: So funktionieren die neuen Switch Expressions appeared first on JAX.

]]>
Entwicklertypen und wo sie zu finden sind https://jax.de/blog/entwicklertypen-und-wo-sie-zu-finden-sind/ Wed, 06 Mar 2019 16:44:43 +0000 https://jax.de/?p=67262 Lambda, IoT, Serverless oder Machine Learning - die Welt der in Java programmierten Anwendungen ist so vielseitig wie ihre Schöpfer. Unsere Infografik hilft Ihnen dabei, den Überblick zu behalten und sich selbst zu finden.

The post Entwicklertypen und wo sie zu finden sind appeared first on JAX.

]]>

Java 12 steht vor der Tür und damit einhergehend einige Neuerungen für die Java-Welt. Doch nicht nur das JDK hat sich im Laufe der Jahre und der Versionen verändert und weiterentwickelt, auch die Menschen dahinter. Zeit, einen Blick hinter die Kulissen zu werfen und auf die Leute, die Java 12 erst möglich machen – die Entwickler.

Damit sind nicht nur die JDK-Entwickler gemeint, sondern auch eben diese, welche Java für die vielfältigsten Softwareprodukte nutzen. Bei genauerem Hinsehen haben wir für Sie 10 Entwicklertypen identifiziert, aber schauen Sie selbst!

 

Cheat-Sheet: Die neuen JEPs im JDK 12


Unser Cheat-Sheet definiert für Sie, wie die neuen Features in Java 12 funktionieren. Von JEP 189 „Shenandoah“ bis JEP 346 „Promptly Return Unused Committed Memory from G1“ fassen wir für Sie zusammen, was sich genau ändern wird!

Cheat-Sheet sichern!

 

The post Entwicklertypen und wo sie zu finden sind appeared first on JAX.

]]>
Lost in Transaction: Businesstransaktionen mit Microservices umsetzen https://jax.de/blog/microservices/lost-in-transaction-businesstransaktionen-mit-microservices-umsetzen/ Fri, 15 Feb 2019 11:20:52 +0000 https://jax.de/?p=67064 Die Auftrennung des historisch gewachsenen Monolithen in eine Unmenge von Microservices scheint heute Pflicht. Wirklich unabhängig sind die Services am Ende allerdings nur dann, wenn die Trennung konsequent auf allen Ebenen vollzogen wird. Nicht nur den Code, sondern auch die Datenbank gilt es zu trennen. Willkommen in der Wunderwelt der verteilten Transaktionen.

The post Lost in Transaction: Businesstransaktionen mit Microservices umsetzen appeared first on JAX.

]]>

von Lars Röwekamp

Die Vorteile Microservices-basierter Architekturen sind hinlänglich bekannt. Ist erst einmal ein passender Schnitt gefunden – Domain-driven Design lässt grüßen –, können die einzelnen Services mehr oder minder unabhängig voneinander entwickelt, getestet und deployt werden. Dies erhöht die Agilität der Teams und vermindert die Umsetzungsdauer neuer Features (a.k.a. Time to Market).

Eine wesentliche Grundvoraussetzung für den Erfolg einer Microservices-basierten Architektur ist dabei die konsequente Trennung der Services und ihrer Ressourcen. Getreu dem Motto „share nothing“ wird nicht nur der Sourcecode voneinander getrennt, sondern auch die zugehörige Datenhaltung. Nur wenn auch dieser Schritt konsequent gegangen wird, kann tatsächlich von einer losen Kopplung und einer damit einhergehenden Unabhängigkeit der Services gesprochen werden.

Was ist nun aber, wenn sich ein Use Case über mehrere Services und somit auch über mehrere Datenquellen aufspannt? Nehmen wir als Beispiel eine stark vereinfachte Variante eines Check-out-Prozesses innerhalb eines Webshops. Im Rahmen des Check-outs, also des Verkaufs von Produkten an einen Kunden, wird die Anzahl der Produkte innerhalb des Lagersystems um die gekaufte Menge reduziert. In einem monolithischen System würde dies transaktional erfolgen. In einem Microservices-basierten System dagegen würde es wahrscheinlich einen Check-out-Service und einen Inventory Service inkl. eigener Datenhaltung geben. Durch die Auftrennung der Datenhaltung und damit auch der Datenbank ist die Umsetzung der benötigten Transaktion nicht so ohne Weiteres realisierbar. Wir brauchen einen Plan B.

 

Versuch 1: Transaktionen vermeiden

Die aus technologischer Sicht einfachste Variante, mit dem eben beschriebenen Problem umzugehen, ist, die Services so zu schneiden, dass sich Transaktionen grundsätzlich nur innerhalb eines Service abspielen. Was sich in der Theorie denkbar einfach anhört, hat in der Praxis enorme Konsequenzen. Zwar vermeiden wir durch den Ansatz die Notwendigkeit des Aufteilens einer Transaktion auf mehrere Services, sorgen aber auf der anderen Seite für unnatürliche Service-Grenzen.

Nehmen wir noch einmal das Beispiel von oben. Um die notwendige Transaktion zu realisieren, müssten wir die beiden Services Checkout und Inventory zu einem CheckoutAndInventory Service zusammenlegen. Die erhofften Vorteile der Microservices-basierten Architektur gingen so verloren. Wir enden aus fachlicher Sicht in einem zu großen Service. Der Service hätte durch den Wegfall von Seperation of Concerns und Single Responsibility mehr als nur einen fachlichen Verantwortlichen und würde wahrscheinlich eine Größe einnehmen, die nicht mehr von nur einem agilen Team gemanagt werden kann.

Natürlich könnte man ggf. auch noch einmal die Grenzen der Services prüfen und den fachlichen Schnitt derart verschieben, dass zwar die notwendige Transaktion Teil eines der beiden Services wird, ansonsten aber weiterhin zwei Services bestehen bleiben. Dies ist dann eine Option, wenn die Services eher auf Entitäten aufbauen als auf Use Cases und somit von Anfang an ein ungünstiger Service-Schnitt gewählt wurde. In unserem Fall stellt das nicht wirklich eine Option dar. Was also tun?

Your Coffee Shop Doesn’t Use Two-Phase Commit

Als Erstes sollten wir uns einmal die Frage stellen, ob wir denn tatsächlich eine Transaktion benötigen. Was für eine dumme Frage, wird sich jetzt sicherlich der eine oder andere Leser denken. Natürlich brauchen wir eine Transaktion. Schließlich finden schreibende Zugriffe auf zwei Tabellen statt. Und wenn es eins zu vermeiden gilt, dann ist das doch wohl ein inkonsistenter Zustand in unserer Datenbank! Aus technologischer Sicht ist diese Aussage sicherlich korrekt. Aber stimmt sie auch aus fachlicher Sicht? Ist die Welt tatsächlich so transaktional, wie wir sie in unseren Systemen immer darstellen? Zu dieser Frage hat sich bereits vor fünfzehn Jahren Gregor Hohpe Gedanken gemacht und in seinem Artikel „Your Coffee Shop Doesn’t Use Two-Phase Commit“ [1] am Beispiel des Kaffeebestellvorgangs innerhalb der Starbucks-Läden beeindruckend erläutert, um wie viel besser Systeme skalieren könnten, wenn man auf die strenge Einhaltung von Transaktionen verzichtet und stattdessen lediglich garantiert, dass die Konsistenz der Daten zu einem bestimmten Zeitpunkt sichergestellt ist. Eventual Consistency – also letztendliche Datenkonsistenz – ist hier das Zauberwort der Stunde.

Okay, mag sich jetzt der deine oder andere denken, wir reden hier über Kaffee. Was ist aber mit dem obigen Beispiel des Shopsystems? Oder gar mit einem Bankensystem, bei dem Geld von Konto A nach Konto B transferiert wird?

Nehmen wir zunächst das Beispiel des Shopsystems. Aus fachlicher Sicht stellt sich weniger die Frage, ob der Produktzähler im Inventory Service zu jedem Zeitpunkt auf dem aktuellen Stand ist, sondern vielmehr, ob das bestellte Produkt an den Kunden in einer vordefinierten Zeit geliefert werden kann oder nicht. Falls das auch dann gewährleistet werden kann, wenn bei einem Lagerbestand von null z. B. auf eingehende Retouren zurückgegriffen werden kann oder Lieferanten eine kurzfristige Lieferung bei Nachbestellung garantieren, dann bringt die Sicherstellung der Konsistenz zwischen Check-out und Inventory Service nicht wirklich einen fachlichen Mehrwert. Natürlich muss letztendlich („eventual“) sichergestellt werden, dass spätestens zum Zeitpunkt einer Inventur die Produktzähler im Inventory Service mit dem tatsächlichen Lagerbestand übereinstimmen. Für unseren Bestellprozess ist das aber nicht wirklich essenziell. Tatsächlich wird sich diese Tatsache in vielen Shopsystemen zunutze gemacht, um den Check-out-Prozess zu beschleunigen und so für den Nutzer eine bessere Usability zu erreichen.

Schauen wir uns nun das Beispiel des Bankensystems an. Wer schon einmal Geld von einem Konto auf ein anderes überwiesen hat, der weiß, dass dies definitiv kein transaktionaler Vorgang ist. In der Regel erfolgt die Abbuchung auf dem eigenen Konto sehr zeitnah. Die Wertstellung auf dem Gegenkonto kann dagegen bis zu mehreren Tagen dauern. Auch hier gilt wieder, dass lediglich sichergestellt sein muss, dass die Wertstellung letztendlich erfolgt, wir also garantieren können, dass es nach einer endlichen und fachlich vertretbaren Zeit zu einem konsistenten Zustand der Daten innerhalb des Systems kommt. Was wäre aber, wenn in der Zwischenzeit das Zielkonto gesperrt oder gar geschlossen würde? Bei einer klassischen Transaktion käme es hier zu einem Rollback. In unserem Fall muss sichergestellt sein, dass der Betrag dem Ausgangskonto wieder gutgeschrieben wird.

Für die eben gezeigten Beispiele lassen sich Transaktionen also durchaus vermeiden, indem fachliche Alternativen umgesetzt werden. Was aber, wenn es dennoch den Bedarf für Transaktionen gibt, die über Service-Grenzen hinausgehen?

 

 

Versuch 2: fachliche Transaktionen

Wir haben im obigen Beispiel des Bankensystems gesehen, dass die ursprüngliche Transaktion in mehrere Schritte aufgeteilt wurde, die zeitlich versetzt abgearbeitet werden können. Gleichzeitig wurde sichergestellt, dass am Ende entweder alle angedachten Schritte ordnungsgemäß ausgeführt wurden oder alternativ eine Kompensation – in unserem Beispiel eine Rückbuchung des Betrags auf das Ausgangskonto bei gesperrten Zielkonto – erfolgt.

Hinter diesem Pattern, das auch als SAGA-Pattern [2] bekannt ist, verbirgt sich folgende Idee: Es gilt, fachliche Transaktionen oder Invarianten, die sich über unterschiedliche Services erstrecken, auf mehrere, technisch lokale Transaktionen mit fester Ablaufreihenfolge zu verteilen.

Jede gelungene lokale Transaktion triggert den nächsten Schritt der Aufrufkette und somit die nächste lokale Transaktion an. Jede misslungene lokale Transaktion hingegen muss dafür sorgen, dass alle anderen bisher abgelaufenen lokalen Transaktionen der Aufrufkette kompensiert, also wieder rückgängig gemacht werden.

Nehmen wir uns noch einmal einen Use Case aus unserem Webshop mit folgender Invariante vor: Eine Bestellung kann nur dann erfolgreich aufgegeben werden, wenn die Summe aller offenen Bestellungen des Kunden kleiner oder gleich seines Kreditvolumens ist. Während die Bestellung selbst innerhalb des Order Service abgehandelt wird, findet die Prüfung und Aktualisierung des Kreditrahmens innerhalb des Customer Services satt. In einem ersten Schritt legt der Order-Service eine Bestellung mittels lokaler Transaktion innerhalb seiner Datenbank an, gibt diese aber noch nicht frei. Der Status der Bestellung steht entsprechend auf „pending“. Im Anschluss signalisiert der Order-Service, z. B. durch ein Domänen-Event, den erfolgreichen Abschluss dieses Schritts. Das Domänen-Event wiederum ist das Signal für den nächsten Service in der Aufrufkette – nämlich den Customer-Service, der seinen Teil der verteilten fachlichen Transaktion ausführen soll. Die Verfügbarkeit der notwendigen Bestellsumme wird geprüft und durch eine lokale Transaktion innerhalb des Customer-Service reserviert. Auch dieser Schritt wird wieder mit einer Erfolgsmeldung an die Außenwelt, also die anderen Services beendet. Dadurch weiß der Order-Service, dass er im Rahmen einer weiteren lokalen Transaktion den Status der Bestellung von „pending“ auf „approved“ setzen kann (Abb. 1).

Abbildung 1: Saga-Pattern in Aktion

 

So weit, so gut. Was aber, wenn innerhalb des beschriebenen Ablaufs nicht alles so läuft wie geplant? Was wäre, wenn zum Beispiel der Verfügungsrahmen des Kunden nicht ausreichend ist oder der Bestellstatus, aus welchen Gründen auch immer, nicht auf „approved“ gesetzt werden kann? In diesem Fall müsste die Aufrufkette Schritt für Schritt wieder zurückgegangen werden und für jede bis dato stattgefundene lokale Transaktion eine lokale Kompensation stattfinden (Abb. 2).

Abbildung 2: Transaktion und Kompensation

Was in der Theorie recht einfach klingt, kann in der Praxis beliebig komplex werden. Was passiert zum Beispiel, wenn in einem der Schritte eine E-Mail versandt wurde? Diese lässt sich nicht einfach via lokalem Rollback rückgängig machen. Stattdessen müsste eine zweite E-Mail versandt werden, die dem Adressaten verdeutlicht, dass der Inhalt der ersten E-Mail nicht mehr valide ist. In Shopsystemen wird zum Beispiel aus Gründen der User Experience (schnelle Reaktionszeit) häufig eine Bestellung bestätigt, ohne 100% zusichern zu können, dass diese am Ende auch wirklich ausgeliefert werden kann. Das ergibt durchaus Sinn, da durch Retouren und ausstehende Lieferungen weniger der tatsächliche Lagerbestand von Interesse für die Verfügbarkeit ist, als vielmehr der wahrscheinliche Lagerbestand zum Zeitpunkt des geplanten Versands. Eine angemessene Überbuchung ist somit fachlich sinnvoll.

In der Regel erhält der Shopbesucher neben der reinen Bildschirmdarstellung der Bestellbestätigung auch eine entsprechende E-Mail. Sollte nun einer der wenigen Fälle eintreten, in denen die Ware am Ende tatsächlich nicht geliefert werden kann, wird eine zweite E-Mail hinterhergeschickt. Die beinhaltet dann im günstigsten Fall neben der negativen Meldung der Nichtverfügbarkeit des Artikels gleich alternative Produktangebote evtl. sogar mit Sonderrabatten versehen, um so das negative Kauferlebnis am Ende doch noch positiv zu gestalten.

Der Microservices Track auf der JAX 2019

 

Wer soll da noch durchsteigen?

Bereits die kleinen oben aufgezeigten Beispiele lassen vermuten, dass die Verteilung einer fachlichen Transaktion auf mehrere Services und somit auf mehrere lokale Transaktionen keine triviale Herausforderung darstellt. Das gilt insbesondere dann, wenn man neben den Transaktionen selbst und ihrem Zusammenspiel auch die für sie jeweils notwendigen Kompensationen in Betracht zieht. Ganz zu schweigen von möglichen Kompensationen der Kompensationen, also Fallback-Szenarien für den Fall, dass nicht nur die Transaktion, sondern auch deren Kompensation fehlschlägt. Für die Steuerung des Ablaufs innerhalb des SAGA-Patterns kommen prinzipiell zwei Varianten infrage: Choreografie und Orchestrierung.

Bei der Choreografie sendet ein Service nach erfolgreicher Abarbeitung seiner lokalen Transaktionen eine Erfolgsmeldung via Domänen-Event. Interessierte Services, also die jeweils nächsten in der Ablaufsteuerung, registrieren sich für dieses Event und werden so zur Laufzeit durch das Auftreten des Events aktiviert. Der Gesamtablauf ergibt sich implizit durch den Fluss der Events und die Reaktion der Services auf ebendiese. Der Vorteil dieses Vorgehens liegt klar auf der Hand. Zum einen ist die Ablaufsteuerung relativ einfach zu implementieren, da die Services lediglich in der Lage sein müssen, Events zu erzeugen bzw. auf diese zu reagieren. Durch die Verwendung von Events ist zusätzlich eine lose Kopplung der Services untereinander garantiert. Die Services rufen sich niemals direkt auf und müssen sich gegenseitig nicht kennen. Nachteile ergeben sich insbesondere dann, wenn die fachliche Transaktion komplexer wird. Es findet sich nirgends im Code explizit der gewünschte Ablauf. Und auch die Komplexität des Domänenmodells erhöht sich, da für die Kommunikation zwischen den Services Domänen-Events benötigt werden. Dadurch, dass diese Events und deren Bedeutung sowohl vom Sender als auch dem Empfänger gekannt werden müssen, ergeben sich zyklische Abhängigkeiten der Services untereinander. Abbildung 3 zeigt die Choreografie eines Bestellprozesses, bei dem im Rahmen der fachlichen Transaktion zunächst die Kundendaten verifiziert werden (Customer Services). Im Anschluss erfolgt die Reservierung des zu bestellenden Produkts (Inventory Service) sowie die abschließende Zahlung (Accounting Service).

 

Abbildung 3: Implizite Ablaufsteuerung via Choreografie

 

Bei der Orchestrierung übernimmt eine zentrale Instanz die Steuerung des Ablaufs. Derjenige Service, der die Transaktion anstößt, erzeugt eine Instanz eines SAGA-Koordinators. Dieser Koordinator orchestriert den Ablauf der fachlichen Transaktion, d. h., er kümmert sich um den Aufruf der involvierten Services bzw. deren Logik sowie ggf. um notwendige Kompensationen. Der Aufruf der involvierten Services erfolgt dabei nicht über Domänen-Events, sondern über Commands. In unserem Beispiel würde der SAGA-Koordinator, der innerhalb des Order-Service beheimatet wäre, also nicht ein Event „Order mit dem Status pending wurde angelegt“ in den Raum werfen. Stattdessen würde er gezielt das Command „verify customer“ absetzen, das zur Validierung des Kunden innerhalb des Kundenservices führt (Abb. 4).

Abbildung 4: Orchestrierung

Das klingt zunächst einmal sehr ähnlich, ist es aber nicht. Da der angesprochene Service über sein Standard-API aufgefordert wird, einen seiner Dienste auszuführen, benötigt er keinerlei Wissen über den Aufrufer bzw. dessen Domäne. Bei Domänen-Events dagegen muss der Empfänger wissen, was das Event eines anderen Service fachlich bedeutet und wie er darauf regieren muss. Durch die Orchestrierung lösen wir also die zyklische Abhängigkeit auf und verlagern das Wissen über den Prozess an eine zentrale Stelle, nämlich in den SAGA-Koordinator.

 

Fazit

Soll eine Microservices-basierte Architektur zum Erfolg führen, setzt dies eine strikte Trennung der Ressourcen voraus. Das gilt auch für die Datenbank, was automatisch zu Problemen führt, wenn sich Use Cases, die zu einer verteilten Änderung von Daten führen, über mehr als einen Service erstrecken. Eine Möglichkeit, mit diesem Problem umzugehen, besteht im Hinterfragen der fachlichen Notwendigkeit der Transaktion. In der Praxis zeigt sich häufig, dass eine Transaktion nicht wirklich notwendig ist, sondern lediglich die letztendliche Sicherstellung der Konsistenz der Daten (eventual consistency).

In den Fällen, in denen tatsächlich eine Transaktion benötigt wird, lässt sich dies mit Hilfe des SAGA-Patterns realisieren, bei dem die verteilte, fachliche Transaktion in mehrere, technisch lokale Transaktionen aufgeteilt wird – inklusive Kompensation für den Fehlerfall. Die Koordination des verteilten Ablaufs der fachlichen Transaktion kann dabei entweder implizit via Choreografie, unter Verwendung von Domain Events, erfolgen oder aber explizit via Orchestrierung unter Zuhilfenahme eines SAGA-Koordinators und passender Commands. Sowohl die Choreografie als auch die Orchestrierung haben ihre Vor- und Nachteile, sodass es gilt, von Fall zu Fall bewusst abzuwägen.

Bei beiden Ansätzen geht es im Grunde genommen darum, die aktuelle fachliche Transaktion mit Hilfe diverser Status und deren Übergänge in den Griff zu bekommen. Das legt natürlich nahe, das Rad nicht neu zu erfinden, sondern für die Umsetzung auf entsprechend leichtgewichtige State- oder Workflow-Engines zu setzen. Aber das ist ein Thema für eine andere Kolumne. In diesem Sinne: Stay tuned and engage!

 

 

Cheat-Sheet: Die neuen JEPs im JDK 12


Unser Cheat-Sheet definiert für Sie, wie die neuen Features in Java 12 funktionieren. Von JEP 189 „Shenandoah“ bis JEP 346 „Promptly Return Unused Committed Memory from G1“ fassen wir für Sie zusammen, was sich genau ändern wird!

Cheat-Sheet sichern!

Links & Literatur

[1] https://www.enterpriseintegrationpatterns.com/docs/IEEE_Software_Design_2PC.pdf
[2] https://microservices.io/patterns/data/saga.html

The post Lost in Transaction: Businesstransaktionen mit Microservices umsetzen appeared first on JAX.

]]>
Services im Zwiegespräch: Synchrone Kommunikation zwischen REST Services mithilfe von OpenFeign https://jax.de/blog/software-architecture-design/services-im-zwiegespraech-synchrone-kommunikation-zwischen-rest-services-mithilfe-von-openfeign/ Fri, 18 Jan 2019 17:03:00 +0000 https://jax.de/?p=66668 Wer sich heutige Softwareprojekte oder -architekturen anschaut, steht immer wieder vor ähnlichen Herausforderungen. Eine davon ist die Kommunikation zwischen Services. Asynchron oder synchron, das ist hier die Frage.

The post Services im Zwiegespräch: Synchrone Kommunikation zwischen REST Services mithilfe von OpenFeign appeared first on JAX.

]]>

von Jörn Hameister
Ob es Microservices sein müssen oder ob es sich um Anwendungen in einer Systemlandschaft handelt, spielt keine Rolle. Wenn zwei Services miteinander kommunizieren sollen, besteht die Möglichkeit, dass die Kommunikation asynchron (etwa über Messages mit Kafka oder JMS) oder synchron (beispielsweise über REST) abläuft. Auf lange Sicht bietet die asychrone Kommunikation eine Reihe von Vorteilen, wie lose Kopplung von Services und Resilience. Allerdings wird trotzdem häufig die synchrone Kommunikation bevorzugt, weil sie leichter zu verstehen und zu implementieren ist. Zusätzlich fallen auch die Fehlersuche und das Debugging leichter. Deshalb stelle ich hier ein Framework vor, mit dem sich relativ einfach, elegant und übersichtlich die synchrone Kommunikation zwischen REST Services realisieren lässt: OpenFeign [1].

 

Erst nach dem Problem fragen

Wer kennt es nicht: In einer Systemlandschaft existiert ein Service mit einer REST-Schnittstelle, der Daten bereitstellt, die in einem anderen Service benötigt werden. Dazu gehören zum Beispiel Rechnungen, Kundendaten oder Wetterinformationen. Als Erstes stellt sich dann die Frage, mit welcher Technologie und welchem Framework der Service angebunden werden kann und welches Datenformat benutzt werden soll oder muss. JSON, XML, binär oder ein proprietäres Format?

In unserem Artikel gehen wir davon aus, dass JSON als Format zum Einsatz kommt. OpenFeign unterstützt auch alle anderen Formate und bietet die Möglichkeit, eigene proprietäre Formate zu ergänzen, sodass sie verarbeitet werden können. Wenn ein Service mit REST-Schnittstelle angesprochen werden soll, versucht man zuerst oft, den Service mit einem einfachen HTTP Call anzusprechen und die benötigten Daten abzufragen. Wenn von der Schnittstelle nur ein Integer oder String als Wert zurückkommt, ist das eventuell sogar ausreichend. Allerdings ist es oft so, dass komplexe Objekte (Entities, DTOs, …) an der Schnittstelle als JSON zurückgeliefert werden.

Für diesen Fall können wir beispielsweise den Jackson Mapper [2] ergänzen, um die Serialisierung und Deserialisierung der Objekte zu realisieren. Die Alternative ist das REST-Template von Spring. Es bietet sich an, wenn man sowieso im Spring-Boot-Umfeld unterwegs ist. Wer das REST-Template schon einmal eingesetzt hat, weiß, dass man jedes Mal überlegt, welche API-Methode (exchange, getForEntity usw.) man verwenden soll und wie die Parameter gesetzt werden müssen, um den gewünschten Wert abzufragen. Am Ende landet man meistens bei exchange, schaut wieder in die Dokumentation und sucht Codebeispiele, wie die Syntax genau aussieht.

Aus meiner Sicht ist der Java-Code mit seiner Fehlerbehandlung und dem Exception Handling immer wieder recht aufgebläht. Viel praktischer wäre es doch, wenn man einfach nur eine Clientschnittstelle beschreiben würde. Sie gibt an, wie die Service-Schnittstelle angesprochen werden soll. Das bedeutet, wir müssen uns nicht um die Fehlerbehandlung und die technischen Details kümmern. OpenFeign, ehemals Netflix Feign, ermöglicht beides. Schauen wir uns im ersten Schritt an einem kleinen Beispiel an, wie das funktioniert. Später wird an einem komplexeren API demonstriert, welche Möglichkeiten es gibt, um OpenFeign so zu erweitern, dass auch SPDY [3] verarbeitet werden kann.

ItemService

Anhand eines ItemStores, der Items (Dinge) verwaltet und über eine einfache REST-Schnittstelle angesprochen werden kann, erkennen wir, wie OpenFeign generell benutzt wird und funktioniert. Anfangs werfen wir einen kurzen Blick darauf, wie der Zugriff auf die Schnittstelle mit dem REST-Template oder einer http-Verbindung aussehen kann, um klar zu machen, welche Vorteile OpenFeign bietet. Die REST-Schnittstelle des ItemStore findet sich in Listing 1.

Listing 1: „ItemStore“ REST-Interface

@GetMapping(value = "/item")
ResponseEntity<List<Item>> getAllItems()

@PostMapping(value = "/item")
ResponseEntity<Item> createItem(@RequestBody Item item)

@PutMapping(value = "/item")
ResponseEntity<Item> updateItem(@RequestBody Item item)

@DeleteMapping(value = "/item/{id}")
ResponseEntity<Item> deleteItem(@PathVariable("id") long id)

@GetMapping(value = "/item/{location}")
ResponseEntity<List<Item>> getItemAtLocation(@PathVariable("location") String
location)

 

Es ist eine recht überschaubare Schnittstelle mit einer Methode zum Anlegen (createItem), Ändern (updateItem), Löschen (deleteItem) und Suchen (getItemAtLocation) von Items.

 

REST-Template

Wenn man mit der Klasse RestTemplate auf den Service zugreifen möchte, gestaltet sich das so:

RestTemplate restTemplate = new RestTemplate();
ResponseEntity<Item[]> responseEntity =
restTemplate.getForEntity("http://localhost:8080/item", Item[].class);
List<Item> listWithItems = Arrays.asList(responseEntity.getBody());

Hier verwenden wir die Methode getForEntity, um alle Items abzufragen. Eine weitere Variante, um lesend mit GET auf den Service zuzugreifen, kann so aussehen:


ResponseEntity<List<Item>> rateResponse =
  restTemplate.exchange("http://localhost:8080/item",
  HttpMethod.GET, null, new ParameterizedTypeReference<List<Item>>() {
  });
  List<Item> itemList = rateResponse.getBody();

Hier wird die exchange-Methode benutzt, um alle Items abzufragen und das Ergebnis in einer Liste zu erhalten. Sobald wir allerdings nicht nur lesend auf die Schnittstelle zugreifen möchten, sondern auch PUT und POST benutzen, müssen wir die Funktion exchange verwenden.

RestTemplate restTemplate = new RestTemplate();
HttpEntity<Item> request = new HttpEntity<>(item);
ResponseEntity<Item> response = restTemplate.exchange("http://localhost:8080/item",
HttpMethod.POST, request, Item.class);

In diesem Beispiel legen wir ein neues Item über das REST API an. Das bedeutet, die Methode createItem wird aus dem Interface in Listing 1 aufgerufen und die Response enthält das neu angelegte Item.

HttpConnection

Natürlich kann man den lesenden Zugriff auch mit einer einfachen HttpConnection und mit dem Jackson ObjectMapper lösen. Allerdings wird schon bei GET deutlich, dass extrem viel Boilerplate-Code entsteht und eine aufwendige Fehlerbehandlung dazukommt (Listing 2).

Listing 2: „HttpConnection“ für GET

private static List<Item> httpClientGet() throws IOException {
  URL url = new URL("http://localhost:8080/item");
  HttpURLConnection con = (HttpURLConnection) url.openConnection();
  con.setRequestMethod("GET");

  BufferedReader in = new BufferedReader(
  new InputStreamReader(con.getInputStream()));
  String inputLine;
  StringBuffer content = new StringBuffer();
  while ((inputLine = in.readLine()) != null) {
  content.append(inputLine);
  }
  in.close();

    ObjectMapper objectMapper = new ObjectMapper();
    return objectMapper.readValue(content.toString(), new
TypeReference<List<Item>>() { });
}

Außerdem ist bei diesem Ansatz auch die Gefahr größer, dass Fehler passieren. Beispielsweise weil vergessen wird, die Streams und Connections zu schließen. Eine andere Fehlerquelle liegt darin, dass wie im Beispiel kein Timeout von etwa fünf Sekunden mit con.setReadTimeout(5000); gesetzt wurde. Es ist eindeutig, dass das keine gute Lösung ist, die man für ein umfangreiches API implementieren und testen möchte.

Mit OpenFeign

Nachdem wir uns angeschaut haben, wie die REST-Schnittstelle mit dem REST-Template und mit HttpConnection angesprochen werden kann, kommen wir dazu, wie sich das mit OpenFeign lösen lässt. Um die Service-Schnittstelle aus Listing 1 mit OpenFeign anzusprechen, legen wir schlicht ein Interface an (Listing 3).

Listing 3: OpenFeign-Interface


package org.hameister.itemmanager;

import feign.Headers;
import feign.Param;
import feign.RequestLine;

import java.util.List;

public interface ItemStoreClient {

  @RequestLine("GET /item/")
  List<Item> getItems();

  @RequestLine("POST /item/")
  @Headers("Content-Type: application/json")
  Item createItem(Item item);

  @RequestLine("PUT /item/")
  @Headers("Content-Type: application/json")
  Item updateItem(Item item);

  @RequestLine("DELETE /item/{id}")
  void deleteItem(@Param("id") String id);

  @RequestLine("GET /item/{location}")
  List<Item> getItemAtLocation(@Param("location") String location);
}

Auf den ersten Blick ist deutlich, dass es nahezu identisch zum Service-Interface ist und keinerlei Boilerplate-Code enthält. Man beschreibt nur die Schnittstelle des Service mit dem Pfad der Operation und den Parametern und legt den Content-Type fest.

Mit der Annotation @RequestLine(“GET /item/”) geben wir die Operation und den Pfad an, der beschreibt, wo die Methode zu finden ist.

Um dieses Interface zu benutzen, lässt sich mit dem Feign.Builder einfach ein Client erzeugen und anschließend übers Interface auf die REST-Schnittstelle des ItemStore zugreifen (Listing 4).

Listing 4: OpenFeign-Client

package org.hameister.itemmanager;

import feign.Feign;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;

import java.time.LocalDate;
import java.util.List;

public class ItemManager {

  public static void main(String[] args) {

    ItemStoreClient api = Feign.builder()
      .encoder(new JacksonEncoder())
      .decoder(new JacksonDecoder())
      .target(ItemStoreClient.class, "http://localhost:8080");

    }
}

Dem Builder kommunizieren wir, welche Decoder und Encoder er verwenden soll, wo der Server mit der REST-Schnittstelle läuft und welches OpenFeign-Interface er verwenden soll. In unserem Beispiel nutzen wir den JacksonDecoder und JacksonEncoder. Diverse andere Standardencoder und -decoder zu verwenden, wäre ebenfalls möglich. Beispielsweise für Gson zum Serialisieren und Deserialisieren von Java-Objekten, JAXB zum Serialisieren und Deserialisieren von XML und SAX zum Serialisieren von XML.

Außerdem lässt sich im Builder das Logging konfigurieren, indem wir einen Logger ergänzen:


Feign.builder().logger(new Slf4jLogger())

Zusätzlich kann man einen Client definieren, der Dinge wie SPDY erledigt.


SwapiFeign api = Feign.builder()
 .client(new OkHttpClient())

Es ist auch möglich, Ribbon für das clientseitige Loadbalancing hinzuzufügen:


SwapiFeign api = Feign.builder().client(new RibbonClient())

Außerdem können wir eigene Encoder und Decoder implementieren und registrieren, sodass proprietäre Formate unterstützt werden können. Dazu muss nur das jeweilige Interface implementiert werden. Für den Decoder:


public class MyCustomDecoder implements Decoder {
  @Override
  public Object decode(Response response, Type type) throws IOException,
DecodeException, FeignException {
    return ...;
  }
}


Für den Encoder:


public class MyCustomEncoder implements Encoder {
  @Override
  public void encode(Object o, Type type, RequestTemplate requestTemplate) throws
EncodeException {
  ...
    }
}


Diese Encoder und Decoder müssen anschließend wie die Standardencoder und -decoder

registriert werden, wenn der Client mit dem Builder erstellt wird. Nicht zu vergessen, dass man bei der Definition des Clientinterface auch direkt Hystrix integrieren kann. Dafür gibt es einen HystrixFeign Builder, der genauso benutzt wird wie der Standard-Builder.


ItemStoreClient api = HystrixFeign.builder()
  .target(ItemStoreClient.class, "http://localhost:8080");


Er ermöglicht uns, die Schnittstelle um ein fehlertolerantes Verhalten zu erweitern. Beispielsweise, wenn der Service nicht oder nur langsam antwortet. Gerade in einem Umfeld, in dem mehrere Services miteinander kommunizieren, lässt sich dadurch verhindern, dass der Ausfall eines Service das Gesamtsystem zum Stehen bringt. Das war es auch schon. Anschließend dient der erstellte ItemStoreClient dazu, über die REST-Schnittstelle auf den ItemStore zuzugreifen.

In Listing 4 zeigt sich, wie der ItemStoreClient erstellt wird. Anschließend können die Methoden übers Interface direkt aufgerufen werden: List<Item> items = api.getItems();

Das Anlegen von Items funktioniert so:


Item item = new Item();
item.setDescription("New Item");
item.setLocation("Schrank 5A");
item.setItemdate(LocalDate.now());
Item newItem = api.createItem(item);


Ein Item zu ändern lässt sich analog durchführen:


newItem.setLocation("Schrank 5B");
Item updateItem = api.updateItem(newItem);


Um ein Item zu löschen, muss die jeweilige ID übergeben werden: api.deleteItem(“1”);.Das ist im Vergleich zum REST-Template oder dem HttpClient erheblich eleganter und verständlicher. Anzumerken ist, dass bei allen Ansätzen auf Clientseite ein DTO für das Item vorhanden sein muss. Entweder wir kopieren die Klasse aus dem Item-Service oder legen eine neue Klasse an (Listing 5).

Listing 5:  Item-DTO

@Data
public class Item {

  Long id;

  private String description;
  private String location;
  private LocalDate itemdate;

  public Item() {
  }
}

 

In dem DTO-Item haben wir Lombok [4] verwendet, um die Getter und Setter automatisch generieren zu lassen.

SWAPI

Wir haben uns angeschaut, wie OpenFeign generell bei einer einfachen Schnittstelle verwendet werden kann und welche Vorteile es gegenüber anderen Ansätzen mitbringt. Jetzt wenden wir uns dem zu, was OpenFeign noch bietet. Das soll am Beispiel von SWAPI (The Star Wars API) gezeigt werden. Dabei handelt es sich um eine öffentliche REST-Schnittstelle, über die man Personen, Filme, Raumschiffe und Planeten aus dem Star-Wars-Universum abfragen kann. Die Schnittstelle ist unter dem URL https://swapi.co zu erreichen. Wie auch schon bei dem Beispiel oben legen wir als Erstes ein Interface für die Schnittstelle an (Listing 6).

Listing 6:  SWAPI-Interface

public interface SwapiFeign {
  @RequestLine("GET /planets/{id}")
  Planet getPlanet(@Param("id") String id);

  @RequestLine("GET /planets/")
  GenericList<Planet> getPlanets();

  @RequestLine("GET /films/")
  GenericList<Film> getFilms();

  @RequestLine("GET /people/")
  GenericList<People> getPeople();

  @RequestLine("GET /starships/")
  GenericList<Starship> getStarships();

  @RequestLine("GET /vehicles/")
  GenericList<Vehicle> getVehicles();

}

Wir müssen für die Rückgabewerte noch DTOs anlegen. Im Vergleich zum anderen Beispiel benötigt man außerdem noch eine Generic List, weil das API die Rückgabewerte untereinander verlinkt. Das heißt, die Rückgabewerte enthalten immer einen Link auf das vorhergehende und nächste Element (Listing 7).

Listing 7:  „GenericList“

public class GenericList<T> {
  public int count;
  public String next;
  public String previous;

  public List<T> results;
}

Anschließend lässt sich wieder ein Client erstellen. Er ermöglicht, die Daten über die REST-Schnittstelle abzufragen. Auch im folgenden Beispiel werden ein JacksonEncoder und ein JacksonDecoder verwendet, um die JSON-Daten von der REST-Schnittstelle zu serialisieren und zu deserialisieren.

SwapiFeign api = Feign.builder()
  .encoder(new JacksonEncoder())
  .decoder(new JacksonDecoder())
  .client(new OkHttpClient())
  .target(SwapiFeign.class, "http://swapi.co/api");

Beim Erstellen des Clients fällt auf, dass der OkHttpClient() gesetzt wird. Das ist notwendig, damit SPDY, HTTP/2 und TLS des REST-Interface bedient werden können. Der komplette Quellcode zum OkHttpClient steht im GitHub Repository zu dem Artikel zur Verfügung [5].

Mit dem api-Objekt lässt sich nun die Schnittstelle ansprechen:

GenericList<Starship> starships = api.getStarships();

Listing 8 zeigt exemplarisch das DTO für das Starship.

Listing 8:  „Starship“

@Data
public class Starship {

  private String name;
  private String model;
  private String manufacturer;
  private String costs_in_credits;
  private String length;
  private String max_atmosphering_speed;
  private String crew;

  private String cargo_capacity;
  private String consumables;
  private String hyperdrive_rating;
  private String MGLT;
  private String starship_class;

  private List<People> pilots;
  private List<Film> films;
  private String created;
  private String edited;
  private String url;

  public Starship() {

  }
}

Um das Schema, also die Felder eines Starships herauszufinden, kann man einfach das API befragen, das unter [6] zu erreichen ist. Auch dies ist eine REST-Schnittstelle, die sich abfragen lässt:

Schema schema = getSchema("https://swapi.co/api/starships/schema");

Wobei das Schema-DTO aussieht wie in Listing 9

Listing 9:  Schema-DTO

@JsonIgnoreProperties(ignoreUnknown = true)
public class Schema {
  public List<String>required;
  public Map<String, Properties> properties;
  public String type;
  public String title;
  public String description;

  public Schema() {
  }
}

Und das verwendete Properties DTO so:

public class Properties {
  public String type;
  public String format;
  public String description;
}

Die getSchema()-Methode mit dem OkHttpClient zum Abfragen des Schemas findet sich in Listing 10.

Listing 10:  „getSchema“-Methode

private static Schema getSchema(String url) throws IOException {
  okhttp3.OkHttpClient okHttpClient = new okhttp3.OkHttpClient();

  Request request = new Request.Builder()
  .url(url)
  .get()
  .build();
  Response response = okHttpClient.newCall(request).execute();
  ObjectMapper objectMapper = new ObjectMapper();
  Schema schema =
objectMapper.readerFor(Schema.class).readValue(response.body().string());
  return schema;
}

 

Hier haben wir bewusst darauf verzichtet, OpenFeign einzusetzen, um zum Abschluss noch einmal zu verdeutlichen, dass der Quellcode ohne OpenFeign länger ist als mit OpenFeign. Zu beachten ist, dass das Exception Handling hier weitgehend ignoriert wurde, indem die IOExceptions einfach an den Aufrufenden zurückgeworfen und nicht behandelt werden.

Um noch einmal zu unterstreichen, wie einfach die Abfrage mit OpenFeign funktioniert, definieren wir zuerst ein Interface:

public interface SwapiSchemaClient {

  @RequestLine("GET ")
  Schema getSchema();
}

Anschließend kann ein Feign-Client mit dem Builder erstellt und daraufhin das Schema

abgefragt werden (Listing 11).

Listing 11:  „SchemaClient“

SwapiSchemaClient api = Feign.builder()
  .encoder(new JacksonEncoder())
  .decoder(new JacksonDecoder())
  .client(new OkHttpClient())
  .target(SwapiSchemaClient.class, "https://swapi.co/api/starships/schema");

Schema schema = api.getSchema();

 

Weniger selbst implementieren

OpenFeign ist eine elegante Möglichkeit, REST-Schnittstellen anzusprechen. Der Anwender bekommt eine Menge Features quasi geschenkt, die er normalerweise selbst implementieren müsste. Allerdings ist es nur eine von vielen Möglichkeiten. Wie so oft bei der Softwareentwicklung muss man immer genau schauen, in welchem Kontext man sich bewegt, welche Rahmenbedingungen es gibt und was dann die beste Lösung in dem Projekt ist. Einen kurzen Einführungsvortrag zu OpenFeign hat Igor Laborie bei der Devoxx 2016 in Belgien gehalten [7].

Anmerken sollte man vielleicht noch, dass ab Java 11 ein HttpClient fester Bestandteil von Java (JEP 321) ist, der sowohl synchrone, als auch asynchrone Requests absetzen kann [8].

 

Cheat-Sheet: Die neuen JEPs im JDK 12


Unser Cheat-Sheet definiert für Sie, wie die neuen Features in Java 12 funktionieren. Von JEP 189 „Shenandoah“ bis JEP 346 „Promptly Return Unused Committed Memory from G1“ fassen wir für Sie zusammen, was sich genau ändern wird!

Cheat-Sheet sichern!

Links & Literatur
[1] OpenFeign: https://github.com/OpenFeign/feign
[2] Jackson Mapper: https://github.com/FasterXML/jackson
[3] SPDY: https://de.wikipedia.org/wiki/SPDY
[4] Project Lombok: https://projectlombok.org
[5] https://github.com/hameister/ItemStoreFeignClient
[6] SWAPI: https://swapi.co/api/starships/schema
[7] OpenFeign in Action: https://youtu.be/kO3Zqk_6HV4
[8] Java 11 HttpClient, Gson, Gradle, and Modularization: https://kousenit.org/2018/09/22/java-11-httpclient-gson-gradle-and-modularization/

The post Services im Zwiegespräch: Synchrone Kommunikation zwischen REST Services mithilfe von OpenFeign appeared first on JAX.

]]>