Stranger Than Usual

Rant: Java und Spring

Ich habe gestern den folgenden, etwas über zwei Jahre alten Kommentar zu Spring gelesen:

Spring is a lovecraftian ballache of unnamable horrors. Spring Boot does a decent job at putting those horrors in a box that, with some luck, you may not need to open (and God help you when you do). Of the commonly used modern web frameworks, Spring and Spring Boot are just aggressively mediocre. The learning process of Spring et al involves being thrashed around by enterprise-tier Java errors until you develop enough scar tissue that you can dodge them like a Dark Souls boss fight. I say this as someone with quite a few years professional experience using it, it's such an uneccessarily convoluted mess that's imposed on junior developers like some weird tech generational trauma.

Nun muss ich zugeben, dass meine Suche zu Meinungen über das Spring-Framework voreingenommen war. Meine Suchbegriffe waren „java spring rant“ oder so ähnlich. Aber meine Güte, dieses Framework macht mich fertig. In Kürze: Unter dem Versprechen, alles einfacher, besser geordnet und wartbarer zu machen wird so viel Komplexität, magisches Verhalten und Abstraktion eingeführt, dass am Ende alles komplizierter geworden ist, man eine overengineertes Klassenhierarchie hat und aufrufenden von aufgerufenem Code so weit getrennt hat, dass man selbst für einfache Vorgänge immer in mindestens vier verschiedene Dateien schauen muss.

Aber fangen wir mal weiter vorne an. Java. Eine Programmiersprache, die ich nie wirklich gemocht habe. In den letzten Jahren wurden ein paar Features eingeführt, die sie ein bisschen erträglicher machen, aber wirklich Spaß gemacht hat sie mir nie. Aber man kann mit ihr arbeiten. Was mir mehr Probleme bereitet sind die Paradigmen, mit denen viele große Java-Projekte entwickelt werden.

Da ist zunächst einmal der Zwang, alles zu entkoppeln. Man solle nie mit der Klasse selbst reden, immer nur mit einem Interface, dann kann man die Implementierung ganz einfach austauschen. I call bullshit. Ich habe jahrelang an einem Javaprojekt gearbeitet. In vielleicht 95 % der Fälle will man eine Implementierung eines Interfaces eh nie austauschen. In den anderen fünf Prozent ist es relativ leicht, die Implementierung auch so auszutauschen. Wenn man doch einmal Polymorphie braucht, kann man sie meist ohne Probleme nachträglich hinzufügen. Aber nein. Das wäre ja zu einfach. Stattdessen muss für alles immer erst ein Interface gemacht werden, für das es dann genau eine Implementierung gibt. Das ist immer eine Menge Boilerplate, eine Menge Indirektion und macht es schwieriger zu verstehen, welche Wege der Code gerade genommen hat.

Und dann kommt Spring. Spring will Independency Injection, aber das muss alles automagisch gehen, also hängt man irgendwelche Annotationen an irgendwelche Klassen und hofft, dass es funktioniert. Wenn es funktioniert, dann gut. Wenn nicht: Viel Spaß mit den Fehlermeldungen, die garantiert nutzlos sind um herauszufinden, was nicht funktioniert, geschweige denn, warum. Ich könnte einfach Instanzen meiner Services manuell hereinreichen, aber nein. Das muss automagisch, mit Reflektion gehen.

Was mich aber diese Woche wirklich auf die Palme getrieben hat, war Transaktionshandling. Spring hat dafür die @Transactional-Annotation. Die hängt man an Funktionen, und dann werden die automagisch in einer Transaktion ausgeführt (per default in einer existierenden, wenn keine existiert, wird eine neue geöffnet). Aber nur, wenn die Funktion aus einer anderen Klasse heraus aufgerufen wird. Wird die Funktion aus derselben Klasse heraus aufgerufen, wird die Transaktionsannotation ignoriert. Warum? Nun, das liegt an der Magie, mit der das gemacht wird.

Jetzt habe ich also eine Transaktion. Bei Transaktionen möchte ich, dass sie zurückgerollt werden, wenn irgendwas schiefgeht. Aber… Mist, Spring föngt die Datenbankexceptions von selber ab, und leitet sie nicht an mich weiter (merkt sie sich aber wohl für den Transaktionsabschluss?). Ich kann also zum Beispiel nicht direkt auf eine constraint violation exception reagieren. Also mache ich ein bisschen manuellen Kram und werfe eine Exception, damit ich weiter oben im Webservice einen ordentlichen 4xx-Statuscode zurückgeben kann. Für den seltenen Fall, dass es beim Transaktionsabschluss tatsächlich zu einer race condition mit Konflikten gekommen sein sollte, nehme ich halt den 500er-Fehler in Kauf.

