Erhältlich ab: Mai 2018
Ruben, der Scrum Master des MusicStore-Teams, hat festgestellt, dass Christian (einer der Backend-Entwickler) in den letzten Wochen zunehmen gereizt und unzufrieden war. Er hat sich mit Christian auf ein Bier verabredet, um die Gründe herauszufinden. Sie sitzen an der Bar, und nach ein paar Sätzen Smalltalk kommen sie auf das eigentliche Thema zu sprechen.
Ruben: „Du wirkst in den letzten Wochen sehr demotiviert und gehst auch sehr ruppig mit deinen Kollegen um.“
Christian: „Findest du? Lukas und ich arbeiten doch super zusammen.“
Ruben: „Das stimmt, mit Lukas verstehst du dich gut. Aber dein Team besteht nicht nur aus Lukas. Vor allem mit dem neuen Kollegen Jens sprichst du sehr unfreundlich und bist sehr reizbar.“
Christian: „Weil Jens mir auch auf die Nerven geht. Ich arbeite jetzt seit sieben Jahren als Entwickler und er kommt direkt von der Uni.“
Ruben: „Was stört dich daran? Und was ist bei Lukas anders?“
Christian: „Lukas hat’s einfach drauf, der Neue nicht! Er kommt ständig mit irgendwelchen Vorschlägen, die Quatsch sind.“
Ruben: „Warum meinst du, dass sie Quatsch sind?“
Christian: „Weil man das nicht so ,quick and dirty‘ macht, sondern direkt richtig!“
Ruben: „Ist das alles, was dich stört?“
Christian: „Naja, durch Jens habe ich auch gemerkt, dass ich in unserem Laden einfach nicht vorankomme ...“
Ruben: „Wie meinst du das?“
Christian: „Ich arbeite jetzt seit sieben Jahren hier und bin auf derselben Stufe wie Jens. In einer anderen Firma wäre ich längst Senior Developer und könnte ihm ab und zu mal sagen, dass er einfach die Klappe halten soll – und das machen, was Lukas und ich ihm sagen. Stattdessen diskutieren wir ständig mit ihm.“
Ruben: „Und du meinst, dass ein Jobtitel etwas daran ändern würde? Hättest du dir vor sechs bis sieben Jahren von irgendwelchen Senior Developern Vorschriften machen lassen?“
Christian: „Nö. Ich hatte aber auch nicht so wenig Ahnung!“
Ruben (lacht): „Das lasse ich jetzt mal so stehen. Ich habe nämlich noch eine andere Frage.“
Christian: „Und die wäre?“
Ruben: „Warum du nicht gut auf Jens zu sprechen bist, haben wir jetzt geklärt. Du hast diese Woche aber auch Julia ganz schön angeblafft, als sie dich um Hilfe gebeten hat.“
Christian: „Wegen ihres blöden Frontend Builds? Damit ging sie mir auch ziemlich auf den Geist. Was kann ich denn dafür, dass sie so einen JavaScript-Mist verwendet und dann nicht in die Build-Pipeline reinbekommt.“
Ruben: „Du kennst dich aber mit dem Build-System gut aus ...“
Christian: „Ich bin aber kein Frontend-Entwickler, Julia schon. Ich habe auch genug anderes zu tun.“
Ruben: „Du hättest also am liebsten „Senior Backend Developer“ als Titel?“
Christian: „Ja, das klingt gut!“
Christians Team hat Verstärkung bekommen. Eigentlich sollte das eine gute Nachricht sein, aber er ist sehr verärgert über den neuen Kollegen – aus zwei Gründen. Zum einen „stresst“ das neue Mitglied das Team. Christian und Lukas sind aufeinander eingestellt und haben ihr Vorgehen beim Lösen von Problemen und beim Entwickeln von Software aufeinander abgestimmt. Für die beiden fühlt sich dieses Vorgehen gut und richtig an, objektiv gesehen muss es das nicht immer sein. Jens bringt jetzt frischen Wind ins Team und „stört“ die beiden in ihrem Paarlauf. Nicht ohne Grund gibt es die Faustregel, dass ein Team seinen Teamvertrag mit jedem neuen Mitglied neu justieren sollte [1].
Zum anderen ist Christian verärgert, weil er sich Jens vor allem in der Softwareentwicklung überlegen fühlt. Durch den neuen Kollegen wird ihm klar, dass er durch diese Überlegenheit eigentlich Sonderrechte haben möchte, aber nicht hat. Jens’ Meinung hat genauso viel Gewicht wie seine eigene. Er will gerne einen Titel haben, der seine Überlegenheit dokumentiert und es ihm erlaubt, die von Jens ausgehenden Störungen zu minimieren.
Agile Teams leben davon, dass die verschiedenen Sichten und Informationen der Teammitglieder zu einem Gesamtbild zusammengefügt werden [2]. Außerdem strebt das ideale agile Team nach steter Verbesserung (Inspect and Adapt in Scrum). Diese beiden Grundgedanken lassen sich nur umsetzen, wenn alle Meinungen im Team gleich viel wert sind und nicht der Jobtitel darüber entscheidet, welche Meinung gewinnt. Nach Larman und Vodde ist dies der eine negative Effekt, den Jobtitel auf agile Teams haben können (Hierarchy: „Ich glaube dem Senior mehr“) [3]. Der zweite negative Effekt taucht ebenfalls im Gespräch zwischen Christian und Ruben auf. Christian sagt, er sei Backend-Entwickler und habe mit Julias Problemen nichts zu tun (Specialization). Je spezifischer die Jobtitel sind, desto schneller gerät in Vergessenheit, dass Teams immer nur als Ganzes gewinnen können. T-shaped Personalities sind das Ziel.
Außerdem führt der Verweis auf den Titel bei Mitarbeitern wie Jens über kurz oder lang zu Frust, wenn sie immer unbegründete Bastaentscheidungen der Kollegen umsetzen müssen. Christian gibt ja sogar zu, dass er sich von Senior-Entwicklern früher auch nichts hätte sagen lassen. Im siebten der „Ten Commandments of Egoless Programming“ [4], [5] ist das Prinzip echter Führung sehr treffend zusammengefasst: „Echte Autorität fußt auf Kenntnissen, nicht auf (hierarchischer) Position“.
Christian ist verärgert, weil man ihn nach sieben Jahren Betriebszugehörigkeit nicht mit einem Titel als Senior Developer dekoriert hat. Er lässt offen, was für ihn genau einen solchen Entwickler ausmacht. Dieser Frage widmet sich ein Blogpost mit dem Titel „On Being A Senior Engineer“ [6]. Zunächst einmal beschreibt der Autor dieses Texts seine Schwierigkeiten mit dem Begriff „Senior“. Wenn Christian nach sieben Jahren Senior Developer wäre, was möchte er dann nach zehn Jahren sein? Super Senior Developer? Vice President Technology? Der Autor schlägt vor, eher von „Mature Developer“ zu sprechen. Dieser Mature Developer ist aber kein Titel, sondern ein Zielbild mit bestimmten Eigenschaften.
Einige dieser Eigenschaften scheinen Christian zu fehlen. Zusammenfassend wird dieser Entwickler als jemand beschrieben, mit dem die Kollegen gerne zusammenarbeiten. Jens arbeitet vermutlich nicht so gerne mit Christian zusammen, weil dieser ihn immer seine vermeintliche Überlegenheit spüren lässt, und auch Julia kann sich spätestens seit dieser Woche vermutlich angenehmere Gesprächspartner vorstellen. Es ist völlig unerheblich, wie viel Christian weiß und kann, wenn niemand mit ihm arbeiten möchte.
Aus Sicht des Bloggers zeichnet einen Mature Developer eine „Good-enough-for-now“-Mentalität aus. Im Gegensatz zu Christian ist er der Meinung, dass man Software nicht immer „gleich richtig“ machen kann, sondern sie sich Stück für Stück entwickelt („Premature optimization is the root of all evil.”).
Ein solcher Entwickler ist sich auch darüber im Klaren, dass unsere menschliche Wahrnehmung kognitiven Verzerrungen (Cognitive Biases) [7] unterliegt. Christians Argumentation deutet auf mindestens zwei dieser Verzerrungen hin. Zum einen auf „Self-serving Bias“: Was er selbst tut, ist per definitionem immer besser als das, was Jens tut. Zum anderen auf einen „Fundamental Attribution Error“: Die Fehler, die andere machen – wie Julia oder Jens –, liegen immer an deren Person, seine eigenen Fehler immer am Kontext.
Jobtitel scheinen gefährlich für (agile) Teams zu sein. Wie aber sieht die Alternative aus? Sollte eine Organisation vollständig auf Titel verzichten? Larman und Vodde schlagen folgende Optionen vor [3]:
generisch: Alle Mitarbeiter tragen generische Jobtitel wie „Entwickler“ oder sogar „Mitarbeiter“.
selbstgewählt: Die Mitarbeiter sind frei in der kreativen Ausgestaltung ihrer Titel. Je lustiger desto besser (z. B. „Retro-Queen“, „DB-Gott“ ...)!
Levels: Wenn es aus irgendwelchen Gründen zwingend eine Unterscheidung geben muss, sollte man Levels verwenden (z. B. „Entwickler 1“, „Entwickler 2“ ...).
Diese drei Optionen funktionieren intern meist sinnvoll, führen bei der Kommunikation mit der Außenwelt aber zu Irritationen. Die Autoren schlagen deshalb vor, die internen auf externe Jobtitel abzubilden. Wenn ein Mitarbeiter das Unternehmen verlässt, kann der neue Arbeitgeber ihn anhand des externen Titels entsprechend einstufen und seine Erwartungen definieren.
Ruben hat Christian im weiteren Verlauf ihres Gesprächs vom Zielbild des Mature Developers erzählt und ihn insbesondere auf kognitive Verzerrungen hingewiesen. Sie haben sich auf ein gemeinsames Gespräch mit Jens geeinigt, in dem Christian mal explizit aussprechen kann, was ihn stört. So bekommt Jens die Chance, sich zumindest ein wenig in Christian hineinzuversetzen. Ruben hat Christian angeboten, ihm in Vier-Augen-Gesprächen regelmäßig sein Verhalten zu spiegeln und mit ihm auf das Zielbild eines Mature Developers hinzuarbeiten.
Konstantin Diener ist CTO bei cosee. Der Titel findet nur extern Verwendung. Jobtitel haben bei cosee keine praktische Relevanz. Mit einer Ausnahme: Die guten Feen bei cosee bezeichnen sich selbst als „Tinkas“ (in Anlehnung an Disneys Tinker Bell)
[1] Diener, Konstantin: „So wird hier gearbeitet!“; Kolumne „DevOps Stories“; Java Magazin 7.2017
[2] Diener, Konstantin: „Technologieentscheidungen an der Basis“, Kolumne „DevOps Stories“; Java Magazin 5.2017
[3] Larman, Craig; Vodde, Bas: „Scaling Lean & Agile Development: Thinking and Organizational Tools for Large-Scale Scrum“; Addison-Wesley, 2008
[4] Wyatt, Stephen: „Dad and the Ten Commandments of Egoless Programming“: http://blog.stephenwyattbush.com/2012/04/07/dad-and-the-ten-commandments-of-egoless-programming
[5] Weinberg, Gerald M.: „The Psychology of Computer Programming”; Dorset House, 1998
[6] https://www.kitchensoap.com/2012/10/25/on-being-a-senior-engineer/
„Jede hinreichend fortschrittliche Technologie ist von Magie nicht zu unterscheiden.“ Dieses Zitat von Arthur C. Clarke trifft auf vieles zu, was ein modernes Build-Skript stellenweise leistet. In dieser Folge unserer Kolumne lüften wir das Geheimnis und erklären einige der nützlichen Gradle-Features, die Sie für Ihre Dokumentation verwenden können. Sollte das Ihr erstes Date mit Gradle sein, empfehlen wir zuerst die Lektüre einer entsprechenden Einführung [1].
Listing 1 zeigt ein typisches Build-Skript für eine Spring-Boot-Anwendung, erstellt über den Spring Initializr [2]. Auffällig sind die doppelten Abschnitte repositories und dependencies. Der erste der beiden – innerhalb des buildscript {}-Abschnitts – ist für die Abhängigkeiten während des Builds zuständig und somit für Sie interessant. Der zweite Abschnitt definiert die Abhängigkeiten des Codes, der gebaut wird. Da Sie Dokumentation bauen, werden Sie diesen Abschnitt nicht benötigen.
Listing 1: Spring Boot Build
buildscript {
ext {
springBootVersion = '1.5.10.RELEASE'
}
repositories { mavenCentral() }
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'groovy'
apply plugin: 'org.springframework.boot'
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories { mavenCentral() }
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.codehaus.groovy:groovy')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
Auch der apply-plugin-Abschnitt wird für den Docs-as-Code-Ansatz benötigt. Listing 2 zeigt einen Ausschnitt aus dem docToolchain-build.gradle-File [3], in dem das AsciiDoc-Plug-in definiert und über die Abhängigkeiten um das Diagramm- und PDF-Plug-in ergänzt wird (ja, auch htmlSanityCheck wird als Plug-in referenziert).
Listing 2: Asciidoctor Dependencies
plugins {
id "org.asciidoctor.convert" version "1.5.3"
id "org.aim42.htmlSanityCheck" version "0.9.7"
}
dependencies {
asciidoctor 'org.asciidoctor:asciidoctorj-pdf:1.5.0-alpha.15'
asciidoctor 'org.asciidoctor:asciidoctorj-diagram:1.5.4.1'
}
Für die AsciiDoc-Plug-ins ist dabei die Definition mit asciidoctor wichtig, damit alles funktioniert. Sie wird anstelle der Ausdrücke genutzt, die man von Java her kennt, wie compile oder runtime.
Gradle bringt viele Tasks für die gängigsten Aufgaben eines Build-Systems mit. Wir wollen Ihnen hier kurz zeigen, wie Sie zu ganz individuellen Build-Schritten Ihres Docs-as-Code-Projekts kommen.
Um ein Gradle-Skript zu erweitern, können Sie einen eigenen Gradle-Task schreiben. Listing 3 zeigt dafür ein Grundgerüst. Solche Tasks schreiben Sie über eine Groovy-basierte DSL (Domain Specific Language).
Listing 3: „helloWorld.gradle“
task helloWorld (
description: 'just a greeting',
group: 'demo',
dependsOn: [],
) doLast {
10.times { i ->
println "Hello World"
}
}
Die Attribute description und group dienen der Dokumentation. Mit dem Befehl gradle tasks erhalten Sie eine Liste aller Tasks mit Namen und description nach group geordnet. Möchten Sie einen Task als internen Task verstecken, dann setzen Sie das Attribut group auf null – das ist oft hilfreich, wenn Sie vor mehreren ähnlichen Build-Schritten den gleichen vorbereitenden Schritt ausführen möchten. So generiert die docToolchain das Zwischenformat .docBook für .epub und .docx, was wir ansonsten nicht benötigen.
Das Attribut dependsOn enthält eine Liste von Tasks, die vor dem hier definierten Task ausgeführt werden müssen. Sollte es sich um nur einen einzelnen Task handeln, könnten Sie das einfach als String definieren – aber verwenden Sie zur besseren Lesbarkeit lieber eine Liste (in Listing 3 ist diese leer).
Der doLast-Codeblock wird in die Liste der auszuführenden Aktionen des Tasks hinten eingereiht. Theoretisch können Sie mehrere solcher Blöcke definieren, und auch das Statement doFirst ist vorhanden. In der Praxis reicht aber meist ein solcher Block, da Sie beliebigen Code hineinpacken können. Mit gradle helloWorld können Sie diesen Task nun ausführen.
Da Sie auch im Docs-as-Code-Ansatz, genauso wie beim Bauen Ihrer Anwendung, mit Dateien arbeiten und nur die veränderten Dateien neu bauen wollen, fehlt jetzt noch die Definition der Input- und Outputfiles, damit Gradle überprüfen kann, ob es die entsprechenden Tasks überhaupt ausführen muss. Dies steuern Sie über die Schlüsselwörter inputs und outputs zur Konfigurationsphase des Tasks. Ein Beispiel sehen Sie in Listing 4 (vgl. Kasten: „Linter“). Alle Zeilen außerhalb des doLast-Blocks werden in der Konfigurationsphase ausgeführt. Durch die Angabe des Inputverzeichnisses und des Outputfiles kann Gradle nun entscheiden, bei unveränderten Dateien den Task nicht auszuführen. Probieren Sie es aus, indem Sie das Skript zweimal aufrufen.
Listing 4: Textanalyse
ext {
docsDir = new File('./src/docs')
reportFile = new File(
project.buildDir,
'report.txt'
)
}
task analyzeText (
description: 'einfache Textanalyse',
group: 'docToolchain',
dependsOn: [],
) {
inputs.dir docsDir
outputs.files reportFile
doLast {
def srcFolder = docsDir
reportFile.write("Analyse Ergebnis: \n", "UTF-8")
srcFolder.eachFile { file ->
if (file.name.endsWith('.adoc')) {
println "analysiere " + file.name
file.text.split("[^a-zA-Zäöü]")
.each { word ->
if (word.endsWith("bar")) {
reportFile.append(
"> Warning: bitte ersetze " +
"'${word}' durch " +
"'es lässt sich " +
"${word - 'bar' + 'en'}' \n",
"UTF-8"
)
}
}
}
}
}
}
defaultTasks 'analyzeText'
In Listing 4 haben wir zum Spaß eine kleine Textanalyse implementiert. Was Sie für Ihren Code als Linter kennen (also im weitesten Sinne eine Analyse des Schreibstils), gibt es auch für Ihre Dokumentation. Chris Ward hat in einem Blogbeitrag eine Liste von Linters für englische Texte zusammengetragen [4].
DRY (don’t repeat yourself) gilt auch bei Docs as Code. Einige Eigenschaften Ihrer Tasks werden Sie an mehreren Stellen wiederholen, wie z. B. das aktuelle Datum der Erstellung Ihres Dokuments oder den Namen des Autors. Sie können Gradle-Build-Skripte auf vielfältige Weise parametrisieren. Die wichtigsten sind aber wohl das gradle.properties-File und die in build.gradle definierten zusätzlichen Werte, genannt Properties.
Das gradle.properties-File erstellen Sie einfach im gleichen Verzeichnis wie das build.gradle-Skript. Die Extra Properties [5] definieren Sie in einem speziellen ext-Block des Build-Skripts, wie in den ersten Zeilen von Listing 4 zu sehen.
All diese Properties referenzieren Sie nun aus Ihrem Task als wären es lokale Variablen. Ob die Properties im gradle.properties-File oder als Extended Properties definiert wurden, macht dabei keinen Unterschied. Sie sind bei der Definition auch nicht auf Strings beschränkt, wodurch Sie Ihren Build lesbarer gestalten können – auch hierzu haben wir ein Beispiel in Listing 4 und Listing 5 eingebaut.
Damit Sie auch in umfangreichen Build-Skripten den Überblick behalten, sollten Sie die einzelnen Build-Schritte in sogenannten Skript-Plug-ins organisieren. Solche Schritte sind Taskdefinitionen, die Sie in einzelne Dateien auslagern (sprich: modularisieren). In Ihrer build.gradle-Datei referenzieren Sie diese einzelnen Skript-Plug-ins (= Dateien) über apply: 'helloWorld.gradle'. Unserer Ansicht nach genügt diese einfache Modularisierung, um Ihren Build verständlich zu strukturieren.
Listing 5: „gradle.properties“
helloName = Universe
currentDate = new Date().format('dd.mm.yy')
In der letzten Folge haben wir Ihnen ein paar fortschrittliche Browser und IDEs vorgestellt, die eine Livepreview Ihrer Dokumentation anbieten. Immer, wenn Sie die Quellen Ihrer Dokumentation ändern, wird die Voransicht neu aufgebaut.
Was aber, wenn Ihr Lieblingseditor das nicht unterstützt? Gradle holt in diesem Fall zusammen mit einem modernen Browser ein Ass aus dem Ärmel: Continuous Builds.
Starten Sie in einer Shell die AsciiDoc-Konvertierung mit ./gradlew -t asciidoctor (-t steht für für --continuous). Gradle wird nun Ihre Dokumentation als HTML generieren. Öffnen Sie die generierte Dokumentation mit Chrome, um sich davon zu überzeugen. Gradle beendet den Build aber nicht, sondern wartet nun auf Änderungen an den Quelldateien. Öffnen Sie die Quelldatei in einem Editor und beobachten Sie, was bei einer Änderung passiert. Richtig – nicht viel. Außer dass Gradle die HTML-Ausgabe neu generiert. Wenn Sie jetzt in Chrome das LiveReload-Plug-in installieren (in den Einstellungen das Häkchen bei Allow Access to file URLs setzen!) und in einer weiteren Shell den Befehl ./gradlew livereload absetzen, können Sie Chrome über das Plug-in mit dem von Gradle gestarteten LiveReload-Server verbinden. Eine Änderung an der Quelldatei stößt nun einen Re-Build an, was den Browser zum Neuladen der Seite veranlasst. Gefunden haben wir dieses Beispiel übrigens im Asciidoctor-Examples-Repository [6].
Mit den hier vorgestellten Ansätzen können Sie die AsciiDoc-Konvertierung sehr einfach und modular um eigene Build-Schritte erweitern. Der Automatisierung Ihrer Dokumentation steht somit nichts mehr im Weg. Noch mehr Tipps und Tricks zu diesem Thema finden Sie im Blog von Hubert Klein Ikkink [7].
Ralf D. Müller arbeitet als Architekt und Entwickler. Er erlebt täglich die Notwendigkeit effektiver Dokumentation. Er ist erklärter AsciiDoc-Fan und Committer bei arc42 sowie Gründer des docToolchain-Projekts [3].
Dr. Gernot Starke (innoQ Fellow) verbessert seit Jahren Softwarearchitekturen und deren Dokumentation. Er ist Mitgründer und Betreiber von http://arc42.org und http://aim42.org.
[1] Einstieg in Gradle: https://gradle.org/guides/
[2] Spring Initializr: http://start.spring.io
[3] Die freie Toolkette für Doc As Code – docToolchain: https://github.com/docToolchain/
[4] Linter für englische Texte: https://dzone.com/articles/lint-lint-and-away-linters-for-the-english-languag
[5] Extra Properties mit Gradle: https://docs.gradle.org/current/userguide/writing_build_scripts.html#sec:extra_properties
[6] Das Asciidoctor-Examples-Repository: https://github.com/asciidoctor/asciidoctor-gradle-examples/
[7] HAKI, Blog Hubert A. Klein Ikkink zu Gradle: http://mrhaki.blogspot.de/search/label/Gradle%3AGoodness
Vor mehr als zehn Jahren erblickte Scala das Licht der Softwarewelt, oder besser, die Softwarewelt erblickte Scala. Martin Odersky hatte sie im Rahmen seiner Professur an der Schweizer Hochschule EPFL (https://epfl.ch/) entwickelt. Er suchte damals nach Verbesserungen für Java, heraus kam eine hybride Programmiersprache. 2004 ging die erste Version an den Start. Die Reaktionen auf Scala fielen sehr unterschiedlich aus. Die einen sprangen begeistert auf den objektfunktionalen Zug auf, die anderen hielten die Sache für zu kompliziert, zu abgehoben, zu akademisch.
Inzwischen ist Scala natürlich längst eine feste Größe in der Softwarentwicklung. Es ist aus seinen Kinderschuhen herausgewachsen und hat sich seinen Teil des Java-Universums erobert. Kein Wunder – Heiko Seeberger bezeichnet Scala in seinem Buch „Durchstarten mit Scala – Tutorial für Einsteiger“, erschienen bei entwickler.press, als „eine moderne und dennoch reife, objektfunktionale, praxisorientierte, statisch typisierte und trotzdem leichtgewichtige Sprache für die JVM, die vollständig kompatibel zu Java ist.“ Der Sprachhybrid bringt gewiss einige nützliche Dinge mit.
In dieser Ausgabe des Java Magazins schauen wir uns an, wie sich altbekannte Probleme mit Ansätzen aus der abstrakten Algebra lösen lassen. Markus Hauck spricht ab Seite 20 darüber, wie funktionale Programmierung Entwicklern im Alltag helfen kann. Des Weiteren werfen wir mit Markus Günther einen Blick auf ein das Thema Property-based Testing in Verbindung mit ScalaCheck (S. 29). Das Tool ergänzt den Werkzeugkasten des Entwicklers um eine mächtige Testmethodik.
Außer vielen spannenden Artikeln zu Scala, funktionaler Programmierung, Java 10 und vielem mehr bringt das Java Magazin diesmal außerdem etwas ganz Neues mit. Zukünftig wird es in jedem Heft einen Teil namens „Sag mal … mit Carina & Niko“ geben. Eine redaktionelle Spielwiese, auf der ich mich zu verschiedenen Themen aus der Welt der Softwareentwicklung zusammen mit meinem technischen Berater Niko Köbler austoben kann. In Folge eins unserer Serie kümmern wir uns um Lizensierung in Java und die Frage: „Sag mal … wie läuft das denn jetzt mit dem Support?“
Ich wünsche viel Freude beim Lesen!
Schneller und schneller dreht sich die Welt der Technologien: Projekte veröffentlichen immer häufiger aktuelle Versionen, neue Programmiersprachen bzw. Modelle werden im Monatsrhythmus ent- und auch wieder verworfen, und natürlich muss sich auch Eclipse an diese modernen Zeiten anpassen. Genau darum geht es in dieser Ausgabe der Eclipse-Kolumne.
Wer in den letzten Jahren die Tätigkeiten von Oracle rund um JavaFX genauer beobachtet hat, der hat sicherlich mitbekommen, dass Oracle immer weniger in diese Technologie investiert hat. Immer wieder gab es aufgeregte Forderungen von JavaFX-Begeisterten an Oracles Adresse, endlich klarzustellen, was man mit JavaFX eigentlich vorhabe. Das hat Oracle nun getan, leider fällt das Urteil aber nicht zum Vorteil von JavaFX aus. Ab Java 11 wird JavaFX aus dem JDK ausgegliedert. Wer denkt, dass das noch eine ganze Weile dauern wird, irrt. Oracle hat nämlich beim Veröffentlichen neuer Versionen der Sprache zum Glück auf ein viel schnelleres Modell umgestellt. So kommt es, dass Java 11 bereits im September dieses Jahres fällig ist.
Danach garantiert Oracle nur noch den kommerziellen Support bis 2022. Und danach? Das steht natürlich noch in den Sternen, aber zumindest Oracle scheint sich dann endgültig zurückzuziehen. Wenn sich hier kein neuer finanzstarker Investor findet, wird JavaFX wahrscheinlich den gleichen Weg wie Hudson und OpenOffice gehen, zwei Open-Source-Projekte, die Oracle ja so schlecht geführt hat, dass sie de facto nicht mehr existent sind, bzw. durch neue Projekte (Jenkins und LibreOffice) weitergeführt werden.
Damit stehen Kunden, die auf JavaFX gesetzt haben, jetzt wohl leider im Regen, und als aktiv entwickeltes Java-UI-Framework gibt es dann eigentlich nur noch das Standard Widget Toolkit (SWT). Schade für Kunden, die hier den früheren Aussagen von Oracle vertraut haben, aber aus Sicht des Autors wieder mal ein Grund, lieber auf eine Technologie zu setzen, die nicht von einem Hersteller alleine kontrolliert wird.
Dunkel ist trendig. Moderne Editoren wie Visual Studio Code kommen deshalb auch standardmäßig mit einem Dark Theme daher. Das Standard-Dark-Theme von Eclipse, das über die Jahre ausgebaut wurde, war ... okay. Okay heißt aber eben leider nicht gut. Zum Glück haben das Dark Theme und die CSS Engine sich in den letzten Jahren allerdings so weit gemausert, dass immer mehr Eclipse-Platform-Entwickler das Dark Theme auch tatsächlich nutzen bzw. nutzen wollen. Das ist auch der Grund, warum an dieser Stelle recht viel passiert. Schaut man sich das New-and-Noteworthy-Dokument 4.8 M6 von Eclipse an, steht gefühlt beinahe jeder Punkt in Verbindung mit dem Dark Theme und dem Stylingthema [1]. Als Beispiel für die Verbesserungen im Styling kann man etwa die Farbe des Hypertexts im Javadoc Hover anführen, die mit dem Update richtig gut aussieht (Abb. 1 und 2).
Diese und viele andere Änderungen haben dazu geführt, dass der Autor dieser Zeilen tatsächlich endgültig auf das Dark Theme umgeschaltet hat. Toll ist auch zu sehen, dass Entwickler von Red Hat (z. B. Roland Grunberg und Lucas Bullen), SAP (z. B. Matthias Becker), IBM (z. B. Daniel Megert), der vogella GmbH und freie Committer wie etwa Conrad Groth und Till Brychcy gemeinsam daran arbeiten, das Theme zu verbessern. Zudem haben sich auch neue Contributors wie Robert Munteanu in die Arbeit eingeklinkt.
Wer es dann noch ein wenig besser (aber nicht Open Source) haben will, installiert sich am besten gleich das Darkest Dark Theme von Genuitec [2], ein tolles und liebevoll gestaltetes Theme. Es ist jedoch sehr schade, dass Genuitec sich anscheinend dafür entschieden hat, nichts in Sachen Eclipse-Plattform beitragen zu wollen, sondern, soweit notwendig, Änderungen über Class Loading Hacks durchzuführen. Und das, obwohl die Firma von einer kommerziellen Eclipse-Distribution lebt. Dies ist gleich aus aus zweierlei Sicht schade: Genuitec verbessert damit nicht die Plattform, von der das Unternehmen lebt, und User haben immer dann Probleme, wenn sich die Plattform verändert und das Class Loading Hacking dann doch nicht mehr funktioniert. Zum Beispiel berichten Nutzer des Darkest Dark Theme häufiger von doppelten Einträgen in der Toolbar, wenn sie das Release wechseln. Oft trägt Darkest Dark Theme von Genuitec wegen der erwähnten Hacks die Schuld daran.
Separate Erwähnung verdient der Patch von Nikita Nemkin im SWT Windows Port [3]. Nikita hat ca. 18 000 Zeilen Code gelöscht, indem er die Unterstützung für das von Microsoft und Oracle Java schon seit Jahren nicht mehr unterstützte Windows XP entfernte. Nach relativ kurzer Diskussion im Eclipse PMC wurde beschlossen, die Unterstützung von Windows XP auch für die Eclipse IDE offiziell nicht mehr zu gewährleisten. Windows-Benutzer profitieren dadurch von einer kleineren, damit besser wartbaren und möglicherweise auch schnelleren Codebasis, da viele Checks des Betriebssystems weggefallen sind. SWT-Entwickler können damit die Codebasis des SWT für Windows schneller und einfacher weiterentwickeln. Aus einer Konversation mit Nikita weiß der Autor, dass dieser noch viele grundlegende Verbesserungen für das SWT für Windows geplant hat. Das lässt doch auf ein noch besseres Standard Widget Toolkit in der Zukunft hoffen.
Während viele an einer hübschen Eclipse IDE arbeiten, befasst sich Mickael Istria von Red Hat mit der grundlegenden Performance der Entwicklungsumgebung. Für Eclipse 4.8 hat er die Eclipse Builder parallelisierbar gemacht, d. h. unterschiedliche Tools können jetzt parallel Code umwandeln. So könnte das Spring Tooling beispielsweise Spring Beans lesen und entsprechende Marker schreiben, der Java-Compiler den Java Sourcecode umsetzen und XML-Validatoren könnten die schnöden XML-Dateien überprüfen – gleichzeitig.
Das Ganze ist noch in einer frühen Phase, da nun auch die Builder angepasst werden müssen. Zurzeit holen sich die meisten (z. B. der Java Builder) einen sogenannten Workspace Lock und laufen somit allein. Aber die Hoffnung ist, dass sich das in Zukunft ändert, da damit der Build-Prozess in Eclipse dann sehr viel schneller werden würde.
Wie schon in einem vorherigen Teil der Kolumne angedeutet, hat sich das Eclipse-Projekt entschlossen, häufiger Releases herauszugeben. Das Planungskommittee des Eclipse-Release hat nun offiziell beschlossen, alle 13 Wochen (d. h. alle 3 Monate) ein neues Release herauszubringen. Nach Eclipse 4.8 (Photon) im Juni 2018 wird es also schneller vorangehen.
Das sind natürlich tolle Nachrichten für alle Nutzer der Eclipse IDE, die damit immer die neuesten Features in einem offiziellen Release zur Verfügung gestellt bekommen. Aber auch Kunden der Eclipse RCP, die die Eclipse-Plattform für ihre hauseigenen Applikationen nutzen, können sich freuen, da sie jetzt viel leichter von Verbesserungen oder selbst beigesteuerten Patches profitieren.
Die Eclipse-Entwickler schrauben auch weiter am Framework, das SWT wird weiterentwickelt und von Altlasten befreit. Zusätzlich gibt es bald alle drei Monate eine neue Version der Eclipse IDE und des damit verbundenen Frameworks. Gute Nachrichten für die Nutzer der Eclipse IDE und auch für Kunden der Eclipse Rich Client Platform.
Lars Vogel ist Geschäftsführer der vogella GmbH, die Kunden im Bereich Eclipse, Android und anderen Themen unterstützt. Als Project-Management-Committee-Mitglied, Eclipse-Projektleiter und Committer hilft er dem Eclipse-Projekt.
[1] Eclipse Photon (4.8) M6 – New and Noteworthy: https://www.eclipse.org/eclipse/news/4.8/M6/
[2] Darkest Dark Theme: https://marketplace.eclipse.org/content/darkest-dark-theme-devstyle/
[3] Eclipse Bugzilla – Bug 531097: https://bugs.eclipse.org/bugs/show_bug.cgi?id=531097
Seit dem 20. März ist Java 10 da, angekommen im Entwickleralltag ist es sicherlich noch nicht. Von der Veröffentlichung einer neuen Version bis zum tatsächlichen Einsatz vergeht schon einmal etwas Zeit. Der ein oder andere dürfte sich sicherlich auch noch vor dem Modulsystem in Java 9 scheuen. Kommt hinzu, dass Release nicht gleich Release ist. Nicht jedes bringt einen Long Term Support mit.
Anlässlich der Veröffentlichung von Java 10 wollten wir in der Redaktion wissen, mit welcher Version die Welt da draußen im echten Leben eigentlich so arbeitet. Die Frage verlangt nach einem Quickvote auf Jaxenter [1]. Achtung, Achtung! Die kleine Befragung ist genauso ausgefallen, wie wir uns das schon gedacht haben. Die Eckdaten: Insgesamt haben rund 1 600 Jaxenter-Leser mitgemacht und den Java-Versionen 5, 6, 7, 8 und 9 ihre Stimme gegeben (Abb. 1). Ganz oben auf dem Treppchen steht wenig überraschend Java 8 mit 69,97 Prozent. Weit dahinter liegen Java 9 mit (doch) 13,20 Prozent und Java 7 mit (immer noch) 11,33 Prozent. Java 6 erreicht den vorletzten Platz (4,32 Prozent) und Java 5 ist erwartungsgemäß das Schlusslicht (0,56 Prozent). Und Java 10? Ach, lassen wir das. In Sachen Nutzungszahlen ein Urteil abzugeben, ist ohne valide Daten sicherlich schwierig. Einen Blick in die Zukunft wollen wir trotzdem riskieren.
Java 10 hat nicht die Welt verändert, keine Frage. Trotzdem bringt das Release einige nette Features mit. Das wahrscheinlich bedeutendste hört auf den Namen Local-Variable Type Interference (JEP 286). Typinferenz bedeutet, mithilfe der restlichen Angaben des Codes und der Typisierungsregeln auf Datentypen zu schließen. In der Konsequenz lässt sich eine Menge Schreibarbeit vermeiden. Der Quellcode speckt deutlich ab und die Lesbarkeit nimmt zu. Das könnte Ihnen bekannt vorkommen: „Wir kennen das in Java bereits bei Lambdaparametern und beim Diamond-Operator für generische Datencontainer bzw. seit Java 9 auch für anonyme innere Klassen“, schreibt Falk Sippach in seinem Beitrag zu den Java-10-Features [2].
Stichwort Java 9 – seit dem Release im September 2017 setzt Oracle auf Jigsaw und stellt die Community damit vor Herausforderungen. Jetzt der nächste Streich: Der Releasezyklus ändert sich. Bisher operierte man bei neuen Java-Versionen featuregetrieben. Damit ist jetzt Schluss: zeitbasiert lautet die neue Devise. Dieses Modell ist durchaus sinnvoll. Schon nach sechs Monaten wird Java 11 Java 10 ablösen. Dazwischen liegen nur zwei kleinere Public Releases. Die Community muss nicht länger auf kleinere Features warten. Zusätzlich ist Java 10, wie schon Version 9, im Gegensatz zu Java 11 recht kurzlebig. Erst mit Letzterem im September 2018 wird es wieder Long Term Support geben, allerdings nur für zahlende Kunden. Service Releases und Patches bekommen Unternehmen nur dann für einen längeren Zeitraum, wenn sie bei Oracle einen Supportvertrag abschließen (Abb. 2). Gerade größere Unternehmen, etwa Banken oder Industriekonzerne, werden den Releasemarathon ohne diesen nicht mitmachen können oder wollen. Oracles Director Java SE Product Management, Sharat Chander, stellt die berechtigte Frage: „When 10 comes out, what happens to 9? It’s no longer supported […] There’s no way for us to keep up with that level of support pace“ [3]. Alle sechs Monate eine drei Jahre supportete LTS-Version anzubieten ist viel zu viel Aufwand und für Unternehmen wie Oracle nicht machbar. Die Verwaltung solcher umfangreicheren Releases (bis zu sieben gleichzeitig zu unterstützende Versionen!) bremst zudem die Entwicklung auf anderen Gebieten. Angular beispielsweise setzt auf sehr engmaschige Releases. Nahezu jede Woche liefert Google kleinere Patches zu Angular aus, monatlich Minor Releases und alle halbe Jahre eine Major Version, eine LTS-Version gibt es nur einmal im Jahr [4]. In der Angular-Community stößt dieser Releaseplan auf offene Ohren. „Es ist wohl ein Mittelweg, der die Anforderungen aller Parteien (Angular-Team, große Firmen, agile Firmen etc.) gut genug bedient“, erklärt Angular-Experte Manfred Steyer.
Für das Oracle JDK gibt es einen LTS, für das Open JDK nicht. Wer in Zukunft auf frei verfügbare Open-JDK-Versionen setzt, läuft Gefahr, wichtige Sicherheitspatches nicht oder nur deutlich verzögert in seiner Umgebung nutzen zu können. Stellen wir uns dieses Szenario vor: Wenn wir heute eine Applikation mit JDK 10 in Produktion bringen, und in einem Jahr wird ein dann auftretendes Sicherheitsloch gestopft, haben wir mit Open JDK das Nachsehen. In einem Jahr werden wir bei Java 12 sein, der Patch läuft allerdings erst in den Entwicklungsbranch des nächsten Major Release und wird dann schließlich erst mit 13 verfügbar sein. Sehen Sie das Problem? Damit müssen wir dann noch fast ein halbes Jahr warten, bis ein entsprechendes Update kommt. Doch damit genug, wir müssen von 10 auf 13 springen. Die Alternative wäre, den Patch selbst auf die laufende Java-Version rückwärts zu portieren und damit ein selbstkompiliertes JDK einzusetzen (Kasten: „Lieber lassen“). Zwei Faktoren sind nicht ganz ohne: Der mit einer glühenden Nadel gestrickte Workaround kostet nicht nur Zeit, sondern gleicht einem Tanz auf einem Vulkan. Wer will schon die Verantwortung für ein selbst gepachtes System übernehmen? Dazu Sharat Chander: „You’re missing out on all the performance benefits, all the security improvements by staying on an olden version“ [3].
Nehmen wir beispielsweise an, in Java 12 taucht ein Bug auf. An sich schon suboptimal, doch noch schlimmer wird es, wenn wir versuchen, ihn selbst zu fixen. Wir arbeiten mit Java 10, um einen eigenen Patch zu stricken, brauchen aber gegebenenfalls die Codeschnipsel aus 11 und 12. Alternativ können wir versuchen, den Patch auf den für Java 10 relevanten Teil zu reduzieren. Richtig, das klingt nach viel Arbeit und ist ziemlich heikel. Dieser Workaround ist brandgefährlich, da wir nicht ausschließen können, dass unsere Flickschusterei ein Loch an anderer Stelle aufreißt.
Kein Zweifel, Oracles Entschluss, etwas Schwung in die Entwicklung von Java zu bringen, war richtig und wichtig. Mitunter Jahre auf ein noch so kleines Feature warten zu müssen, macht einfach keinen Spaß und ist nicht mehr zeitgemäß. Zeitbasiert neue Java-Versionen auszuliefern, löst dieses Problem, verstärkt allerdings gleichzeitig auch ein anderes. Klar, Oracles Support kostet nicht erst seit Java 10 Geld. Wer für Support nicht bezahlen will, muss sich nun allerdings Gedanken machen. Die kürzere Lebensdauer der Releases zwingt Open-JDK-Nutzer jedoch, sich mit dem Thema Rückwärtskompatibilität auseinanderzusetzen. Entwicklern stehen zwei Wege offen: Entweder sie wechseln auf Oracles kostenpflichtiges Angebot oder springen von Release zu Release. Michael Vitz von innoQ erklärt, was das bedeutet: „Setzt man bereits auf das JDK9, so ist ein Upgrade jedoch Pflicht. Mit dem Erscheinen von JDK9 hat Oracle die Supportzeiträume geändert. JDK9 erhält mit dem Release von JDK10 nämlich keine Securityupdates mehr. Lediglich JDK8 als sogenanntes LTS-(Long-Time-Support-)Release erhält parallel zu JDK10 noch Updates. Ist man also aktuell noch auf JDK8, so kann es sich lohnen, auf JDK11 im September zu warten, da dieses das nächste LTS-Release wird“ [5]. Das Problem mit der Rückwärtskompatibilität löst sich dadurch nicht in Luft auf. Möglicherweise liegt es an der Java-Community, es zu lösen. AdoptOpenJDK beispielsweise stellt Java-Entwicklern vorkonfigurierte OpenJDK-Binärdateien aus einem vollständig offenen Satz von Build-Skripten und Infrastruktur zur Verfügung [6]. Das Projekt plant laut Steve Wallin, IBM Runtime Technologies Program Director, Änderungen an LTS-Versionen zurückzuportieren (Abb. 3) [7].
Unsere Redakteurin Carina Schipper und der Technical Advisor Niko Köbler beobachten die Welt der Softwareentwicklung und was in ihr vor sich geht. Sie sind immer auf der Suche nach Neuigkeiten, Wissenswertem und spannenden Geschichten.
[1] Quickvote „Welche Java-Verison nutzen Sie?“: https://jaxenter.de/quickvote-java-version-2-68515
[2] Sippach, Falk: „Java 10 ist da! Die neuen Features auf einen Blick“: https://jaxenter.de/java-10-features-news-69182
[3] Chander, Sharat: https://www.youtube.com/watch?v=YauqubC8FKM
[4] Angular-Releaseplan: https://github.com/angular/angular/blob/master/docs/RELEASE_SCHEDULE.md
[5] Vitz, Michael: „Java 10 – Evolution statt Revolution“: https://www.innoq.com/de/articles/2018/03/java-10/#fazit
[6] AdoptOpenJDK: https://adoptopenjdk.net/
[7] Wallin, Steve auf Twitter: https://twitter.com/stevewallin/status/964515481003667456
Pünktlich zum Start ins neue Jahr wurde am 3. Januar ein neues Release von MicroProfile veröffentlicht. In nur drei Monaten hat die Community, neben kleineren Erweiterungen bei schon vorhandenen APIs (Metrics 1.1 und Config 1.2), auch noch drei neue APIs ins Leben gerufen: OpenTracing 1.0, OpenAPI 1.0 und Type-safe Rest Client 1.0.
Mit OpenTracing wird damit das Tracing von Requests in der verteilten Welt von Microservices ermöglicht. Zur Dokumentation von REST Endpoints setzt sich mehr und mehr OpenAPI v3 durch. Dieser Tatsache geschuldet wurde MicroProfile OpenAPI 1.0 in den Standard integriert. Und last but not least kann der Entwickler mit Type-safe Rest Client 1.0 seinen REST-Client typsicher gestalten.
Für dieses Jahr sind zwei weitere Releases geplant. Mit MicroProfile 1.4 soll den Entwicklern stärker unter die Arme gegriffen werden, indem man Videotutorials und verbesserte Dokumentationen sowie Beispiele und somit notwendige Informationen für die tägliche Programmierarbeit zur Verfügung stellt. Damit soll die Integration der MicroProfile-APIs in die Microservices-Implementierung erleichtert werden. Und das ist noch nicht alles für das geplante Release am 23. März. Zur gleichen Zeit ist MicroProfile 2.0 angekündigt, das alle vorhandenen MicroProfile-APIs fit für Java EE 8 machen wird. Damit hält die Community das Versprechen, weiterhin am Jakarta-EE-Standard festzuhalten und keine neue Parallelwelt aufzubauen.
Eine grundsätzliche Herausforderung bei verteilen Systemen und somit auch bei Microservices-Applikationen ist es, Aufrufketten nachzuvollziehen. Da sich Requests teilweise über mehrere Microservices-Instanzen hinweg erstrecken können, ist es nicht ganz so einfach zu erkennen, welche Requests in den jeweiligen Microservices-Instanzen zusammengehören. Um dies zu erreichen, ist es notwendig, dass ein Request, der die Microservices-Applikation aufruft, mit einer Correlation-ID versehen wird, und diese dann bei jedem weiteren Aufruf an nachgelagerte Microservices mitgegeben wird. Diese so markierten Requests werden dann an ein zentrales Tracing-System übermittelt und dort gespeichert. Darüber hinaus bietet das Tracing-System Möglichkeiten zur Auswertung dieser Request-Ketten an.
Mit OpenTracing [1] hat sich ein Standard etabliert, der herstellerneutrale APIs definiert, um mit solchen Trace-Daten umgehen zu können. Damit werden die Anwendungen vom eingesetzten Tracing-System unabhängig. Es gibt bereits mehrere Tracing-Systeme, die an diesem Standard beteiligt sind, etwa Zipkin [2] oder Jaeger [3]. Auf der OpenTracing-Webseite sind weitere Systeme, die dem Standard folgen, aufgeführt.
Mit MicroProfile OpenTracing wird ein JAX-RS-basierender Microservice mit den Fähigkeiten des Tracings ausgestattet. Das bedeutet, er erzeugt automatisch die Correlation-ID beziehungsweise leitet sie an nachgelagerte Microservices weiter. Die Übermittlung der Trace-Informationen erfolgt unter Zuhilfenahme von OpenTracing an das jeweilige Tracing-System. Zusätzlich wird dafür gesorgt, dass die Aufrufkette (Span) mit einheitlichen Informationen arbeitet (SpanContext), da sonst einzelne Microservices, die mit diesen Informationen nicht umgehen können, von Span ausgeschlossen werden. Diese Services würden damit die Aufrufkette unterbrechen bzw. beenden. Zwei verschiedene Arten der Integration von MicroProfile OpenTracing in den Microservice werden unterstützt: entweder konfigurativ, das heißt ohne eine Zeile Anwendungscode schreiben zu müssen, oder programmatisch, indem man sich den Tracer mit einer Annotation (@Traced) injizieren lässt.
Für die Konfigurationsvariante schreibt MicroProfile OpenTracing vor, dass eine Implementierung von io.opentracing.Tracer für jede Applikation vom Applikationsserver zur Verfügung gestellt werden muss. Die Einstellungen des Tracers müssen von außen über Konfigurationseinstellungen im Applikationsserver gesetzt werden können. Das stellt sicher, dass ein Wechsel des Tracing-Systems keinen Einfluss auf den Microservice haben wird. Aus Sicht des Entwicklers ist das die Komfortfunktion, da er in seinem Microservice keine Zeile Code schreiben muss, um dies zu erreichen. Wer darüber hinaus noch den Bedarf hat, das Tracing selbst in die Hand zu nehmen, kann das mithilfe der Annotation org.eclipse.microprofile.opentracing.Traced. Diese Annotation lässt sich auf Klassen- oder Methodenebene anwenden und hat zwei optionale Argumente. Mit dem Argument value=[true|false] lässt sich das Tracing der Klasse bzw. Methode an- oder abschalten. Mit dem zweiten optionalen Argument operationName lässt sich die Standardbezeichnung (<HTTP method>:<package name>.<class name>.<method name>) des Spans verändern. Wollen wir noch weiter in die Art und Weise des Tracers eingreifen, können wir uns diesen auch injizieren lassen:
@Inject
io.opentracing.Tracer myTracer;
Hiermit eröffnet sich beispielsweise die Möglichkeit, weitere Spans innerhalb einer Businessmethode zu starten.
Die Trace-Ausgaben lassen sich auf dem Tracing-System persistieren und auswerten. Damit lässt sich auch noch nachträglich das Aufrufverhalten innerhalb der Microservices-Anwendung analysieren. Neben einigen Open-Source-Systemen können auch kommerzielle Hersteller wie Dynatrace [4] mit dem OpenTracing-Format umgehen.
Die OpenAPI-Spezifikation (OAS) ist ein Standard, der sich zum Ziel gesetzt hat, RESTful APIs, die von einem Service zur Verfügung gestellt werden, zu modellieren und zu dokumentieren. Diese Beschreibung soll dabei von Mensch und Maschine gleichermaßen verstanden werden. Diesem Fortschritt trägt MicroProfile mit der OpenAPI-1.0-Spezifikation Rechnung, indem sie dem Entwickler Möglichkeiten an die Hand gibt, aus seiner JAX-RS-Applikation heraus OAS-Dokumente zu generieren.
Ein Projekt, das den API-First-Ansatz verfolgt, mit einem geeigneten Editor (etwa Swagger [5]) eine OAS-Beschreibung seiner Services vorab erzeugt und dann mit der Implementierung der Services beginnt (oder die Service-Endpoints aus der OAS-Datei generieren lässt), kann auch von der neuen MicroProfile-Spezifikation profitieren, da eine Mischform möglich ist. Die statische OAS-Datei, die in der Anwendung hinterlegt worden ist (META-INF-Verzeichnis mit dem Dateinamen openapi.[.yml|.yaml|.json]), lässt sich mit der Bottom-up-Generierung kombinieren. Dabei wird nach spezifischen Annotations im Sourcecode gescannt und die vorhandene OAS-Datei damit angereichert. Aus der Kombination der beiden Informationsquellen können wir nun eine gemeinsame OAS-Datei erzeugen. Microservices, die nach dem Prinzip des API-First-Ansatzes entwickelt werden, können die Bottom-up-Generierung komplett deaktivieren, indem einfach das Konfigurationsattribut mp.openapi.scan.disable auf true gesetzt wird.
Bei der Bottom-up-Generierung ist es mitunter notwendig, auf Konfigurationswerte zurückzugreifen, beispielsweise geänderte Hostnamen. Um dies zu erreichen, setzt die MicroProfile-OpenAPI-Spezifikation auf MicroProfile Config auf. Das Verhalten des Scanvorgangs, der Inhalt und die Generierung der OAS-Datei lassen sich dabei beeinflussen. Zusätzlich zu den vom Standard vorgegebenen Konfigurationsattributen (beginnend mit mp.openapi) kann ein Applikationsserver weitere Attribute (Präfix mp.openapi.extensions) definieren. Das Ergebnis der Generierung wird dann unter dem Anwendungs-URL /openapi angezeigt.
Der Scanvorgang des Applikationsservers muss dabei nicht nur die JAX-RS Annotations, sondern auch die POJOs, die als Input- oder Outputparameter der REST Endpoints verwendet werden, mit einbeziehen. Darüber hinaus werden weitere im Code hinterlegte OpenAPI Annotations mit in das OAS-Dokument aufgenommen.
Wer sich die generierte OAS-Datei seiner JAX-RS-Applikation anzeigen lässt, kann schnell in die Thematik einsteigen. Falls das Ergebnis nicht den gewünschten Erwartungen entspricht, können wir mit einem selbst programmierten OASModelReader (Interface org.eclipse.microprofile.openapi.OASModelReader) oder OASFilter (Interface org.eclipse.microprofile.openapi.OASFilter) das Ergebnis verändern oder Inhalte herausfiltern. Der Applikationsserver muss dabei nach diesem Regelwerk vorgehen:
Konfigurationswerte (mp.openapi) einlesen
OASModelReader aufrufen
statische OpenAPI-Datei einlesen (falls vorhanden)
Annotationa aus dem Sourcecode lesen und verarbeiten
Ergebnis mit OASFilter filtern
Das Endergebnis steht dann am Endpoint/openapi im YAML-Format (Default) oder JSON-Format zur Verfügung. Noch nicht spezifiziert ist das Verhalten, wenn mehrere Anwendungen auf einem Applikationsserver betrieben werden. In dem Fall sollte der Applikationsserver für ein vernünftiges Zusammenführen aller vorhandenen OAS-Ergebnisdokumente sorgen. Leider fehlt noch eine Möglichkeit, die eigenen APIs internationalisiert zur Verfügung zu stellen. Auch eine Validierung des Generierungsergebnisses oder der Umgang mit der CORS-Problematik (Cross-Origin Resource Sharing) ist noch nicht spezifiziert. Trotzdem sollten die angebotenen Möglichkeiten für den ersten Wurf einer neuen Spezifikation ausreichend sein, um APIs vernünftig präsentieren zu können.
REST-Clients, die derzeit mit JAX-RS 2.0 entwickelt werden, sind noch ein wenig low-level. Als Entwickler müssen wir uns mit den Spezifikationen des Protokolls beschäftigen. Ein Beispiel soll dies verdeutlichen. Ausgangspunkt ist dieses Serviceinterface:
@Path("/customers")
@Produces("application/json")
public interface CustomerService {
@GET
@Path("/{customerId}")
public Customer getCustomer(@PathParam("customerId") String customerId);
...
}
Ein JAX-RS-2.0-Client würde auf die Ressource zugreifen:
public class CustomerRestClient {
private static final String CUSTOMER_URI = "http://localhost:9080/api/customers";
private Client client = ClientBuilder.newClient();
public Customer getCustomer(String customerId) {
return client.request(MediaType.APPLICATION_JSON)
.target(CUSTOMER_URI)
.path(customerId)
.get(Customer.class);
}
...
}
Mit dieser Art der Clientprogrammierung muss der Entwickler beispielsweise den Media Type selbst angeben und das Casting auf das Ergebnis POJO durch die manuelle Angabe der entsprechenden Java-Klasse festlegen. Auch ein falsch zusammengebauter Pfad würde zu Fehlern im Aufruf führen. Genau hier will MicroProfile mit seinem typsicheren REST-Client ansetzen:
public class CustomerRestClient {
private static final String CUSTOMER_URI = "http://localhost:9080/api/customers";
private CustomerService customerService = RestClientBuilder.newBuilder()
.baseUrl(CUSTOMER_URI)
.build(CustomerService.class);
public Customer getCustomer(String customerId) {
return customerService.getCustomer(customerId);
}
...
}
Im Vergleich zum normalen JAX-RS-Client ergeben sich hier ein paar Verbesserungen. Nachdem das Serviceinterface des Endpoints (CustomerService) zusammen mit dem URI an den RestClientBuilder übergeben worden ist, lassen sich die Methoden des Interface wie andere Java-Methoden aufrufen. Dadurch ist der Compiler in der Lage, die Typsicherheit festzustellen und den Entwickler bei falscher Verwendung mit einem Compilerfehler zu warnen. Die korrekte Konvertierung im vorgegebenen Mime-Typ übernimmt der Client. Auch hier wird wieder volle CDI-Unterstützung angeboten.
Da der RestClientBuilder das Interface javax.ws.rs.core.Configurable implementiert, können wir Custom-Provider (analog JAX-RS) registrieren. Eigene ClientResponseFilter und ClientRequestFilter, MessageBodyReader und MessageBodyWriter sowie ReaderInterceptor, WriterInterceptor und ParamConverter lassen sich einbinden.
Das Exception Mapping beim MicroProfile-basierten REST-Client erfolgt genau umgekehrt zum Exception Mapping des JAX-RS-Servers. Wenn der Client einen HTTP-Statuscode 400 oder höher empfängt, versucht er diesen Code in eine Subklasse von Throwable umzuwandeln. Das zentrale Interface, das die Clientlaufzeit verwendet, ist org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper<T extends Throwable>. Die Clientlaufzeit scannt dabei nach ResponseExceptionMapper-Klassen, die entsprechend einer angegebenen Priority in eine aufsteigende Reihenfolge gebracht werden. Entsprechend dieser Reihenfolge wird mit dem empfangenen javax.ws.rs.core.Response-Objekt die toThrowable(Response)-Methode des jeweiligen ResponseExceptionMapper-Objekts aufgerufen. Falls die Methode mit null antwortet, wird die Anfrage an den nächsten ResponseExceptionMapper übergeben. Das passiert so lange, bis sich ein Mapper für den HTTP-Statuscode zuständig fühlt und ein passendes Throwable-Objekt erzeugt. Als Fallback implementiert die Client-Runtime einen speziellen Mapper, der mit der maximalen Priority versehen ist und die Runtime Exception javax.ws.rs.WebApplicationException erzeugt. Das vom Mapper erzeugte Throwable wird dann von der Clientlaufzeit geworfen, sofern die Methodensignatur der Servicemethode dies erlaubt. Sollte die Methodensignatur dagegensprechen, werden die restlichen Exception Mapper abgefragt, bis schließlich einer mit der passenden (Runtime) Exception antwortet. Als letzte Möglichkeit greift die Fallback-Funktionalität der Client-Runtime.
Dieses Beispiel dient der Verdeutlichung: Das Serviceinterface des REST Endpoints, das der Client-Runtime bekannt ist, besteht aus zwei Methoden:
@Path("/api")
public interface MyService {
@PUT
public void createSomething(String parameter);
@GET
public String selectSomething() throws SpecialException;
...
}
Zusätzlich existiert dieser Exception Mapper:
public class MyResponseExceptionMapper implements ResponseExceptionMapper <SpecialException> {@Override
public SpecialException toThrowable(Response response) {
... // check HTTP Status Code und set message
return new SpecialException(message);
}
}
In unserem Beispiel wird im Fehlerfall nur beim Aufruf der GET-Methode die erzeugte Exception (SpecialException) geworfen. Bei der Methode @PUT wird nach einer passenden RuntimeException gesucht, da die Methodensignatur von createSomething keine Checked Exceptions besitzt. Im Fallback-Fall wirft die Client-Runtime WebApplicationException.
Analog der Providerregistrierung in JAX-RS lassen sich auch im MicroProfile-REST-Client Provider wie JSON-P Providers oder MessageBody Readers und Writers registrieren. Dazu müssen die jeweiligen Provider-Klassen mit der Annotation org.eclipse.microprofile.rest.client.annotation.RegisterProvider versehen werden, damit sie vom RestClientBuilder erfasst werden.
Auch in dieser MicroProfile-Spezifikation ist die Unterstützung für CDI konsequent umgesetzt. Jeder der REST-Client-Inferfaces (MyService) lässt sich mit der Annotation org.eclipse.microprofile.rest.client.inject.RegisterRestClient versehen. Die Client-Runtime kümmert sich dann darum, die Client-Service-CDI-Bean an der richtigen Stelle zu erzeugen und zu injizieren:
@RequestScoped
public class MyServiceBean {
@Inject
@RestClient
private MyService client;
...
}
Es stellt sich also nur noch die Frage, wie die Annotation (@RegisterRestClient) in das Serviceinterface eingetragen wird, denn eigentlich könnte das Serviceinterface vom Swagger-generierten Sourcecode des REST Endpoints übernommen werden. Sowohl eine kleine Erweiterung der Codegenerierung als auch eine Anpassung am Build-System könnte hier helfen.
Abschließend sollte noch erwähnt werden, dass MicroProfile Type-safe Rest Client 1.0 sich auch mit der MicroProfile-Spezifikation Config kombiniert lässt. Das ermöglicht es, die Config-Spezifikation zu verwenden. Diese hat sich marginal verändert und wurde mit der Version 1.2 in das aktuelle MicroProfile-Release 1.3 aufgenommen. Auch an MicroProfile Metrics wurde geringfügig weitergearbeitet, das somit folgerichtig mit der Version 1.1 in MicroProfile 1.3 Einzug hält.
Beim derzeitigen Funktionsumfang von MicroProfile bleiben kaum noch Wünsche offen. Daher wird es spannend zu sehen, was die Community für das Nachfolgerelease von MicroProfile 2.0 planen wird. Derzeit steckt die Eclipse Foundation viel Energie in die Unterstützung der Entwickler. Sie hat zu Recht erkannt, wie wichtig es für die Akzeptanz der Spezifikationen ist, ausreichend Infos für die Entwicklerwelt zur Verfügung zu stellen. Deswegen wurde noch kurzerhand ein neues Release MicroProfile 1.4 gestartet, das den Entwicklern mit unterschiedlichen Informationen unter die Arme greifen soll. Die ersten Resultate dieses Release sind in Form von Tutorials und Videotutorials bereits im Netz verfügbar. Das Etablieren neuer Spezifikationen und das Weiterarbeiten an schon verabschiedeten Spezifikationen zeigt, dass die MicroProfile-Community sehr aktiv ist. Falls noch etwas am Funktionsumfang fehlt, wird ohne Umschweife noch ein weiteres Release (wie MicroProfile 1.4) auf den Weg gebracht. Zusätzlich gewünschte Funktionalität können Entwickler in den entsprechenden Diskussionsforen einbringen. In der Regel bekommt man relativ zeitnah eine Antwort. Also heißt es dranbleiben, denn nach dem Release ist vor dem Release.
Michael Hofmann ist freiberuflich als Berater, Coach, Referent und Autor tätig. Seine langjährigen Projekterfahrungen in den Bereichen Softwarearchitektur, Java Enterprise und DevOps hat er im deutschen und internationalen Umfeld gesammelt.
[1] OpenTracing: http://opentracing.io/documentation/
[2] Zipkin: https://zipkin.io
[3] Jaeger: http://jaegertracing.io/
[4] Dynatrace: https://www.dynatrace.com/technologies/integration/opentracing/
[5] Swagger: https://editor.swagger.io