Aber Pustekuchen. Beim Testen muss ich feststellen, dass meine Exception keinen Rollback ausgelöst hat. Warum nicht? Die @Transational-Annotation rollt nur zurück, wenn eine unchecked exception über die Funktionsgrenzen der annotierten Funktion hinaus fliegt. Checked Excepions? Per default kein Rollback. Aber ich kann extra angeben, dass auch die einen Rollback auslösen. Dann kommt auch der Rollback. Aber anstelle wie gewollte einen 4xx-Fehlercode zurückzugeben, fliegt jetzt eine UnexpectedRollbackException und damit gibt es einen Fehler 500.

Ich habe bis jetzt noch nicht herausgefunden, wie ich sowohl die Transaktion zurückrollen als auch einen von mir ausgewählten Fehlercode zurückgeben kann.

Also kurz zusammengefasst:

  • ich muss @Transactional verwenden, um Transaktionen zu verwalten, aber das funktioniert nur bei Aufrufen über Klassengrenzen hinweg
  • Spring hält Datenbankexceptions zurück, ich kriege sie nicht zu sehen
  • ich muss trotzdem manuell angeben, welche Exceptions ein Zurückrollen auslösen sollen
  • wenn ein Zurückrollen ausgelöst wird, kann ich das nicht graceful machen.

Und das alles soll irgendwie einfacher sein als manuelles Transaktions-Handling.

Ich schiebe die Hauptschuld hier auf Spring, aber auch ein bisschen auf die Java-Kultur, die jahrelang gepredigt hat, Design Patterns bis zum Gehtnichtmehr zu verwenden, alles auf Teufel komm raus zu entkoppeln und alles möglichst flexibel zu halten. Nur, dass diese Flexibilität in vielen Fällen garnicht nötig ist, dafür aber eine Menge Komplexität erzeugt.

Nicht umsonst hat „Everything wrong with Java in a single class“ so weite Kreise gezogen, nicht umsonst stammt das Beispiel aus dem Spring-Framework: AbstractSingletonProxyFactoryBean.

So. Genug geranted. Aber das musste raus, dieses Framework treibt mich einfach in den Wahnsinn.

PS: Der Kunde besteht darauf, dass wir Spring verwenden. Normalerweise würden wir es bei meinem Arbeitgeber nicht verwenden. Wir haben ein paar grundsätzliche Regeln gegen Pattern, die alles einfacher machen sollen, aber genau das Gegenteil erreichen:

  • benutze kein ORM
  • benutze keine dependency injection
  • benutze keine großen Frameworks
  • schreibe Interfaces nur, wenn du sie wirklich brauchst
  • fange keine neuen Projekte in Java an

Das ist nur ein Teil der Regeln, aber das sind imho die wichtigsten Regeln, die wir in diesem Projekt verletzen.

Antiquiertes HTML

Lesetipp: Antiquated HTML Snippets and Artefacts. Dort zeigt der Autor einen Haufen HTML-Snippets, die früher sehr verbreitet, die aber ihren Nutzen verloren haben. Meist, weil sie ein Workaround für etwas waren, dass man früher nicht anders machen konnte, oder ein Workaround, um mit irgendwelchen kaputten Browsern (IE) klarzukommen.

Ein paar Sachen davon stammen noch aus der Zeit bevor ich mich mit Webentwicklung beschäftigt habe. Viele davon habe ich aber auch schon in HTML-Code gesehen, mit dem ich arbeiten musste, manche habe ich auch entfernt, weil sie nicht mehr benötigt wurden. Ein Klassiker, den ich fast überall gesehen habe, ist

<meta http-equiv="X-UA-Compatible" content="IE=edge">

In eine ähnliche Schiene fallen The HTML Elements Time Forgot, ein Artikel, den der Autor oben auch verlinkt. Da geht es aber um tatsächliche HTML-Elemente, die (standardisiert oder nicht) einmal mehr oder weniger weit verbreitet waren, heute aber nicht mehr benutzt werden sollen und oft von Browsern nicht einmal mehr interpretiert werden.

Und heute hat der Autor auch noch einen weiteren Post veröffentlicht, dieses mal geht es um CSS Curiosities of the Past, wobei mir davon nur die Vendor-Prefixes bekannt waren. Die habe ich mal beruflich genutzt. Ich wollte das damals neue flex benutzen, aber wir mussten einige veraltete Browser noch unterstützen. Also habe ich zusätzlich zum standardisierten flex noch drei oder vier pre-Standard-Regeln mit Vendor-Prefixen genutzt, die (abgesehen von einer Version) sich fast so verhalten haben, wie der spätere Standard.

Auf jeden Fall lesenswert, wenn man in die Ungeheuerlichkeiten des HTMLs der 2000er einsteigen will oder Nostalgie für eine Zeit verspürt, in der das WWW noch ein Ort voller Hoffnung war und nicht der Torment Nexus, der es zu großen Teilen heute ist. Nostalgie halt.

PS: Und gerade eben habe ich über das gleiche Blog (aber einen anderen Artikel) erfahren, dass intitial-scale=1 nicht mehr nötig ist.

Wenn die AfD „Remigration“ sagt, meint sie „ethnische Säuberung“

Ich habe ja nicht viel für Friedrich Merz übrig. Der Mann ist „1 Pimmel“ (uh, und jetzt muss ich aufpassen, dass mir keine Wohnungsdurchsuchung bevorsteht). Er ist durch aggressive, beratungsresistente Inkompetenz mit daran Schuld, dass die AfD so gut abschneidet. Und trotz aller Belege die für ein AfD-Verbot sprechen, weigert er sich weiterhin, ein AfD-Verbotsverfahren auch nur prüfen zu lassen.

Aber diese Woche hat er etwas gesagt, dem ich nur zustimmen kann: Remigration ist nichts anderes als eine ethnische Säuberung nach Hautfarbe und Herkunft.

Nehmen wir das erst einmal auseinander. Zunächst: „ethnische Säuberung“ ist meinem Sprachgefühl nach auch nur ein Euphemismus. Es klingt so schön hygienisch und sauber, meint aber im Prinzip: Mord oder Vertreibung. Der Begriff ist auf der Euphemismus-Tretmühle ganz unten angekommen. Also braucht die AfD einen neuen Begriff, um dasselbe in der Öffentlichkeit auszudrücken.

Remigration“ ist ursprünglich ein Begriff, der einfach eine Rückmigration von Menschen bedeutet. Die Rechten, u.a. auch die AfD, verwenden ihn aber seit Jahren als Begriff für ethnische Säuberungen. Nicht zuletzt beim Treffen von Rechtsextremen in Potsdam 2023. Damals hat die AfD geleugnet, etwas damit zu tun zu haben und sich auch ein bisschen von dem Begriff „Remigration“ distanziert. Als dann die öffentliche Empörung ein bisschen abgeflacht ist, haben sie aber angefangen, den Begriff überall zu verwenden.

Er dient jetzt als Dogwhistle. Gegenüber rechtsextremen Wählern kann man so seine menschenverachtenden Vorhaben verbreiten. Gegenüber der gemäßigten Öffentlichkeit kann man trotzdem behaupten, man sei nicht rechtsextrem. Ein Feigenblatt, mehr nicht.

Und deswegen hat die AfD Strafanzeige gegen Merz gestellt:

Der Parlamentarische Geschäftsführer der AfD-Fraktion, Stephan Brandner, erklärte, die Äußerungen von Merz seien »strafrechtlich relevant« und »vollkommen inakzeptabel«. Die AfD-Fraktion stehe »fest auf dem Boden des Grundgesetzes«. Es handle sich »um verleumderische Beleidigungen, die sich nicht nur gegen jedes einzelne Mitglied der AfD-Fraktion richten, sondern letztlich auch politisch gegen Millionen Bürger«.

Die AfD steht nicht fest auf dem Boden des Grundgesetzes. Es ist keine verleumderische Beleidigung. Nur ein Beispiel von vielen: Diese Woche hat der AfD-Spitzenkandidat in Sachsen-Anhalt gesagt, er bräuchte Rüstungsgüter für Deportationen.

Es ist keine Verleumdung, die AfD als rechtsextrem zu bezeichnen. Es ist mittlerweile so offensichtlich, dass es auch keine Entschuldigung mehr für irgendeinen Wähler der AfD gibt. Wer die AfD wählt, wählt Rassismus, wählt Hass, wählt Umweltzerstörung und wählt gegen die Demokratie.

Und um auf die Ursprüngliche Aussage von Merz zurückzukommen: Wer die AfD wählt, wählt die gewaltsame Vertreibung, die „ethnische Säuberung“ aus rassistischen und nationalistischen Gründen. Millionen von Menschen würden unter Waffengewalt gezwungen, das Land zu verlassen.