Architecture & Design - JAX https://jax.de/blog/software-architecture-design/ Java, Architecture & Software Innovation Wed, 17 Jun 2026 15:07:00 +0000 de-DE hourly 1 https://wordpress.org/?v=6.9.4 AI braucht Design: Die Rolle von Spezifikationen in Enterprise AI https://jax.de/blog/enterprise-ai-braucht-design-spezifikationen-softwareentwicklung/ Fri, 12 Jun 2026 10:05:17 +0000 https://jax.de/?p=210525 KI macht Softwareentwicklung schneller – aber nicht automatisch besser. Der Artikel zeigt, warum präzise Spezifikationen und gutes Design zur Grundlage verlässlicher Enterprise-AI-Systeme werden.

The post AI braucht Design: Die Rolle von Spezifikationen in Enterprise AI appeared first on JAX.

]]>
„Design before Implementation” gehört zu den zentralen Prinzipien guter Softwareentwicklung – und wurde in vielen Projekten in den letzten Jahren vernachlässigt. Mit dem Aufkommen von AI-gestützter Entwicklung entsteht nun oft der Eindruck, man könne aus lose formulierten Geschäftsregeln direkt funktionierende Software erzeugen. Das Gegenteil ist jedoch der Fall: Gerade wenn AI Teil realer Anwendungen wird, steigen die Anforderungen an Klarheit, Struktur und präzise Entwürfe. Der Beitrag zeigt, warum detaillierte Spezifikationen und sauberes Systemdesign nicht an Bedeutung verlieren, sondern im Zeitalter von Enterprise AI wichtiger werden als je zuvor.

Um es gleich vorwegzusagen: Von sauberem Design profitiert jede Softwareentwicklung, ob mit oder ohne KI. Wenn dafür eine „Renaissance“ entsprechender Prinzipien notwendig ist, dann ist sie ohnehin überfällig und der Zeitpunkt perfekt. In vielen Projekten, die nach iterativen/agilen Verfahren arbeiten, sind klassische Techniken leider auf der Strecke geblieben. Design gehört aus zwei Gründen dazu.

Stay tuned

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

[mc4wp-simple-turnstile]

 

Zum einen, weil „agil“ gern als Ausrede für das Weglassen jeglicher Dokumente – also auch jeglicher Designdokumente – herhalten musste. Das ist eine Missinterpretation, die letzten Endes den Begriff „Software Craftsmanship“ hervorgebracht hat [1]. Abläufe und Strukturen in nicht trivialer Software bedürfen detaillierter Beschreibungen, die mit dem Auftraggeber abgestimmt sind. Was dem Handwerker seine Bauzeichnung ist und dem Musiker seine Partitur, ist dem Software Craftsman seine Softwarespezifikation.

Zum anderen – und das macht auch Anhängern des präzisen Entwurfs das Leben schwer –, weil es kaum ein Werkzeug gibt, das sich komfortabel für eine iterative Entwicklung der Entwürfe eignet. Zum Beispiel hat kein einziges bekanntes UML-Tool einen Änderungsmodus mit einer hybriden Darstellung von bestehendem, neuem und gelöschtem Inhalt wie ihn etwa Microsoft Word hat. Das ist ein Hauptgrund, warum MS Word über Jahrzehnte hinweg Grundlage für iterative Systemspezifikationen war. Andererseits ist Word kein Modellierungswerkzeug, sondern „nur“ ein Textverarbeitungsprogramm. Wenn eine KI die Kontrollstrukturen einer Ablaufbeschreibung verstehen soll – Schleifen, Verzweigungen, Ausnahmen usw. –, dann ist eine Freitextbeschreibung die falsche Basis.

Renaissance heißt also nicht einfach zurück zu den alten Methoden und Werkzeugen, sondern die Prinzipien des Software-Engineerings, die sich über Jahrzehnte bewährt haben, mit einer neuen Generation von Werkzeugen umzusetzen, die den heutigen Anforderungen an Agilität und KI-Integration gerecht werden. Selbst Vibe Coder merken das zunehmend und bringen den Begriff „Spec-driven Development“ als Trendthema auf [2], wobei dort aktuell andere Schwerpunkte gesetzt werden.

Warum Design wichtig ist

Mit Design bzw. Entwurf ist hier primär gemeint: eine detaillierte Beschreibung von Abläufen in den System-Use-Cases einer Software. Die Beschreibungen sind einerseits möglichst fachorientiert und natürlichsprachlich formuliert, andererseits aber so präzise, dass sie ohne Risiko von Fehlinterpretationen in Code übersetzt werden können. Sie fußen dabei auf einem Vokabular von Geschäftsobjekten und deren Beziehungen untereinander. Beides – die Geschäftsobjekte und die Abläufe – sind verbindliche Grundlagen für alle Stakeholder, vom Auftraggeber über die Entwickler bis zu den Testern. Das sind seit jeher zentrale Konzepte von OOA/OOD und Domain-Driven Design. Wenn es um UI-Funktionalität geht, kommen außerdem Oberflächenspezifikationen hinzu (z. B. Wireframes), die in diesem Artikel aber außen vor bleiben.

Solche Softwareentwürfe sind in ihrer Funktion einer Bauzeichnung beim Bau oder Umbau eines Hauses nicht unähnlich. Und wer jetzt einwendet, dass Häuser im Gegensatz zu Software nicht ständigen Änderungen unterliegen, der möge an Solaranlagen, Wallboxen, Wärmepumpen, Glasfaser usw. denken. Ein Segen, wenn dann gute, aktuelle Grundrisse und Leitungspläne des Gebäudes vorliegen. Gerade iteratives Vorgehen profitiert von jederzeit aktuellen Entwürfen.

Abb. 1: User Story für die Altersdarstellung von Chatbeiträgen

Abb. 2: Systemspezifikation für Altersdarstellung als „Aktogramm“

Zur Verdeutlichung zeigt Abbildung 1 ein einfaches Beispiel für eine Handvoll Geschäftsregeln, wie sie in einer User Story stehen könnten, und Abbildung 2 eine daraus entworfene Ablaufbeschreibung in einer Notation, die später noch genauer betrachtet wird. Die Entwurfsform und das Software-Engineering dahinter sind bewährte Konzepte, die auch in hochkomplizierten Szenarien Anwendung finden. In dem Trivialbeispiel aus Abbildung 1 geht es um die leserfreundliche Darstellung des Alters von Beiträgen in einem Chat. Statt 3 621 Sekunden seit der Veröffentlichung soll dort z. B. „1 Stunde“ stehen. Völlig unkritische Funktionalität, aber es könnten natürlich auch die Ratingregeln einer Bilanzanalyse sein, die in einer Bank über die Vergabe von Millionenkrediten entscheiden.

Was passiert nun, wenn ein unbedarfter Entwickler direkt aus den Geschäftsregeln heraus Code produziert? Die Anforderungen klingen einfach, also wird er direkt drauflos programmieren, ist aber natürlich trotzdem gezwungen, sich Gedanken über die Struktur des Codes zu machen. Die Geschäftsregeln sagen ja nur, was zu tun ist, aber nicht wie. Was sich jetzt abspielt, nennt sich „design a little, code a little“ – ein Antipattern! Unter [3] kann man nachlesen, wieso es anti ist. Es entstehen schlechtes Ad-hoc-Design und schlechter Code. Und was ändert sich, wenn man stattdessen mit einem KI-Agenten arbeitet? Man bekommt den schlechten Code schneller geliefert, das ist alles.

Enterprise AI & Agentic Systems

Enterprise AI-Track entdecken

 

Bei einem KI-Coding-Dojo im vergangenen Herbst durften sich Github Copilot und Junie mit verschiedenen LLMs an dieser Aufgabe versuchen, ohne dass dabei Code entstand, der durch weiteres Prompting irgendwann eine vernünftige Gestalt angenommen hätte. Man wird also im Zeitraffer direkt Zeuge des Problems, und die KI-Agenten zeigen uns im Grunde nur: So naiv geht es nicht. Dabei ist noch gar nicht berücksichtigt, dass die Fachbereiche ihre Anforderungen eher „in loser Schüttung“ liefern – ohne Anspruch auf Vollständigkeit und Schlüssigkeit, dafür mit unausgesprochenen Details zwischen den Zeilen.

Ganz anders sieht das Ergebnis basierend auf der Ablaufspezifikation in Abbildung 2 aus. Hier ist der Entwurf in einer separaten Phase vorweg entstanden und wurde so beschrieben, dass alle Beteiligten das Prozedere der Software nachvollziehen können. Die Beschreibung geschieht in einer Weise, dass man quasi bei Ausfall der Software einen Sachbearbeiter hinsetzen könnte, damit er dem Ablauf folgend die Arbeit manuell erledigt, wenn auch eine Milliarde Mal langsamer. Sowohl Entwickler als auch KI-Agenten können auf dieser Basis brauchbaren Code erstellen, der sich an der Struktur des spezifizierten Ablaufs orientiert und somit auch leicht darauf zu prüfen ist, ob er überhaupt das tut, was gefordert ist. Bei späteren Änderungen im Rahmen iterativer Entwicklung sind die in der Spezifikation vorgenommenen Modifikationen entsprechend leicht im Code zu verorten.

Hier spielen gleiche mehrere Faktoren eine Rolle, die eine vorgelagerte Designphase so wertvoll machen, die sich in späteren Phasen auszahlt:

  • Klarheit des Entwurfs: Wenn man sich zwingt, ein für „Normalsterbliche“ nachvollziehbares Verfahren zu beschreiben, das das fachlich gewünschte Ergebnis erzielt, dann stellt man häufig fest: Die vermeintlich bereits glasklaren Gedanken dazu sind unklarer, als man glaubte. Fast jeder kennt das: Sobald man versucht, jemand anderem zu erklären, was man vorhat, zerbröselt eine undurchdachte Idee zu Staub. Eine gute Idee hingegen wird dadurch gestärkt und verfeinert.
  • Analytisches Denken: Es dauert u. U. ziemlich lange, bis man ein leicht zu verstehendes Vorgehen formuliert hat, und das ist gut so. Es zwingt den Entwickler, aus dem schnellen impulsiven Denken in das langsame analytische Denken zu wechseln. Es werden dabei ganz andere Hirnregionen aktiv [4] und das Ergebnis ist ein völlig anderes als bei „design a little, code a little“.
  • Kurze Feedbackschleifen: Sind die Entwürfe für alle lesbar, können auch die Fachanforderer sie beurteilen und dazu beitragen. Hier entstehen schnelle Feedbackschleifen, von denen agile Entwicklung ohne Spezifikationen nur träumen kann. Schwere Missverständnisse und Konzeptfehler fallen nicht erst nach zwei Wochen in der Sprintreview auf, sondern schon nach zwei Stunden in der frühestmöglichen Phase der Softwareentwicklung. Die Rule of Ten [5] erlaubt da schon viel Spezifikationsarbeit, bevor eine Überfrachtung entsteht.

Um es klar zu betonen: Es geht nicht um eine Rückkehr zum Wasserfallmodell! Die Entwürfe müssen iterativ weiterentwickelt werden können. Sie sind keine Wegwerfprodukte aus einer initialen Anstrengung, sondern lebende Artefakte des Software-Engineerings. Das funktioniert nur, wenn sie in geeigneten Werkzeugen erstellt werden und tagtäglich Mehrwert schaffen. Das ist eine der Herausforderungen, die sich bewusst zu adressieren lohnt.

Entwürfe MIT statt VON der KI

Entwurfsarbeit ist soooo anstrengend – kann das nicht die KI machen? Kann sie nicht, denn sonst wäre ja in dem oben erwähnten Dojo vom Fleck weg guter Code entstanden. Sie ist Konsument der Entwürfe. Die Diskussionen, ob und wie weit KI auch Entwürfe herstellen kann, füllen derzeit die Kaffeeküchen aller IT-Unternehmen der Welt.

Die in diesem Artikel vertretene Meinung fußt auf folgender Sichtweise: Der Abschnitt zuvor erwähnt die Feedbackschleifen, die einen guten Entwurf ausmachen. Menschen, die solche Entwurfsarbeit lieben, streben dabei nach Erkenntnisgewinn und nach einem gemeinsamen Verständnis der Welt zwischen den Beteiligten. In ihren Entwürfen sind sie darauf aus, fachliche Notwendigkeiten und technische Möglichkeiten optimal auszubalancieren. Sie treiben diesen Vorgang bis zu einem Punkt, an dem sie sicher sind, dass die folgende Umsetzung kein Abenteuer mehr ist, sondern zielgerichtetes Handwerk mit einem vorhersagbaren Ergebnis. Software Craftsmanship eben.

Künstliche Intelligenz heutiger Bauart hingegen will auf gar nichts hinaus. Es ist eine hochentwickelte Mustererkennungsmaschine, der wir Menschen ihrer guten Ausdrucksweise wegen gerne Intelligenz, Empathie und intrinsische Motivation zuschreiben. Das ist eine fundamentale Denkfalle, so alt wie der Begriff „künstliche Intelligenz“ selbst [6]. Um KI in der Entwicklung komplizierter Softwaresysteme zu nutzen, muss sie mit klaren Mustern und Strukturen gefüttert werden. Das Ziel ist schließlich keine grob in die richtige Richtung gehende „interessante Inspiration“, sondern exakter, bis auf die letzte 0 und 1 stimmiger Code. Mit „exakt“ haben es LLMs aber naturbedingt nicht so, weshalb die Klarheit der Strukturen und Muster umso wichtiger wird. Und wo kommt diese Klarheit her? Entwurfsarbeit!

Entwürfe sind von der KI also nicht zu erwarten, was eigentlich eine gute Nachricht ist: Wer sich auf diese Tätigkeit versteht – tendenziell also jeder Clean Coder – wird so schnell nicht arbeitslos. Es geht wohlgemerkt um komplizierte Softwaresysteme mit Hunderttausenden Zeilen. Energiewesen, Bilanzanalyse, Touristik und Kundenbindungsprogramme sind einige der Umfelder, in denen die hier dargestellten Konzepte Anwendung finden. Abbildung 3 zeigt bespielhaft aus der Vogelperspektive eine vollständige Use-Case-Spezifikation mittlerer Größe, um einen Eindruck zu vermitteln, wie so etwas in echten Projekten aussieht. Die Ablaufbeschreibung ist hier eingebettet in eine Präambel, Vor- und Nachbedingungen, Geschäftsklassendiagramme, Wireframes usw. Auch wenn man die Texte in dieser Auflösung nicht lesen kann, sollte deutlich werden, dass hier keine triviale Logik beschrieben ist. Ohne Spezifikation ist eine permanente Fortentwicklung solcher Use Cases viel zu ineffizient. Natürlich lässt sich die Arbeit an großen Entwürfen prima mit KI unterstützen, wie Matthias Bartels in seinem Artikel „Mit automatisierter Review zum gelungenen Entwurf“ in dieser Ausgabe des Java Magazins zeigt [7]. Um die zentralen Aspekte zu erläutern, soll aber im Weiteren das oben vorgestellte kleine Beispiel genügen.

Abb. 3: Echte Use-Case-Spezifikation mittlerer Größe, Vogelperspektive

Der eigene Kopfcomputer bleibt also der Ort, wo die Entwürfe letztlich entstehen, wo sie aber nicht bleiben dürfen. Machen wir sie Mensch und Maschine zugänglich, dann maximieren wir die Möglichkeit der Zuarbeit von beiden Seiten. An dieser Stelle kommt passendes Tooling ins Spiel. Künstliche Intelligenz hat zwar erstaunliche Fähigkeiten, fast beliebigen Input zu verarbeiten. Die Qualität der Ergebnisse hängt aber doch davon ab, wie präzise die Eingaben strukturiert und formuliert sind.

Abb. 4: Systemspezifikation für Altersdarstellung in Microsoft Word

Abbildung 4 zeigt eine alternative Darstellung der Spezifikation aus Abbildung 2, basierend auf einem viel genutzten Microsoft-Word-Template, wie eingangs schon erwähnt wurde. Es soll beispielhaft die Problematik zeigen, die entsteht, wenn man bequem greifbare Text- und Maltools zum Modellieren missbraucht und damit der KI und auch jeder anderen maschinellen Unterstützung das Interpretieren schwer macht. Was direkt auffällt: Die in Abbildung 2 auf den ersten Blick erkennbaren Verzweigungen (dargestellt durch Diamanten) sind nicht mehr unmittelbar zu sehen. Sie liegen textuell beschrieben vor, was dem tabellarischen Layout geschuldet ist, um automatische Nummerierung und Änderungsstabilität zu gewährleisten. Heftige Kompromisse also, die für menschliche Leser vielleicht ein verkraftbares Problem sind. Aber ob eine KI daraus die gemeinte Ablaufstruktur noch gut herauslesen kann? Noch ungünstiger wird es, wenn für Remotearbeit auf kostengünstige, webbasierte Kollaborationswerkzeuge zurückgegriffen wird (z. B. Confluence). Für die Modellierung sind diese Tools nicht gedacht.

Stay tuned

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

[mc4wp-simple-turnstile]

 

Wie sieht’s mit UML-Aktivitätsdiagrammen oder BPMN aus? Hier lassen sich Abläufe modellieren und damit auch gut maschinell auswerten. Leider tauscht man damit die Vor- und Nachteile gegenüber MS Word quasi eins zu eins aus. Änderungsverfolgung, automatische Nummerierung und änderungsstabiles Layout sind futsch. Davon abgesehen brauchen diese Notationen viel Platz und vertragen kaum Text für notwendige Details, die man stattdessen in eine verborgene Doku oder angeheftete Textblasen auslagern muss. Also wieder reichlich Kompromisse in anderer Form, wie in Abbildung 5 exemplarisch dargestellt ist. Das sind alles keine unüberwindbaren Hürden und keine Ausrede dafür, ohne Entwürfe zu arbeiten. Aber vielleicht ist es für eine neue Mensch-Maschine-Zusammenarbeit beim Entwerfen Zeit für „New Kids on the Block“ …

Abb. 5: Systemspezifikation für Altersdarstellung als UML-Aktivitätsdiagramm

Aktogramme

Abbildung 2 zeigt, wie wir gesehen haben, eine Spezifikation für das Beispiel der Altersdarstellung von Chatbeiträgen in Form eines „Aktogramms“. Es handelt sich um eine für Spezifikationen weiterentwickelte Form von Struktogrammen – auch als Nassi-Shneiderman-Diagramme bekannt [8]. Sie sind noch älter als die erste Erwähnung von „Design before Implementation“, dienten aber genau diesem Prinzip. Dabei lag ihr Fokus seinerzeit auf der strukturierten Darstellung von Programmcode, als Letzterer noch 70er-Jahre-Assembler war (so alt sind Struktogramme).

DIE KUNST DER SOTWARE-ARCHITEKTUR

Architecture & Design-Track entdecken

 

Dieses Problem ist mit Hochsprachen und IDEs mit Syntax-Highlighting inzwischen besser gelöst. Für Spezifikationen werden sie aber wieder interessant, wenn man sie ein bisschen aufmöbelt. Der Begriff „Aktogramm“ ist aus „Struktogramm“ und „Aktivitätsdiagramm“ zusammengesetzt, weil die Notation im Kern dem Darstellungskonzept von Struktogrammen folgt, aber einige Anlehnungen an UML-Aktivitätsdiagramme aufweist. So zum Beispiel die in Abbildung 2 gut erkennbaren Diamanten als Ausdrucksform für Verzweigungen, weil die originale Dreiecksform von Struktogrammen unordentlich wirkt, wenn sie in einem Ablauf wiederholt auftaucht. Es steckt also einiges an Feinschliff in der Notation, um sie für Spezifikationen größeren Umfangs tauglich zu machen. Schaut man sich das Aktogramm aus Abbildung 2 und das entsprechende UML-Aktivitätsdiagramm aus Abbildung 5 aus größerer Entfernung an, bemerkt man die beiden Diamanten recht gut als Gemeinsamkeit.

Das Aktogramm wurde mit dem Java-WYSIWYG-Editor Specman [9] erstellt, der verschiedene praktische Eigenschaften von Word und UML zusammenbringt, z. B.

  • Änderungsstabiles Layout: Wenn man hier Schritte in den Ablauf einbringt oder entfernt, gerät das Layout nie durcheinander wie bei Aktivitätsdiagrammen
  • Änderungsmodus: Wie in Word, aber mit einer deutlicheren farblichen Hervorhebung wie mit einem Textmarker (3); man scrollt einfach durch und schaut sich eine gelbe Markierung nach der nächsten an, um die am Use Case vorgenommenen Änderungen zu erkennen
  • Automatische Schrittnummerierung: Sehr wichtig für Abstimmungsprozesse; „Schau dir mal Schritt 3.4.2 an, ob ich dich gestern richtig verstanden habe“, ohne Nummern wird das Absprechen komplizierter Abläufe schwierig
  • Schrittreferenzen: Automatisch aktualisierte Referenzen auf die Nummern anderer Schritte in den Beschreibungen; in Word Usus, in UML ein Manko
  • Viel Inhalt: Wir können uns nicht aussuchen, ob die Geschäftslogik einfach oder kompliziert ist, und die Notation darf uns im Fall der Fälle nicht beschränken; in Specman kann man aber jeden untergliederten Schritt (Schleifen, Verzweigungen, Untersequenzen) in der Ansicht zusammenklappen, wenn man an deren Inhalt gerade nicht interessiert ist
  • Kontrollstrukturen: Sind gut erkennbar – nicht so gut wie in UML, aber viel besser als in Word; hinzu kommt eine wichtige Erweiterung gegenüber Struktogrammen: Letztere kennen zwar sogenannte Breaks als Entsprechung zum Werfen einer Exception, aber es fehlt die Entsprechung für das Fangen; in Spezifikationen ist es aber wichtig, wie das System mit fachlich relevanten Ausnahmen umgeht, also nicht mit NullPointerException und OutOfMemory– das gehört als querschnittliches Verfahren in die Architekturdoku –, sondern mit Fällen wie „Kunde ist gesperrt“ oder „Zahlungsart ungültig“

Specman speichert die Diagramme in einem JSON-Format, aus dem eine KI die Struktur des Ablaufs gut ablesen kann. Muss sie so etwas aus Bildern ablesen, führt das zu einer höheren Anfälligkeit für Fehlinterpretationen und kostet wesentlich mehr Zeit (und Tokens).

Beim Coding auf Grundlage von Aktogrammen können KI und menschliche Entwickler einige Aspekte direkt übernehmen. Ein Beispiel ist die Unterstrukturierung, die man beim Entwurf komplizierter Abläufe in Aktogrammen automatisch vornimmt. Sie lässt sich bei der Implementierung für das Designprinzip „Single Level of Abstraction“ nutzen. Ein erneuter Blick auf Abbildung 2 zeigt beispielsweise, welche Schritte die oberste Spezifikationsebene bilden – die Schritte mit einstelliger Schrittnummer. Man darf also erwarten, dass auf der obersten Codeebene für die Funktionalität eine Abfolge von Funktionsaufrufen zu finden ist, die genau das widerspiegelt:

  • Absicherung gegen negative Angaben
  • Bildung des Stundenanteils
  • Bildung des Minutenanteils
  • Bildung des Sekundenanteils
  • Zusammenstellen des Ergebnisses aus den Anteilen

Es stellt sich für den Entwickler nicht die Frage, ob das eine funktionierende und sinnvolle Strategie zur Erfüllung der Anforderungen ist. Das wurde im Vorfeld in der Entwurfsphase ausgetüftelt. Idealerweise unter seiner direkten Mitarbeit oder sogar Federführung, denn die Entwürfe sind nur brauchbar, wenn sie mit technischem Sachverstand entstehen. Dass Fachbereiche oder Businessanalysten das zukünftig selbst machen und dann nur noch eine KI anwerfen, ist also unwahrscheinlich.

Speckit und Co.

Ausgerechnet die junge Szene der Vibe Coder [10] bringt das Thema Spezifikation im Zusammenhang mit KI wieder ins breite Bewusstsein. Trotz zweifellos höherer Schmerzresistenz bzgl. der Codequalität sind die Erkenntnisse dort offenbar die gleichen wie in dem oben beschriebenen KI-Dojo: Von Hello-World-Programmen abgesehen führt Coding ohne Design innerhalb kürzester Zeit in die Unwartbarkeit. Also entstehen auch in diesem Umfeld Spezifikationswerkzeuge, die allerdings nicht den Anspruch haben, die Entwürfe für alle Stakeholder lesbar zu machen. Speckit [11] ist so ein Werkzeug, das eine Plaintext-basierte Notation verwendet. Die Zugänglichkeit aus einer IDE oder Kommandozeile heraus genügt, weil es nur um verbesserte Kommunikation zwischen Entwickler und KI geht. Andererseits reicht der Anspruch in Richtung Coding so weit, dass die KI auf reproduzierbare Weise vollständige Applikationen implementieren soll.

Der Verbrauch kostbarer KI-Tokens gilt bei Speckit allerdings als enorm, was die leichtgewichtigere Alternative OpenSpec [12] zu verbessern verspricht. Dafür hat Letztere aber noch weniger mit Spezifikationen gemeinsam, wie sie in diesem Artikel gemeint sind. Es geht hier eher um die Beschreibung von Arbeitsaufträgen an die KI. Immerhin entsteht auf diese Weise Wiederholbarkeit und Nachvollziehbarkeit, statt dass der Code über geheimnisvolles Ad-hoc-Prompting immer wieder überraschend anders entsteht [13].

Nassi

Plaintext-basierte Spezifikation und hoher Detailgrad sind übrigens kein Widerspruch. Neben Specman gibt es den Aktogramm-Editor Nassi [14], der einen solchen Weg verfolgt und die für Fachanforderer verständlichen Diagramme in HTML-Form generiert. Dieser andere Ansatz für die Erstellung der Aktogramme führt zu einigen Unterschieden darin, wie sich der Spezifikationsvorgang in das Software-Engineering integriert. Im Kern verfolgt Nassi aber dieselbe Intention wie Specman.

In seinem Artikel „Aktogramme als Kommunikationsmedium“ in dieser Ausgabe des Java Magazins stellt Jan Hermanns den Editor ausführlich vor und geht außerdem tiefer auf iterative Entwurfsarbeit ein [15]. Beide Editoren sind Open Source.

KI auf Aktogrammen

Mit maschinenlesbaren, feingranularen und klar strukturierten Entwürfen kann die KI nun auf vielfältige Weise unterstützen, ohne dabei ausgeprägt in die Irre zu laufen oder zu halluzinieren.

Das naheliegende erste Einsatzgebiet ist die Validierung der Spezifikation selbst. Nicht so sehr im Sinne von: Beschreibt die Spezifikation das, was gebraucht wird? Das zu entscheiden ist Aufgabe der Fachbereiche im Rahmen ihrer Reviews. Deswegen ist gute Lesbarkeit für uns Menschen so wichtig. Eine KI kann aber prüfen, ob die Abläufe in sich logisch konsistent und vollständig sind oder ob sie „lose Enden“ haben. Matthias Bartels geht darauf in seinem Artikel ein [7].

Auch die Validierung von Code gegen die Spezifikation – ein zentraler Bestandteil jeder Code-Review – lässt sich teilautomatisieren. Wenn sich der Code sauber an der Struktur der Spezifikation orientiert, dann ist eine KI wie beispielsweise Claude Code ziemlich gut in der Lage, die Übereinstimmung zu prüfen.

Matthias Bartels vertieft auch diesen Aspekt in seinem Artikel, da er ein wichtiges Sicherheitsnetz für die Frage aller Fragen ist: Kann die KI aus genügend präzisen Beschreibungen zielgerichtet Code generieren, der exakt die spezifizierte Funktionalität realisiert? Vorzugweise, ohne dass die Spezifikation dafür den Umfang des Codes selbst annimmt.

Ein klares „Jein“

Es müssen Stand heute noch viele zusätzliche Bedingungen geschaffen werden, um in komplizierten Systemen nennenswert Code aus Entwürfen generieren zu können. Neben der Spezifikation der Geschäftsobjekte (siehe unten) braucht KI noch eine ganze Menge mehr saubere Strukturen – und zwar in der Architektur. Wenn die KI auf eine Weise coden soll, dass es in eine größere Codebasis passt, dann muss sie darin Muster für alles vorfinden, was es zu coden gibt. Sie verwendet sonst munter alles durcheinander, wovon sie in ihrem angelernten projektfremden Wissen meint, dass es passen könnte. Wenn also im Code keine Ordnung herrscht, dann beschleunigt die KI das Broken-Window-Problem [16], und die Unordnung wächst noch schneller als mit menschlichen Entwicklern.

In einer unordentlichen Codebasis empfiehlt es sich, zunächst eine „Insel der Ordnung“ zu schaffen, an der sich die KI orientieren kann. Ein erfolgreich praktizierter Ansatz besteht darin, in einem verlebten Bestandsprojekt einen getrennten Source-Folder einzurichten, in dem sich ausschließlich blitzsaubere Referenzimplementierungen mit minimalem Umfang für alle Bausteine der Architektur befinden, die als verbindliche Muster fungieren. Wie sieht eine Entity aus? Wie sieht ein Testdaten-Builder für eine Entity aus? Wie ist ein Repository mit Cache für datenbankbasierte Konfigurationsdaten aufgebaut? Die Codebausteine in diesem Folder dienen KI und menschlichen Entwicklern als garantiert unverschmutzte Vorbilder und landen nicht in der Auslieferung. Auch hier gilt das gleiche Prinzip: Design before Implementation.

Ein anderer Punkt ist die Minimierung des Kontexts, den die KI für eine Aufgabe berücksichtigen muss. Je kleiner, desto besser. Anforderungsseitig sind präzise Spezifikationen eine gute Voraussetzung. Codeseitig empfiehlt Simon Martinelli in einem kürzlich veröffentlichten Podcast zu Spec-driven Development [17] den Architekturansatz der Self-contained Systems [18]. Grundidee ist die Zerlegung eines größeren Softwaresystems in Säulen unabhängiger Subsysteme, ohne in das Extrem von Microservices abzugleiten. Jedes der Subsysteme erfüllt eine klar umrissene fachliche Aufgabe und verfügt über eigene Datenhaltung und eigenes UI.

„Self-contained“ heißt: Die Subsysteme sind in sich abgeschlossen und brauchen nur minimalen Kontakt zueinander. Ein System-Use-Case bezieht sich auf Funktionalität innerhalb eines dieser Subsysteme, was dann auch für dessen Spezifikationen gilt. Eine KI, die auf Grundlage solcher Spezifikationen Code generiert, muss sich also nur auf das jeweilige Subsystem konzentrieren. Das kann den codeseitig zu berücksichtigenden Kontext erheblich reduzieren und umgekehrt die Wahrscheinlichkeit erhöhen, dass die KI brauchbaren Code in akzeptabler Zeit produziert.

Subsysteme in einer gemeinsamen Codebasis erfordern dann strenge Modularisierung und u. U. mehr Arbeit beim Prompting, damit der KI-Agent nicht aus Versehen links und rechts schaut. Mit separaten Codebasen für die Subsysteme geht man auf Nummer sicher, was aber wiederum viele andere Komplikationen mit sich bringen kann. Das ist Abwägungssache.

Eine sauber gegliederte Spezifikation hat den Vorteil, dass man einer KI auch klar abgegrenzte Teilaufgaben geben kann. Ist das Coden eines komplette Use Case ein zu dickes Brett, kann der Auftrag auch lauten: Implementiere Schritt 3.4.2 der Spezifikation. Es entsteht dann auch nicht so viel Code auf einmal und man tut sich leichter mit der Review. Man führt die KI sozusagen an ganz kurzer Leine. „Vise Coding“ statt „Vibe Coding“ nennt David Farago dieses Konzept, das sich mit Entwürfen der hier beschriebenen Detaillierung gut verbinden lässt [19].

Der statische Teil einer Spezifikation

Das oben verwendete Beispiel wurde bewusst so gewählt, dass es auch ohne Modellierung von Geschäftsobjekten einigermaßen verständlich ist. In der echten Praxis machen die Formulierungen der Abläufe aber intensiven Gebrauch von einem projektspezifischen Begriffsvokabular. Wenn der Leser einer Ablaufbeschreibung nicht zufällig alle darin auftauchenden Geschäftsobjekte und ihr Beziehungsgeflecht im Kopf hat, muss er sich das irgendwo anschauen können – und zwar nicht im Code, der nur Entwicklern zugänglich ist.

Ist die Formulierung „Wenn der Kunde einen Rabattcoupon eingelöst hat …“ überhaupt valide? Kann man irgendwo sehen, was ein Kunde und was ein Rabattcoupon ist und wie die Beziehung zwischen beiden aussieht? Vielleicht besteht die nur indirekt über den Umweg der Artikel einer Bestellung, und man sollte den Ablauf präziser formulieren – für Mensch und KI. Wenn diese Dinge nicht einsehbar sind, ist die Ablaufbeschreibung womöglich nur Kauderwelsch.

Abb. 6: Geschäftsobjekte als „umweltverträgliches“ UML-Klassendiagramm

Der Anspruch, dass die Entwürfe mit allen Beteiligten abgestimmt sind und sich permanent weiterentwickeln, gilt auch für die statischen Bestandteile. In den Projekten, in denen Specman und Word für die Ablaufbeschreibungen verwendet werden, kommen UML und im Besonderen Klassendiagramme zum Einsatz. Das ist trotz fehlender Änderungsverfolgung usw. ein akzeptabler Kompromiss, denn die Geschäftsobjekte ändern sich weniger häufig und enthalten auch nicht so viele Details wie die Abläufe. In Abbildung 6 ist das beispielhaft für die Geschäftsobjekte des Chatsystems dargestellt, auf die sich die Anforderung mit der Altersdarstellung bezieht. Allerdings sollte es schon „echtes“ UML sein und nicht nur gemalte Diagramme in DrawIO oder Visio. Der Hauptgrund ist die Unterscheidung zwischen dem Modell einerseits und dem Diagramm als eingeschränkte Sicht auf das Modell andererseits.

In größeren Softwaresystemen ist es wenig hilfreich, ein Riesentapetendiagramm mit allen Geschäftsobjekten darauf herzustellen. Sinnvoll und naheliegend ist eher, dass man Use-Case-bezogene Diagramme herstellt, die den Ablaufspezifikationen der Use Cases beigestellt werden (Abb. 3). Zentrale Geschäftsobjekte tauchen dann in diversen Diagrammen auf, sollen aber natürlich nur einmalig modelliert werden, sofern hier nicht nach dem Prinzip der Bounded Contexts bewusst Redundanzen erwünscht sind [20]. Diese Trennung von Modell und Diagramm ist eine wichtige Eigenschaft professioneller UML-Tools wie z. B. Enterprise Architect oder Visual Paradigm.

Hinzu kommt in diesen Tools die Möglichkeit des Aufbaus sogenannter UML-Profile, die u. a. einen Kanon projektspezifischer Stereotype und zugehöriger Tagged Values bereitstellen. Stereotype sind eine Ausdrucksform querschnittlicher Konzepte, über die sich damit verbundene Muster im Code adressieren lassen. Ein KI-Assistent kann über die Stereotype also auf die richtigen Patterns zur Codegenerierung hingewiesen werden, wie Abbildung 6 verdeutlicht. Aus einer View-Klasse sind völlig andere Dinge zu generieren als aus einer Entity-Klasse, wobei Letztere noch mit einem Tagged Value für den Tabellennamen angereichert ist. Mit einem guten Farbschema, unterstützenden Icons und möglichst fachlicher Terminologie lassen sich auch für die Anforderungsseite ansprechende Diagramme herstellen.

Use-Case-bezogene Diagramme haben übrigens den Nebeneffekt eines geschenkten Dependency-Trackings. Ändert man für einen Use Case etwas an der Struktur eines zentralen Geschäftsobjekts, lässt sich im Tool feststellen, in welchen Diagrammen und damit in welchen anderen Use Cases das Objekt eine Rolle spielt und wo man mögliche Auswirkungen prüfen muss.

Alle professionellen UML-Tools bieten Exporte in maschinenlesbare Formate an, z. B. XMI. Damit ist sichergestellt, dass auch KI die Modelle leicht versteht. Noch einfacher ist es mit Tools, die selbst auf Plaintext arbeiten und die grafischen Darstellungen generieren. Weit verbreitet ist z. B. PlantUML [21]. Leider ist hier die Trennung Modell/Diagramm nicht gegeben, was den Einsatz für größere Softwaresysteme erschwert.

Es ist auch die Frage, ob man mit der Optik generierter Diagramme klarkommt. Einer agentischen Codegenerierung ist Optik egal, solange die modellierten Strukturen präzise sind, aber wie bei den Abläufen sind die Adressaten für die Diagramme auch die Fachanforderer. Sinnvolle Platzierung nach fachlichen Gesichtspunkten, saubere Linienführung und Aufgeräumtheit sind hilfreich für das Verständnis. Auch bei den statischen Modellbestandteilen kann man sich nicht immer aussuchen, wie kompliziert die darzustellenden Zusammenhänge sind. Wo immer es geht, hält man die Dinge klein, etwa nach der C4-Methode [22] oder nach den Gestaltungskonzepten von Jacqui Read [23]. Wenn das aber einmal nicht geht, ist es ärgerlich, wenn die Einflussmöglichkeiten auf das Layout beschränkt sind. Man denke an die Analogie zur Bauzeichnung: Detailreichtum und Lesbarkeit sollten kein Widerspruch sein.

Spezifikation einführen

Wenn man Spezifikationen in einem Projekt einführen will, dann fängt man am besten klein an. Ähnlich wie bei der Einführung von Testautomatisierung muss man dabei gegen das kontraintuitive Gefühl ankämpfen, mehr Dinge herzustellen zu müssen, um am Ende effizienter zu sein. Dass ein Drittel mehr Code für Unit-Tests schlussendlich spart statt kostet, musste man auch erst mal verinnerlichen. Erfahrungsgemäß tun sich Youngster damit leichter als alte Hasen.

Für den Einstieg entwirft man z. B. für die nächste etwas kompliziertere Aufgabe einfach mal vorher ein Aktogramm, um darauf basierend zu coden. Das muss nicht gleich perfekt und mit den Anforderern abgestimmt sein. Erst mal für sich selbst üben, um z. B. das Arbeiten im Flow anzustreben [24]. Ist der Feinentwurf nämlich vorweg entstanden, kann man sich hinterher unterbrechungsarm dem Coden widmen. Wenn mir das als Mensch gelingt, sind die Voraussetzungen für KI-Unterstützung gut.

Ein anderer bewährter Einstieg ist z. B. eine fragmentarische Beschreibung aus einem Reverse Engineering von Code, dessen unklare Funktionsweise wiederholt Ärger macht – übrigens auch eine Tätigkeit, bei der KI helfen kann. Der Effekt, dass alle Beteiligten plötzlich in natürlicher Sprache nachlesen können, wie das System an der fraglichen Stelle arbeitet, ist ein Augenöffner.

Vollständig in das Software-Engineering integrierte Spezifikationen nehmen in der oben dargestellten Form etwa ein Fünftel der Zeit ein, die das Implementieren benötigt. Das ist ein grober Erfahrungswert über 15 Jahre und diverse Projekte in verschiedenen Firmen hinweg. Dabei entsteht ein Shift-Left-Effekt [25], der die Aufwände in teureren Folgephasen des Entwicklungsprozesses deutlicher reduziert als neuer Aufwand in den frühen Phasen entsteht. Es braucht nur seine Zeit, bis es einem in die DNA übergeht.

Immer erst die Spezifikation aktualisieren, bevor man an den Code geht. Das muss sich so natürlich und indiskutabel anfühlen wie: Immer erst den Gurt anlegen, bevor man losfährt. Dann funktioniert es.

Stay tuned

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

[mc4wp-simple-turnstile]

 

Fazit

KI-Unterstützung in der Softwareentwicklung hat, der besonderen Natur von LLMs wegen, ihre Tücken. Wo sie einerseits verspricht, das Coding zu beschleunigen, deckt sie andererseits gnadenlos auf, wo in Design und Code bisher „herumgesumpft“ wurde. Detaillierte, abgestimmte Spezifikationen und saubere Codestrukturen sind die Leitplanken, die einer KI den Weg weisen, um sie auch in anspruchsvollen Projekten für mehr als eine bessere Internetsuche zu verwenden. Insofern ist der Zeitpunkt gerade günstig, sich auf solide Entwurfsarbeit zu besinnen. Neue Spezifikationstools sind am Start und die Investitionsbereitschaft für alles rund um KI-Unterstützung ist hoch. Das Prinzip „Design before Implementation“ hat sich über mindestens 30 Jahre als zeitlos erwiesen und es ist unwahrscheinlich, dass KI das ändert, wenn selbst Vibe Coder es erkennen.

Ist dieser Artikel eigentlich mit KI-Unterstützung entstanden? In der Tat ist er das, und zwar genau nach den Prinzipien, die hier für Software beschrieben sind. Der Entwurf des Artikels ist up-front aus menschlicher Arbeit und vielen, vielen Diskussionen entstanden. Aber wenn es um die „Implementierung“ ging, konnte die KI dabei helfen z. B. einen angefangenen Satz sauber formuliert zu Ende zu bringen.

The post AI braucht Design: Die Rolle von Spezifikationen in Enterprise AI appeared first on JAX.

]]>
Golden Master Testing für Legacy-Systeme https://jax.de/blog/golden-master-testing-fuer-legacy-systeme/ Fri, 13 Mar 2026 16:00:34 +0000 https://jax.de/?p=209952 In diesem Artikel stellt der Autor mit dem Golden Master Testing eine Testmethode für die Migration von Legacy-Systemen vor. Anstatt zu versuchen, unbekannte Fachlichkeit nachträglich zu spezifizieren, wird das bestehende Verhalten des Systems systematisch beobachtet und abgesichert. So entsteht ein belastbares Sicherheitsnetz, das Veränderungen ermöglicht, ohne vollständiges Fachwissen vorauszusetzen. Gerade im Kontext von Migrationsprojekten entwickelt sich Golden Master Testing damit vom reinen Testwerkzeug zu einem zentralen Baustein für eine kontrollierte, schrittweise Modernisierung.

The post Golden Master Testing für Legacy-Systeme appeared first on JAX.

]]>
Die Migration von Legacy-Systemen scheitert selten an Technologie. Die eigentliche Herausforderung liegt fast immer woanders: in der fehlenden oder nur noch teilweise bekannten Fachlichkeit des bestehenden Systems. Über Jahre gewachsene Logik, implizite Geschäftsregeln und historische Sonderfälle sind oft ausschließlich im Code verborgen – die Dokumentation ist veraltet, Domänenexperten haben das Unternehmen verlassen oder sind in den Ruhestand gegangen und fachliche Tests decken nur geringe Teile ab, wenn sie überhaupt vorhanden sind.
Gleichzeitig besteht ein hoher Veränderungsdruck. Softwareversionen sind veraltet, Plattformen werden gekündigt und Bibliotheken nicht mehr mit Sicherheitsupdates versorgt. Das bestehende System muss also modernisiert, eventuell entkoppelt und/oder auf eine neue Plattform überführt werden. All das muss natürlich ablaufen, ohne den laufenden Geschäftsbetrieb zu gefährden.

Stay tuned

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

[mc4wp-simple-turnstile]

 

Die vorhandene Testabdeckung bietet in der Regel nicht genug Sicherheit, um die Migration durchzuführen. Die Testabdeckung zu erhöhen, erweist sich in der Regel als schwierig, weil klassische Testansätze in dieser Situation schnell an ihre Grenzen stoßen: Unit-Tests erfordern ein Verständnis der fachlichen Intention, Integrationstests setzen stabile Zustände der Umsysteme (inkl. der Datenbank) voraus und fachliche Abnahmetests benötigen explizite Erwartungen – all das fehlt häufig genau dort, wo Migration am dringendsten ist. In solchen Situationen ist Golden Master Testing für uns das Mittel der Wahl.

Golden Master Testing: die Grundidee

Die Grundidee dieses Ansatzes ist ebenso einfach wie wirkungsvoll: Statt das gewünschte Verhalten eines Systems zu beschreiben, wird das tatsächlich beobachtbare Verhalten als Referenz festgehalten.

Ein Golden Master ist dabei keine Spezifikation im klassischen Sinn, sondern quasi ein Snapshot des aktuellen Systemverhaltens. Für definierte Eingaben wird die erzeugte Ausgabe aufgezeichnet und als Vergleichsbasis für zukünftige Änderungen verwendet. Solange sich die Ausgaben nicht verändern, gilt das Verhalten als stabil – unabhängig davon, wie sich die interne Struktur des Systems entwickelt.

Dieser Ansatz ist besonders dort hilfreich, wo das fachliche Warum eines Systems nicht mehr vollständig bekannt ist. Golden-Master-Tests setzen bewusst außerhalb des Systems an. Sie betrachten es als Blackbox: Eingabe hinein, Ausgabe heraus. Welche Pfade im Code durchlaufen werden oder welche fachlichen Regeln dabei greifen, ist zunächst zweitrangig. Entscheidend ist allein, dass das beobachtete Verhalten reproduzierbar bleibt.

Wichtig ist dabei, zu verstehen, dass ein Golden Master kein Qualitätsurteil darstellt. Er konserviert bestehendes Verhalten – inklusive historisch gewachsener Sonderlogik, Inkonsistenzen oder fachlichen Fehlern. Golden Master Testing schafft somit zwar keine fachliche Klarheit, aber technische Sicherheit. Und diese ist häufig die notwendige Voraussetzung, um ein System überhaupt schrittweise zu modernisieren, zu refaktorisieren oder in eine neue Zielarchitektur zu überführen.

Damit verschiebt Golden Master Testing den Fokus von Spezifikation zu Stabilität: Erst wenn bekannt ist, was sich nicht verändern darf, wird es möglich, zu entscheiden, was sich tatsächlich verändern soll.

DIE KUNST DER SOTWARE-ARCHITEKTUR

Architecture & Design-Track entdecken

 

Abgrenzung zu anderen Testmethoden

Golden Master Testing wird häufig missverstanden – entweder als Ersatz für bestehende Testarten oder als spezielle Ausprägung von Integrationstests. Technisch ist das zwar richtig, organisatorisch nimmt der Ansatz jedoch eine eigene Rolle im Testportfolio ein und adressiert ein sehr spezifisches Problem: den fehlenden Zugang zur fachlichen Intention eines Systems.

Klassische Unit-Tests setzen voraus, dass die fachliche Bedeutung einzelner Codeeinheiten bekannt ist. Sie formulieren explizite Erwartungen an Methoden, Aggregates oder Services und prüfen diese isoliert. In Legacy-Systemen ist dieses Wissen jedoch oft nicht mehr vorhanden oder nur implizit im Code verankert. Golden-Master-Tests umgehen dieses Problem, indem sie nicht auf Codeeinheiten zielen, sondern auf beobachtbares Gesamtverhalten.

Integrations- und End-to-End-Tests prüfen das Zusammenspiel mehrerer Komponenten, also z. B. des Application Servers und der Datenbank, indem Requests über eine REST- oder (wahrscheinlich eher) SOAP-Schnittstelle abgesetzt werden. Solche Tests benötigen also klare Erwartungen, mit denen das tatsächliche Ergebnis verglichen werden kann. Golden-Master-Tests unterscheiden sich hier grundlegend: Sie bewerten nämlich gar nicht, ob ein Ergebnis korrekt ist, sondern lediglich, ob es sich im Vergleich zum bisherigen Verhalten verändert hat. Damit eignen sie sich besonders für instabile oder historisch gewachsene Systeme, die sich zwar technisch verändern lassen, deren Verhalten aber erhalten bleiben muss.

Häufig werden Golden-Master-Tests auch mit Approval-Tests gleichgesetzt. Auch hier gibt es tatsächlich technische Überschneidungen, da auch Letztere Ausgaben gegen gespeicherte Referenzen vergleichen. Der Unterschied liegt allerdings weniger im Werkzeug als im Ziel: Während Approval-Tests meist bewusst gestaltete, fachlich geprüfte Erwartungen absichern, dienen Golden-Master-Tests primär der Verhaltenskonservierung in Situationen fehlender Fachklarheit.

Im Unterschied zu klassischen Tests formulieren sie keine Erwartungen, sondern treffen eine Beobachtung. Sie beantworten nicht die Frage „Ist das Ergebnis fachlich korrekt?“, sondern „Liefert das neue System dasselbe Ergebnis wie das alte?“ Genau diese Verschiebung macht sie in Migrationsszenarien so wertvoll: Wo keine verlässliche Fachlichkeit verfügbar ist, kann zumindest fachliche Regression verhindert werden.

Entscheidend ist daher die richtige Einordnung: Golden Master Testing ist kein dauerhafter Ersatz für fachlich motivierte Tests. Vielmehr fungiert es als Einstiegspunkt. Es schafft die notwendige Sicherheit, um ein System schrittweise zu verändern, zu modularisieren oder zu migrieren. Erst auf dieser Grundlage können gezielt fachliche Tests ergänzt sowie veraltete Logiken hinterfragt und Domänenwissen wieder explizit gemacht werden. In einer nachhaltigen Teststrategie stehen Golden-Master-Tests somit nicht am Ende, sondern am Anfang.

Typische Einsatzszenarien

Nach dieser Beschreibung wird klar, dass Golden Master Testing seinen größten Nutzen in Situationen entfaltet, in denen Veränderung notwendig ist, das Risiko jedoch schwer abschätzbar bleibt. Besonders in Migrationsprojekten tritt diese Konstellation häufig auf.

Ein klassisches Einsatzszenario ist die technische Migration eines bestehenden Systems, etwa beim Wechsel der Plattform, der Programmiersprache oder der Laufzeitumgebung. Ob Hostsysteme, proprietäre Frameworks oder historisch gewachsene Eigenentwicklungen – häufig ist unklar, welche fachlichen Sonderfälle im Detail implementiert sind. Golden-Master-Tests ermöglichen es, das bestehende Verhalten vor der Migration zu erfassen und nach der Umsetzung gezielt zu vergleichen. Abweichungen werden sichtbar, ohne dass jede Regel explizit verstanden oder neu spezifiziert werden muss.

Auch beim schrittweisen Refactoring großer Legacy-Codebasen sind solche Tests ein bewährtes Mittel. Statt das System in einem großen Wurf umzubauen, können einzelne Module oder Komponenten isoliert verändert werden. Solange das beobachtete Verhalten stabil bleibt, lässt sich die interne Struktur kontinuierlich verbessern. Golden-Master-Tests sorgen dafür, dass strukturelle Änderungen möglich werden, ohne unbeabsichtigte fachliche Regressionen zu riskieren.

Ein weiteres häufiges Szenario ist die Extraktion von Modulen oder Services aus monolithischen Systemen. Bei der Abgrenzung fachlicher Verantwortlichkeiten ist das tatsächliche Verhalten oft aussagekräftiger als die vorhandene Dokumentation. Golden-Master-Tests helfen dabei, den bisherigen Funktionsumfang eines Moduls explizit festzuhalten und ihn beim Übergang in eine neue Architektur abzusichern.

Besonders relevant ist der Ansatz zudem bei Batchverarbeitung und datengetriebenen Prozessen. Hier sind fachliche Regeln oft über Jahrzehnte hinweg gewachsen und eng mit Datenformaten, Sonderfällen und historischen Annahmen verknüpft. Golden-Master-Tests ermöglichen es, diese Logik zunächst als Ganzes zu stabilisieren, bevor sie schrittweise analysiert, vereinfacht oder neu implementiert wird.
Gemeinsam ist all diesen Szenarien, dass das Testing nicht als langfristige Strategie verstanden wird, sondern als Enabler für kontrollierte Veränderung. Dort, wo vollständiges Verständnis fehlt, aber Verlässlichkeit erforderlich ist, bietet der Ansatz einen pragmatischen Weg, Migrationen schrittweise und mit kalkulierbarem Risiko umzusetzen.

Vorteile des Ansatzes

Der zentrale Vorteil des Golden Master Testings liegt in der frühen Herstellung von Sicherheit. In Situationen, in denen weder fachliche Spezifikationen noch belastbare Tests existieren, ermöglicht der Ansatz einen schnellen Einstieg in die Absicherung des Systemverhaltens. Damit wird Veränderung überhaupt erst verantwortbar.

Ein wesentlicher Nutzen besteht im geringen Bedarf an fachlichem Vorwissen. Golden-Master-Tests setzen nicht voraus, dass Geschäftsregeln vollständig verstanden oder neu modelliert werden. Stattdessen machen sie das bestehende Verhalten explizit und überprüfbar. Gerade in Migrationsprojekten, in denen Domänenexperten nur eingeschränkt verfügbar sind, ist das ein entscheidender Vorteil.

Darüber hinaus unterstützen die Tests eine inkrementelle Vorgehensweise. Veränderungen können schrittweise vorgenommen werden, etwa Modul für Modul oder Schnittstelle für Schnittstelle. Jede Anpassung lässt sich unmittelbar gegen das bisherige Verhalten prüfen. Abweichungen werden frühzeitig sichtbar, bevor sie sich in der Zielarchitektur verfestigen oder in spätere Projektphasen verschieben.
Ein weiterer Vorteil liegt in der Unabhängigkeit von internen Strukturen. Da Golden-Master-Tests auf beobachtbares Verhalten abzielen, bleiben sie auch dann gültig, wenn sich die interne Architektur grundlegend ändert. Daher sind sie besonders geeignet für tiefgreifende Refactorings, Plattformwechsel oder Sprachmigrationen.

Nicht zuletzt schaffen die Tests Transparenz über das tatsächliche Systemverhalten. Sie machen implizite Annahmen, Sonderfälle und historische Logik sichtbar, die zuvor im Code verborgen waren. Diese Transparenz bildet eine wichtige Grundlage, um in späteren Schritten fachliche Klarheit zu schaffen, Tests gezielt zu verfeinern und bewusste Entscheidungen über notwendige fachliche Änderungen zu treffen.

Zusammengefasst bietet Golden Master Testing keinen perfekten Testansatz, aber einen pragmatischen und wirkungsvollen Einstieg: Es reduziert Risiken, erhöht die Veränderungsgeschwindigkeit und schafft Vertrauen – genau dort, wo Migration sonst häufig ins Stocken gerät.

Grenzen und Risiken

So hilfreich Golden Master Testing in Migrationsprojekten ist, so wichtig ist ein realistischer Blick auf seine Grenzen. Der Ansatz schafft Sicherheit durch Stabilität – und genau darin liegt zugleich sein größtes Risiko.

Golden-Master-Tests sichern bestehendes Verhalten ab, unabhängig davon, ob dieses Verhalten fachlich korrekt, konsistent oder noch zeitgemäß ist. Historische Sonderfälle, implizite Workarounds oder längst überholte Geschäftsregeln werden mit konserviert. Wird dieses Verhalten ungeprüft in eine neue Zielarchitektur übernommen, so besteht die Gefahr, fachliche Altlasten zu zementieren, statt sie schrittweise abzubauen.

Ein weiteres Risiko liegt in der Fragilität der Tests. Viele Legacy-Systeme produzieren Ausgaben, die nicht vollständig deterministisch sind. Diese Aspekte müssen erkannt und aktiv angegangen werden. Ansonsten führen Golden-Master-Tests zu häufigen, wenig aussagekräftigen Abweichungen. In der Praxis untergräbt das schnell das Vertrauen in die Tests und damit ihren eigentlichen Zweck. Auf das Thema kommen wir weiter unten noch zurück.

Zudem liefern Golden-Master-Tests keine Erklärung für beobachtetes Verhalten. Sie zeigen, dass sich etwas verändert hat, nicht warum. Für tiefere fachliche Analysen, Fehlerursachen oder bewusste Weiterentwicklungen sind zusätzliche Schritte erforderlich. Ohne begleitende Refactorings, fachliche Klärung und gezielte Tests bleibt der Ansatz rein konservierend.

Schließlich besteht die Gefahr, Golden Master Testing als dauerhafte Lösung misszuverstehen. Wird der Ansatz nicht bewusst als Übergangswerkzeug eingesetzt, kann er zu einem Stillstand führen, bei dem Veränderung zwar technisch möglich, fachlich aber nie hinterfragt wird. Auf dieses Problem gehe ich im Abschnitt „Fallstricke und Antipatterns“ näher ein.

Golden-Master-Tests sind also kein Selbstzweck. Ihr Wert entsteht nur dann, wenn sie als temporäre Lösung verstanden werden – mit dem klaren Ziel, auf dieser Basis fachliches Verständnis aufzubauen, Tests zu verfeinern und das System langfristig weiterzuentwickeln.

Stay tuned

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

[mc4wp-simple-turnstile]

 

Einordnung in eine nachhaltige Teststrategie

Golden Master Testing entfaltet seine Stärke vor allem als Einstieg in unsicheres Terrain. In einer nachhaltigen Teststrategie nimmt der Ansatz daher eine klar begrenzte, aber zentrale Rolle ein: Er schafft Stabilität dort, wo Verständnis fehlt, und ermöglicht so die nächsten Schritte.

Zu Beginn einer Migration oder Modernisierung dient der Golden Master als Sicherheitsnetz. Er schützt vor unbeabsichtigten fachlichen Regressionen, während technische Strukturen verändert, Abhängigkeiten reduziert oder Module neu zugeschnitten werden. In dieser Phase steht nicht fachliche Korrektheit im Vordergrund, sondern die Gewissheit, dass sich das beobachtete Verhalten nicht unbemerkt verändert.

Mit zunehmendem Verständnis des Systems sollte sich der Schwerpunkt jedoch verschieben. Einzelne fachliche Bereiche werden expliziter, implizite Regeln werden sichtbar, und erste Domänenkonzepte lassen sich benennen. An diesem Punkt verlieren Golden-Master-Tests ihre Rolle als primäres Absicherungsinstrument und machen Platz für gezielte fachliche Tests: Unit-Tests auf Domänenebene, Integrationstests für klar definierte Schnittstellen und explizite Akzeptanzkriterien.

Wichtig ist dabei ein bewusster Umgang mit bestehenden Golden-Master-Tests. Sie können schrittweise ersetzt, reduziert oder auf grobe Regressionen beschränkt werden, während feinere fachliche Tests an ihre Stelle treten. Auf diese Weise wird das ursprünglich konservierte Verhalten nicht einfach eingefroren, sondern kontrolliert weiterentwickelt.

In einer langfristigen Perspektive sind Golden-Master-Tests somit kein Endzustand, sondern ein Katalysator für Lernprozesse. Sie ermöglichen es, Migrationen zu starten, bevor vollständiges Fachwissen verfügbar ist – und schaffen zugleich die Voraussetzung, dieses Wissen im Lauf des Projekts wieder aufzubauen und explizit zu machen.

Herausforderungen bei der technischen Umsetzung

Golden Master Testing bietet damit einen pragmatischen Ansatz, um Migration auch unter unsicheren fachlichen Rahmenbedingungen kontrollierbar zu machen. Indem das bestehende Verhalten systematisch beobachtet und abgesichert wird, entsteht die Sicherheit für tiefgreifende technische Veränderungen.

Die eigentliche Herausforderung beginnt jedoch dort, wo die Theorie in die Praxis überführt wird. Die Wirksamkeit von Golden-Master-Tests hängt maßgeblich von konkreten Entscheidungen ab: an welchen Stellen angesetzt wird, welche Szenarien abgesichert werden und wie mit instabilem oder historisch gewachsenem Verhalten umzugehen ist. Unbedachte Implementierungen führen schnell zu fragilen Tests, die mehr behindern als helfen.

Im nächsten Abschnitt widme ich mich daher der praktischen Umsetzung. Er zeigt, wie geeignete Einstiegspunkte identifiziert werden, wie aussagekräftige Testfälle ausgewählt werden und welche Gestaltungsprinzipien Golden-Master-Tests in Migrationsprojekten tatsächlich tragfähig machen.

Geeignete Einstiegspunkte finden

Der Erfolg der Methode hängt weniger von der Anzahl der Tests ab als von der Wahl der richtigen Einstiegspunkte. Nicht jeder Codebereich eignet sich gleichermaßen, um beobachtbares Verhalten stabil abzusichern. Gerade in Legacy-Systemen ist es entscheidend, dort anzusetzen, wo fachliche Wirkung entsteht.

Geeignete Einstiegspunkte sind in der Regel die öffentlich sichtbaren Schnittstellen. Dazu zählen externe APIs, Batchprogramme, Dateischnittstellen oder fachliche Services, die von anderen Systemen oder Prozessen genutzt werden. Aber auch ein eventuell vorhandenes UI ist ein guter Einstiegspunkt. Dort ist es erfahrungsgemäß allerdings aufwendiger, Tests zu realisieren, worauf ich im nächsten Abschnitt eingehe. Diese Schnittstellen bilden das tatsächliche Verhalten des Systems ab, das auch von Umsystemen und/oder Anwendern erwartet wird – unabhängig davon, wie komplex oder unübersichtlich die interne Implementierung ist.

Tief verschachtelte interne Methoden oder technische Hilfsfunktionen sind hingegen weniger geeignet. Sie besitzen oft keine eigenständige fachliche Bedeutung und ändern sich im Zuge von Refactorings zwangsläufig. Golden-Master-Tests auf dieser Ebene führen schnell zu instabilen Tests, die strukturelle Änderungen verhindern, statt sie abzusichern.

Bei der Auswahl von Einstiegspunkten helfen drei zentrale Kriterien: fachliche Relevanz, Stabilität und klar definierte Ein- und Ausgaben. Fachlich relevante Einstiegspunkte sind solche, deren Verhalten für nachgelagerte Systeme oder Geschäftsprozesse kritisch ist. Stabil sind Einstiegspunkte, deren Schnittstellen sich im Rahmen der Migration möglichst wenig ändern sollen. Klare Ein- und Ausgaben erleichtern schließlich die Vergleichbarkeit und reduzieren den Aufwand zur Stabilisierung der Tests.

In Migrationsprojekten empfiehlt es sich zudem, Einstiegspunkte entlang geplanter Migrationsschritte zu wählen. Wird ein Modul oder Service schrittweise ersetzt oder neu implementiert, sollte genau dieser Übergangspunkt durch Golden-Master-Tests abgesichert werden. Auf diese Weise entsteht ein direktes Feedback darüber, ob das neue System das bisherige Verhalten zuverlässig reproduziert.

Die bewusste Auswahl weniger und gut geeigneter Einstiegspunkte ist damit ein wesentlicher Erfolgsfaktor. Sie sorgt dafür, dass Golden-Master-Tests nicht zur Belastung werden, sondern gezielt dort Sicherheit schaffen, wo sie für die Migration den größten Nutzen haben.

Migration der Benutzeroberfläche

Die Migration der Benutzeroberfläche wird in Legacy-Projekten häufig unterschätzt, da in ihr oft implizite Fachlichkeit steckt. Bildschirmfolgen, Validierungen oder Navigationslogiken transportieren fachliches Verhalten, das selten dokumentiert ist. Gleichzeitig sind die UI-Technologien häufig veraltet und der Gedanke: „Wenn wir schon migrieren, dann auch das UI“ liegt nahe. Es ist allerdings davon abzuraten, Backend-Logik und UI gleichzeitig zu migrieren, weil die Absicherung durch Golden-Master-Tests dann besonders schwierig wird.

Ein Vorgehen, dass sich in unseren Projekten bewährt hat, besteht darin, die bestehende UI im ersten Schritt unverändert zu lassen und eine neue Service-Schicht, etwa in Form eines REST API, unter der Oberfläche einzuziehen. Um sicherzustellen, dass das fachlich korrekt geschieht, können UI-basierte Golden-Master-Tests von Beginn an eingesetzt werden. End-to-End-Tests mit Werkzeugen wie Playwright bedienen dann die bestehende Oberfläche automatisiert und überprüfen, ob sich das sichtbare Verhalten für die Nutzer trotz neuer Schnittstellen nicht verändert.

Sobald diese Absicherung etabliert ist, wird die Service-Schicht zum fachlichen Einstiegspunkt und kann selbst durch Golden-Master-Tests abgesichert werden. Erst danach wird das UI migriert oder neu implementiert – mit der Sicherheit, dass die Fachlichkeit durch die bestehenden Tests unverändert bleibt. So wird das UI schrittweise austauschbar, ohne implizite Fachlichkeit zu verlieren.

Testfälle auswählen – Qualität vor Quantität

Ein häufiger Fehler bei der Einführung von Golden-Master-Tests ist der Versuch, möglichst viele Testfälle zu erfassen. Gerade in Legacy-Systemen mit komplexem oder historisch gewachsenem Verhalten führt dieser Ansatz jedoch schnell zu hohem Pflegeaufwand und geringer Aussagekraft. Entscheidend ist nicht die Menge der Tests, sondern wie repräsentativ sie sind.

Ziel der Testfallauswahl ist es, typische und kritische Verhaltensmuster des Systems sichtbar zu machen. Bewährt hat sich eine bewusste Mischung aus Normalfällen, Grenzfällen und historisch relevanten Sonderfällen. Normalfälle bilden den alltäglichen Betrieb ab und sorgen dafür, dass zentrale Geschäftsprozesse stabil bleiben. Grenzfälle decken Eingaben an fachlichen oder technischen Grenzen ab und helfen, unerwartete Regressionen frühzeitig zu erkennen. Sonderfälle schließlich spiegeln häufig genau jene implizite Logik wider, die über Jahre hinweg in das System eingebaut wurde und bei Migrationen besonders risikobehaftet ist.

Bei der Auswahl von Testfällen ist zudem Zurückhaltung geboten. Jeder Golden-Master-Test konserviert Verhalten – und damit auch Komplexität. Tests sollten daher nur dort entstehen, wo ihr Schutz tatsächlich benötigt wird. Ein kleiner, gut gewählter Satz an Testfällen ist in der Regel wertvoller als eine breite, aber unstrukturierte Abdeckung.

Produktionsdaten können bei der Testfallerstellung eine wertvolle Grundlage sein, erfordern jedoch besondere Sorgfalt. Sie sollten anonymisiert, reproduzierbar und fachlich nachvollziehbar sein. Eine unreflektierte Übernahme großer Datenmengen führt häufig zu schwer wartbaren Tests, deren Aussagekraft mit der Zeit abnimmt.

Letztlich ist die Auswahl von Golden-Master-Testfällen immer eine Risikoentscheidung. Sie orientiert sich nicht an theoretischer Vollständigkeit, sondern an der Frage, welches Verhalten bei der Migration auf keinen Fall unbeabsichtigt verändert werden darf. Diese bewusste Fokussierung macht Golden Master Testing praktikabel und verhindert, dass der Ansatz selbst zum Hindernis für Veränderung wird.

Umgang mit nichtdeterministischem Verhalten

Golden-Master-Tests setzen voraus, dass identische Eingaben zu vergleichbaren Ausgaben führen. In der Praxis ist diese Annahme bei Legacy-Systemen jedoch häufig nicht erfüllt. Zeitabhängige Werte, zufällige Identifikatoren, implizite Sortierungen, externe Datenbanken oder andere externe Systeme führen dazu, dass sich Ausgaben trotz unverändertem fachlichem Verhalten unterscheiden. Ohne bewussten Umgang mit dieser Nichtdeterministik verlieren Golden-Master-Tests schnell ihre Aussagekraft.

Typische Ursachen nichtdeterministischen Verhaltens sind Zeitstempel, fortlaufende Nummern, technische IDs oder kontextabhängige Reihenfolgen in Listen und Reports. Auch externe Abhängigkeiten, etwa Datenbankzustände oder angebundene Systeme, können zu Abweichungen führen, die fachlich irrelevant, für den Testvergleich aber kritisch sind.

Der erste Schritt besteht darin, solche instabilen Anteile gezielt zu identifizieren. Abweichungen sollten nicht reflexartig akzeptiert oder ignoriert werden, sondern Anlass zur Analyse sein: Welche Teile der Ausgabe variieren und warum? Diese Transparenz ist Voraussetzung für jede weitere Stabilisierung.

Im zweiten Schritt werden die betroffenen Ausgabebereiche normalisiert. Zeitwerte können beispielsweise auf feste Referenzen gesetzt, technische IDs maskiert oder nicht relevante Felder aus dem Vergleich ausgeschlossen werden. Wichtig ist dabei, bewusst zwischen fachlich relevanten und rein technischen Unterschieden zu differenzieren. Alles, was fachliche Bedeutung hat, sollte weiterhin Bestandteil des Vergleichs bleiben.

Nicht jede Quelle von Nichtdeterminismus lässt sich sinnvoll beherrschen. In solchen Fällen ist zu prüfen, ob der gewählte Einstiegspunkt überhaupt für Golden-Master-Tests geeignet ist oder ob der Vergleich auf eine gröbere Ebene gehoben werden sollte. Manchmal ist ein weniger detaillierter, aber stabiler Vergleich wertvoller als ein vollständiger, aber fragiler Golden Master.
Der Umgang mit nichtdeterministischem Verhalten erfordert Disziplin und klare Entscheidungen. Wird diese Arbeit frühzeitig investiert, entstehen belastbare Tests, die strukturelle Veränderungen absichern, ohne bei jeder Ausführung Fehlalarme zu produzieren. Vernachlässigt man diesen Schritt, wird Golden Master Testing schnell als unzuverlässig wahrgenommen – und damit seines eigentlichen Nutzens beraubt.

DIE KUNST DER SOTWARE-ARCHITEKTUR

Architecture & Design-Track entdecken

 

Typische Fallstricke und Antipatterns

Golden Master Testing ist in Migrationsprojekten ein äußerst wirkungsvolles Instrument – zugleich aber auch anfällig für Fehlanwendungen. Viele der Probleme entstehen nicht durch das Konzept selbst, sondern durch falsche Erwartungen und mangelnde Disziplin im Umgang mit den Tests.

Ein verbreiteter Irrtum besteht darin, Golden-Master-Tests als Ersatz für fachliche Tests zu betrachten. Sie sichern ausschließlich das beobachtbare Verhalten eines Systems ab, nicht jedoch dessen fachliche Bedeutung oder Korrektheit. Wird dieser Unterschied nicht bewusst gemacht, bleibt Fachlichkeit dauerhaft implizit. Das Team vergleicht nur noch mit dem Vergangenen, ohne das Verhalten fachlich zu hinterfragen oder weiterzuentwickeln. Die Migration wird dadurch zwar technisch abgesichert, fachlich jedoch nicht beherrschbar.

Ein weiteres häufiges Problem ist die unkontrollierte Ausweitung der Golden-Master-Test-Suite. Es bringt nichts, automatisiert alle möglichen Varianten von Eingaben zu generieren und sie gegen das System zu schicken. In solchen automatisierten Set-ups entstehen schnell sehr große Mengen an Tests, deren Pflegeaufwand und Laufzeit stetig wachsen. Die eigentliche Aussagekraft der Tests leidet darunter, weil Abweichungen immer schwerer zu interpretieren sind. Statt gezielt Sicherheit zu schaffen, erzeugen solche Testlandschaften Unsicherheit und führen dazu, dass Warnungen ignoriert oder pauschal akzeptiert werden.

Besonders kritisch ist der bereits erwähnte Umgang mit nichtdeterministischem Verhalten. Dieses Verhalten darf nicht toleriert werden. Es muss sichergestellt sein, dass die Tests immer grün sind, wenn sich nichts Fachliches verändert hat. Ansonsten gehen echte fachliche Abweichungen im ständigen Rauschen unter. Ein instabiler Golden Master ist gefährlicher als gar keiner, da er vermeintliche Sicherheit vorgaukelt, wo keine existiert.

Eng damit verbunden ist das blinde Akzeptieren von Abweichungen. Werden Referenzausgaben aktualisiert, ohne die Ursache der Differenz verstanden zu haben, wird Wissen dauerhaft vernichtet. Fachliche Änderungen, Regressionen oder unbeabsichtigte Effekte werden stillschweigend in den Golden Master übernommen und damit legitimiert. Der Test dokumentiert dann nicht mehr das Verhalten des Legacy-Systems, sondern lediglich den aktuellen Zustand – unabhängig davon, ob dieser fachlich korrekt ist.

Auch die Wahl der Vergleichsebene ist ein häufiger Stolperstein. Zu feingranulare Vergleiche auf rein technischer Ebene, etwa vollständige Datenbank-Dumps oder detaillierte interne Strukturen, reagieren extrem empfindlich auf Änderungen in der Implementierung. Der fachliche Effekt geht dabei verloren, während der Wartungsaufwand steigt. Golden-Master-Tests sollten sich daher an der fachlichen Wirkung orientieren, nicht an technischen Details.

Das vielleicht subtilste Antipattern ist das dauerhafte Festhalten an diesen Tests. Was ursprünglich als temporäre Lösung für eine regressionsfreie Migration gedacht war, wird aus Bequemlichkeit, Unsicherheit oder aus Kostengründen zur Dauerlösung. In solchen Fällen bleibt Fachlichkeit dauerhaft implizit, fachliche Tests werden nie aufgebaut und die Migration bleibt unvollständig. Der eigentliche Erfolg von Golden Master Testing liegt darin, schrittweise überflüssig zu werden, sobald Fachlichkeit verstanden, modelliert und explizit getestet ist.

Richtig eingesetzt schaffen Golden-Master-Tests Vertrauen in Veränderungen bei unbekannter Fachlichkeit. Falsch eingesetzt konservieren sie genau die Intransparenz, die eine Migration eigentlich überwinden soll.

KI-Ensatz zur Generierung von Golden-Master-Tests

Der initiale Aufbau von Golden-Master-Tests ist in Migrationsprojekten oft der größte Hemmschuh. Gerade bei umfangreichen Legacy-Systemen fehlt es an Dokumentation, fachlichem Wissen und klaren Einstiegspunkten. Hier kann der gezielte Einsatz von KI einen entscheidenden Unterschied machen.

KI eignet sich besonders gut, um bestehende Legacy-Artefakte systematisch auszuwerten. Quellcode, Schnittstellenbeschreibungen, Datenformate oder bestehende Batchjobs enthalten implizites Wissen darüber, wie das System genutzt wird und welche Eingaben zu welchen Ausgaben führen. KI-gestützte Analysen können diese Informationen bündeln und daraus Vorschläge für sinnvolle Testfälle ableiten. Statt Tests manuell aus fachlichen Annahmen zu entwerfen, entstehen Golden-Master-Tests direkt aus dem tatsächlich beobachtbaren Verhalten des Systems.

Ein zentraler Mehrwert liegt dabei in der Skalierung. Während manuell erstellte Golden-Master-Tests meist auf wenige exemplarische Fälle beschränkt bleiben, kann KI eine große Bandbreite realistischer Szenarien abdecken. Insbesondere in Kombination mit Tools zur Messung der Testabdeckung, etwa JaCoCo, deren Ergebnisse als Input für KI dienen, kann sie automatisch typische Eingabekombinationen, Randfälle und Sonderlogiken auf Basis der Codeverzweigungen identifizieren und gezielt Tests generieren. So lassen sich auch selten genutzte, aber potenziell geschäftskritische Ablaufpfade absichern.
Wichtig ist jedoch, KI nicht als autonomes Werkzeug zu verstehen, das dauerhaft Tests schreibt. Die generierten Tests liefern eine gute Basis für das initiale Erstellen der Golden-Master-Tests. Sobald es in die fachliche Bewertung geht, ist das Team wieder gefordert. Gerade bei Abweichungen zwischen Legacy- und Zielsystem ist menschliche Interpretation unverzichtbar, um zu entscheiden, ob es sich um fachlich relevante Unterschiede oder um technische Artefakte handelt.

Richtig eingesetzt beschleunigt KI vor allem die frühe Phase der Migration. Sie reduziert den manuellen Aufwand, senkt die Einstiegshürde und macht Golden Master Testing auch bei sehr großen Systemen praktikabel. Gleichzeitig bleibt die Verantwortung klar beim Entwicklungsteam: KI generiert Tests, aber sie entscheidet nicht über fachliche Korrektheit.

Langfristig unterstützt KI auch den Übergang weg vom Golden Master. Erkenntnisse aus den generierten Tests lassen sich nutzen, um fachliche Regeln explizit zu formulieren und in intentionale Tests zu überführen. So wird KI nicht nur zum Beschleuniger der Absicherung, sondern auch zum Katalysator für das fachliche Verständnis des Legacy-Systems.

Der Einsatz von KI verändert damit nicht das Prinzip des Golden Master Testing, sondern dessen Wirtschaftlichkeit. Was früher Wochen oder Monate manueller Arbeit erforderte, kann heute in kurzer Zeit vorbereitet werden – ohne den Anspruch an fachliche Sorgfalt aufzugeben.

Nach den Tests ist vor der Migration – auch mit KI?

Sobald belastbare Golden-Master-Tests etabliert sind, verändert sich die Rolle von KI im Migrationsprojekt grundlegend. Während KI zuvor vor allem beim Aufbau der Tests unterstützt hat, kann sie nun direkt zur Umsetzung der Migration beitragen. Die Golden-Master-Tests bilden dabei den entscheidenden Sicherheitsanker: Sie definieren das erwartete Verhalten des Systems unabhängig von Technologie und Implementierung.

Auf dieser Grundlage kann KI gezielt eingesetzt werden, um Legacy-Code zu analysieren, zu transformieren oder neu zu strukturieren. Da das gewünschte Verhalten durch die Golden-Master-Tests bereits abgesichert ist, entsteht ein geschützter Raum für automatisierte oder teilautomatisierte Änderungen. KI-gestützte Refactorings, Übersetzungen zwischen Programmiersprachen und die Extraktion von Services können iterativ geschehen, ohne dass jede Änderung manuell fachlich verifiziert werden muss.

Ein wesentlicher Vorteil liegt in der Möglichkeit, alternative Implementierungen zu vergleichen. KI kann verschiedene Migrationsstrategien vorschlagen oder unterschiedliche Zielarchitekturen unterstützen, während die Golden-Master-Tests zuverlässig anzeigen, ob das fachliche Verhalten erhalten bleibt. Dadurch verschiebt sich der Fokus des Teams von der reinen Absicherung hin zur bewussten Gestaltung der Zielarchitektur.

Darüber hinaus lassen sich die Tests als Feedbackmechanismus für KI-gestützte Transformationen nutzen. Schlägt eine Migration fehl oder entstehen unerwartete Abweichungen, liefern sie präzise Hinweise darauf, an welchen Stellen fachliches Verhalten verändert wurde. Diese Rückkopplung ermöglicht es, KI iterativ zu steuern und schrittweise bessere Ergebnisse zu erzielen, statt große, riskante Migrationsschritte zu gehen.

Auch bei der Modularisierung bestehender Systeme entfaltet diese Kombination ihren Nutzen. KI kann dabei helfen, fachlich zusammengehörige Logik zu identifizieren, Abhängigkeiten sichtbar zu machen und Kandidaten für eigenständige Module oder Services vorzuschlagen. Die Golden-Master-Tests stellen sicher, dass diese Schnitte fachlich korrekt bleiben und nicht unbeabsichtigt Verhalten verlieren oder verändern.

Entscheidend ist dabei, dass KI nicht als autonomer Migrator verstanden wird. Sie arbeitet innerhalb eines klar definierten Rahmens, der durch Golden-Master-Tests und fachliche Entscheidungen des Teams vorgegeben ist. Die Verantwortung für Architektur, Fachlichkeit und Priorisierung bleibt weiterhin beim Menschen.

In dieser Rolle wird KI zu einem wirkungsvollen Verstärker: Sie beschleunigt Analyse und Umsetzung, während die Tests die notwendige Sicherheit liefern. Gemeinsam ermöglichen sie eine Migration, die schrittweise, kontrolliert und auch bei unbekannter Fachlichkeit verantwortbar bleibt.

Fachlichkeit zurückgewinnen statt nur bewahren

In vielen Legacy-Systemen ist Fachlichkeit nicht nur technisch verborgen, sondern auch organisatorisch entkoppelt. Wissen steckt in einzelnen Köpfen, in externen Dienstleistern oder in historisch gewachsenen Prozessen, die niemand mehr vollständig überblickt. Entscheidungen werden vermieden oder vertagt, weil niemand sicher sagen kann, welches Verhalten fachlich korrekt ist. Migration wird in solchen Situationen zwangsläufig defensiv: Ziel ist es, nichts kaputt zu machen – und nicht, das System aktiv weiterzuentwickeln.

Das Zusammenspiel aus Golden Master Testing und KI bietet hier eine Chance zur organisatorischen Neuausrichtung. Die Tests machen Systemverhalten unabhängig von Personen reproduzierbar. Sie entziehen implizitem Wissen seinen exklusiven Charakter und überführen es in ein gemeinsames, überprüfbares Artefakt. Fachliche Diskussionen stützen sich nicht länger auf Erinnerungen oder Vermutungen, sondern auf beobachtbares Verhalten.

KI verstärkt diesen Effekt, indem sie große Mengen an Verhalten systematisch erschließt und sichtbar macht. Nutzungsmuster, Sonderfälle und Abhängigkeiten werden transparent, ohne dass das Wissen einzelner Experten vorausgesetzt wird. Damit verändert sich die Gesprächsgrundlage mit der Fachabteilung: Statt „Wer weiß, wie das früher gemeint war?“, tritt die Frage „Wollen wir dieses Verhalten künftig noch?“ in den Vordergrund.

Dieser Perspektivwechsel ist organisatorisch entscheidend. Verantwortung für Fachlichkeit kann wieder im Team verankert werden, weil Entscheidungen auf einer belastbaren Basis getroffen werden. Golden-Master-Tests schaffen Sicherheit, KI reduziert Analyseaufwand – und gemeinsam ermöglichen sie es, fachliche Verantwortung bewusst zu übernehmen, statt sie weiter zu delegieren oder zu konservieren.

Mit fortschreitender Migration verschiebt sich die Rolle der Tests. Fachliche Regeln werden explizit formuliert, Golden-Master-Tests schrittweise abgelöst und Wissen in Modelle, Dokumentation und fachliche Tests überführt. Das System wird damit nicht nur technisch modernisiert, sondern organisatorisch neu verankert.

Migration wird so zu mehr als einem Umbau: Sie wird zu einem Prozess der Rückaneignung. Die Organisation gewinnt Entscheidungsfähigkeit zurück, Abhängigkeiten werden reduziert und Fachlichkeit wird wieder gestaltbar. Genau darin liegt der nachhaltige Wert dieses Vorgehens.

Stay tuned

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

[mc4wp-simple-turnstile]

 

Fazit: Sichere Migration beginnt mit beobachtbarem Verhalten

Migrationen scheitern selten an Technologie, sondern vielmehr an fehlender fachlicher Sicherheit. Legacy-Systeme enthalten oft jahrzehntelang gewachsene Fachlichkeit, die nur implizit im Code, in Datenstrukturen oder in Prozessen existiert. Hier setzt Golden-Master-Testing an: Es ermöglicht, bestehendes Verhalten zu konservieren, ohne es vollständig verstehen zu müssen, und schafft damit die Sicherheit für tiefgreifende Veränderungen.

Richtig eingesetzt sind diese Tests kein Ersatz für fachliche Tests, sondern ein Übergangswerkzeug. Sie machen implizites Wissen reproduzierbar und liefern die Grundlage für bewusste Entscheidungen. Abweichungen werden sichtbar, analysiert und fachlich eingeordnet. So entsteht die Möglichkeit, Fachlichkeit nicht nur zu bewahren, sondern aktiv zurückzugewinnen. Wissen, das zuvor in einzelnen Köpfen, alten Prozessen oder externen Dienstleistern verborgen war, wird ins Team zurückgeführt und kann bewusst gestaltet werden.

Der gezielte Einsatz von KI verstärkt diesen Ansatz erheblich. KI unterstützt beim Aufbau der Tests. Sie identifiziert Muster, macht Sonderfälle transparent und liefert Vorschläge für Refactoring oder Modularisierung – abgesichert durch die Golden-Master-Tests. Die Verantwortung für Fachlichkeit, Architektur und Priorisierung bleibt dabei beim Team, das nun auf einer stabilen und überprüfbaren Basis agiert.

Langfristig ermöglicht dieses Vorgehen eine Migration, die sowohl technisch als auch organisatorisch tragfähig ist. Golden-Master-Tests werden schrittweise durch explizite fachliche Tests ersetzt, Wissen wird dokumentiert und Fachlichkeit wieder gestaltbar. Migration wird so nicht nur zum Umbau von Systemen, sondern zum Prozess der Rückaneignung von Fachlichkeit und Verantwortung. Ihr größter Erfolg liegt darin, dass sie das Team befähigt, Entscheidungen bewusst zu treffen – sicher, nachvollziehbar und nachhaltig.

The post Golden Master Testing für Legacy-Systeme appeared first on JAX.

]]>
Moderne Softwarearchitektur verstehen https://jax.de/blog/moderne-softwarearchitektur-verstehen/ Mon, 10 Feb 2025 15:48:42 +0000 https://jax.de/?p=107098 Softwarearchitektur beschreibt die Struktur eines Softwaresystems und die Entscheidungen, die zu dieser Struktur führen. Entwicklungsteams großer oder komplexer Systeme sollten sich intensiv mit Softwarearchitektur beschäftigen, um stabile und effektive Lösungen zu schaffen. Doch was genau bedeutet der Begriff „Softwarearchitektur“ und wer trägt die Verantwortung dafür?

The post Moderne Softwarearchitektur verstehen appeared first on JAX.

]]>
Was gehört zu Softwarearchitektur?

Sicher kennen Sie die typischen Grundrisspläne von Gebäuden oder ihrer eigenen Wohnung. Da finden Sie Mauern, Türen, Fenster schematisch dargestellt (Abb. 1). Niemand in der Baubranche käme auf die Idee, ein Gebäude ohne derartige Pläne zu beginnen.

Abb. 1: Typischer Grundriss eines Gebäudes [1]

In der Informatik zeigen solche Bilder dann Komponenten („Kästchen“) und deren gegenseitige Abhängigkeiten („Pfeile“). Damit können wir die statische Struktur von IT-Systemen zeigen, also den Aufbau des Quellcodes im Großen. Solche strukturellen Pläne („Grundrisse“) stellen allerdings nur einen Teil der Architektur dar – denn darin fehlen noch die „Baumaterialien“.

Stay tuned

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

[mc4wp-simple-turnstile]

 

Für Software gehört dazu die Auswahl von Programmiersprachen, Frameworks, Middleware sowie der geeigneten technischen Infrastruktur für Test und Betrieb von Systemen. Das nennen wir querschnittliche Konzepte und Technologieentscheidungen. Für unser Gebäude aus Abbildung 1 müssten wir entsprechend festlegen, ob wir mit Holz, Beton oder Ziegelstein bauen, wie wir
Wasser-, Strom- und Netzwerkleitungen verlegen und so weiter.

Fassen wir zusammen: Architektur (ob Gebäude oder IT-Systeme) definiert einerseits die Struktur („Kästchen und Pfeile“) von Systemen, andererseits grundlegende, querschnittliche Themen wie Implementierungs- und Infrastrukturtechnologien. Unter [2] führe ich die Analogie zwischen Gebäude- und Softwarearchitektur etwas weiter aus. Ein kleines Beispiel finden Sie im Kasten „Strukturen und Konzepte“. Für den Überblick an dieser Stelle soll uns das genügen.

Jetzt stellt sich die Frage, wie wir zu diesen Entscheidungen kommen beziehungsweise welche Aufgaben noch rund um diese strukturellen und querschnittlichen Entscheidungen zu erledigen sind.

Strukturen und Konzepte

Am Beispiel eines fiktiven Onlineshops möchte ich die Bedeutung und Unterschiede von Strukturen („Bausteine des Systems“) und Konzepten („Lösungsansätze, Technologien“) erläutern. Wir strukturieren unseren fiktiven (und unvollständigen) Onlineshop etwa nach Domain-Driven Design oder anderen Strukturansätzen und erhalten dabei die Bausteine, Module (oder in DDD-Sprechweise, Bounded Contexts) aus Abbildung 2.

Abb. 2: Bausteine des Onlineshops

Diese Struktur enthält allerdings noch keinerlei Informationen über die gewählte Implementierungs- oder Deploymenttechnologie oder die verwendeten Frameworks. Genau das wären unsere Konzepte – ein paar Beispiele (ebenfalls fiktiv) in der folgenden Aufzählung:

  • Java/Kotlin mit Spring Boot als Backend-Technologie
  • Verwendung von Angular für grafische Frontends
  • Einige Bausteine werden als Self-Contained Systems (aka Microservices) eigenständig deployt und betrieben
  • Apache Kafka als Messaging-System zur (zeitlichen) Entkopplung der Self-Contained Systems
  • Verwendung von Jasper-Reports für sämtliche Reporting-Aufgaben
  • Verwendung von PostgreSQL als Datenspeicher
  • Caching lokal benötigter Daten über SQLite

    Sie sehen, diese Lösungskonzepte beziehen sich teilweise (z. B. Jasper-Reports, PostgreSQL, Spring Boot) auf mehrere der Bausteine, daher rührt die Bezeichnung „querschnittliche Konzepte“. Solche Konzepte können Sie in der Architektur oftmals (fast) unabhängig von der Struktur Ihrer Bausteine oder Komponenten festlegen.

    Wie geht Softwarearchitektur?

    Bevor wir in der Architektur mit diesen Entscheidungen loslegen, sollten wir die grundlegenden Anforderungen an das System verstanden haben: Was soll das System leisten, welche Aufgaben oder Prozesse soll es unterstützen (aka funktionale Anforderungen)? Dazu kommt das schwierige Thema der Qualitätsanforderungen, wie Performanz, Durchsatz, Sicherheit, Änderbarkeit und so weiter (mehr dazu in [3]). Schließlich müssen Sie auch die Rand- oder Rahmenbedingungen kennen (constraints), die die Entscheidungsmöglichkeiten von Architekt:innen einschränken.

    DIE KUNST DER SOTWARE-ARCHITEKTUR

    Architecture & Design-Track entdecken

     

    Es geht hier nicht um alle Anforderungen – denn dann wären wir ja bei einem Waterfallish-upfront-Ansatz – sondern um die aktuell bekannten und relevanten Themen einer Iteration. Primär sollten Sie sich um architekturrelevante Anforderungen kümmern. Etwa solche, die von besonders wichtigen Stakeholdern stammen (z. B. oberes Management, Auftraggeber etc.), besonders kritisch bzw. riskant sind, besonders kritische Qualitätseigenschaften betreffen oder einen ausgeprägt innovativen Charakter besitzen.

    Sollten diese Anforderungen zu schwammig, unklar und widersprüchlich sein oder gar komplett fehlen, müssen Sie in der Architektur handeln statt zu jammern, also gemeinsam mit Stakeholdern nachbessern oder zumindest über die für Requirements verantwortlichen Personen nachfordern. Daher sehen Sie in Abbildung 3 auch verschiedene solcher Stakeholder symbolisch mit der Aufgabe „Anforderungen klären“ verbunden.

    Abb. 3: Aufgaben in der Softwarearchitektur

    Den Kern der Architekturaufgaben bildet das Duo „entwerfen“ – das sehen Sie in Abbildung 4 nochmals hervorgehoben:

    • Durch „Strukturen entwerfen“ legt die Architektur die Zerlegung (auch: Schnitt) Ihres Systems fest. Sie bestimmt dabei die Bestandteile (Komponenten, Module, Services, Pakete oder wie auch immer in Ihrer gewählten Technologie die einzelnen Bestandteile eines Gesamtsystems heißen). Ganz wesentlich hierbei sind die Schnittstellen zwischen den einzelnen Bestandteilen sowie zur Umwelt.
    • Durch „Konzepte entwerfen“ legt die Architektur beispielsweise die genutzten Technologien und Frameworks fest. Sie bestimmt die Art und Weise, wie die Technologien eingesetzt werden, und gibt Patterns (Muster) und Regeln für architekturrelevante Themen vor.

    Stay tuned

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

    [mc4wp-simple-turnstile]

     

    Sie finden in Abbildung 4 einen überschneidenden Bereich, den ich anhand einiger Beispiele erklären möchte: Manche Entscheidungen betreffen sowohl Strukturen als auch querschnittliche Konzepte, beispielsweise:

    • Die (querschnittliche) Entscheidung, sämtliche externen REST-Schnittstellen durch einen Penetration-Test auf Sicherheitsrisiken zu prüfen. Externe Schnittstellen gehören zu den Bausteinen, den Strukturelementen des Systems, Penetration-Tests stellen ein methodisches (querschnittliches) Konzept dar.
    • Die (querschnittliche) Entscheidung, gemäß Domain-Driven Design zu arbeiten und dabei jeden fachlichen Baustein (bounded context) gemäß dem Clean-Architecture-Muster zu implementieren.
    • Die (querschnittliche) Entscheidung, Apache Kafka als Produkt für Messaging zu verwenden, hat Konsequenzen für alle betroffenen Bausteine (Strukturelemente).

    Abb. 4: Entwurfsaufgaben: Strukturen und Konzepte

    Wir arbeiten in der Architektur mit den sogenannten Stakeholdern zusammen. Neben dem Entwicklungsteam gehören dazu Fachbereiche, Auftraggebende, eventuell Behörden und Normungsgremien, Test- und QS-Abteilungen, Management, Product Owner, Nachbarprojekte und so weiter. Deswegen zählt kommunizieren zu den wesentlichen Aufgaben – mündlich wie auch schriftlich (dann nennen wir es dokumentieren). Im gleichnamigen Textkasten erkläre ich, was arc42 mit dieser Aufgabe zu tun hat.

    arc42

    Sie hören in Ihrer Firma immer wieder von arc42 [4], können es aber nicht einordnen? Hier eine kurze Fassung: arc42 ist ein Open-Source-Rahmenwerk zur Kommunikation (sprich: Erklärung und/oder Dokumentation) von Software- und IT-Architekturen. Sie können damit alle für Architektur und Entwicklung relevanten Aspekte Ihres Systems in einer einheitlichen Form beschreiben. Vergleichen Sie arc42 mit einem Schrank, bei dem jedes Fach bestimmte Dinge („Informationen“) über die Architektur enthält. Die Tabelle gibt eine kurze Übersicht der wesentlichen Elemente von arc42, und wie sie mit den anderen Themen dieses Artikels zusammenhängen. In [6] finden Sie viele Beispiele für die einzelnen Sektionen aus konkreten Systemen.

    Sektion Name Bedeutung
    1 Aufgabenstellung Eine Kurzfassung der wesentlichen Aufgaben („funktionale Anforderungen“) des Systems, der wichtigsten drei bis fünf Qualitätsanforderungen sowie eine Übersicht der beteiligten Stakeholder
    2 Randbedingungen Welche technischen oder organisatorischen Einschränkungen gibt es?
    3 Kontextabgrenzung Übersicht der externen Schnittstellen. Einbettung des Systems in dessen (fachliches + technisches) Umfeld
    4 Lösungsstrategie Wesentliche Elemente oder Entscheidungen der Lösung, etwa: zentrale Technologien
    5 Bausteinsicht Statische Struktur des Systems, Subsysteme, Komponenten, Module oder (Micro-)Services. Zeigt den Aufbau des Quellcodes aus einer Vogelperspektive. Wichtig: zeigt auch (interne) Schnittstellen
    6 Laufzeitsicht Wie bearbeiten die Bausteine (siehe Teil 5) wesentliche Abläufe im System?
    7 Verteilungssicht Technische Infrastruktur (Hardware, Netzwerke) und wie die Software darauf verteilt ist (Deployment)
    8 Querschnittliche Konzepte Welche Technologien werden wie eingesetzt? Beispiele: Wie speichert das System Daten, wie findet User-Interaktion statt, wie wird das System getestet, welche wesentlichen Patterns finden Anwendung?
    9 Architekturentscheidungen Alles, was an Entscheidungen sonst nirgendwo Platz findet. Viele Teams bringen hier ihre ADRs unter
    10 Qualitätsanforderungen Die Qualitätsanforderungen, die es nicht in die „Hitparade“ in Teil 1 geschafft haben
    11 Risiken und offene Punkte Technische Schulden, bekannte Probleme oder Risiken
    12 Glossar Erklärt die wichtigsten Fachbegriffe, die speziell und wichtig für dieses System sind. Bitte nicht REST oder HTTP erklären, das steht schon bei Wikipedia

    Jetzt bleibt noch die Aufgabe „Umsetzung begleiten“: Es besteht beim Arbeiten im Team immer das Risiko, dass Menschen sich missverstehen. Das ist ein menschliches Grundproblem, daher können Sie das nicht grundlegend ändern. Wenn Sie dem Team etwas erklären, könnten manche Personen diese Worte und Bilder anders interpretieren, als Sie das gemeint haben.

    Solche Missverständnisse haben wir alle in der Realität schon erlebt. Sie sollten in Ihrer Architekturarbeit aktiv etwas gegen diese Missverständnisse unternehmen: Begleiten Sie die Umsetzung! Prüfen Sie beispielsweise, ob der implementierte Code so beschaffen ist, wie Sie das in der Architektur vorgesehen haben. Code-Reviews, Pull/Merge Requests oder statische Codeanalyse sind nur einige der methodischen Mittel, die Sie hierfür einsetzen können. Design-Reviews, Pair oder Mob Programming, Coding Styleguides, Referenzimplementierungen, Checklisten und noch viele andere.

    Auf eine solche konstruktive Weise die Umsetzung zu begleiten, hat allerdings noch einen weiteren positiven Effekt: Ihre Teamkolleg:innen werden an manchen Stellen schlichtweg auf bessere Ideen kommen als die ursprünglichen Architekturentscheidungen. Solche strukturellen oder technischen Verbesserungen, Vereinfachungen, geschickteren Ansätze oder Ähnliches bezeichne ich als „Goldstücke, und die sollten Einzug in die Architektur halten. Insbesondere weil Sie in Ihrer Rolle als Architekt:in eben nicht alles wissen (können). Dazu kommen wir gleich, wenn wir klären, welche Person(en) überhaupt diese Architekturaufgaben erledigen können.

    So viel zu den sechs Kernaufgaben der Softwarearchitektur.

    Wer macht Softwarearchitektur?

    Welche Optionen gibt es denn? Einerseits könnten wir monarchisch diktieren, also die Entscheidungsgewalt (im wahrsten Sinne des Wortes) auf eine einzelne Person maximal zentralisieren. Andererseits könnten wir die Architekturaufgaben einfach an das gesamte Entwicklungsteam delegieren – und komplett dezentralisieren. Dazwischen gibt es eine Vielzahl möglicher Varianten, von denen Sie in Abbildung 5 einige Vertreter finden (nach [5] und [6])

    Zur Vereinfachung beziehen sich die hier skizzierten Situationen auf Teams überschaubarer Größe, circa acht bis zwölf Personen. Für größere Teams oder Gruppen aus mehreren Teams müssen zusätzliche oder andere Regeln gelten, auf die wir in dieser Übersicht nicht eingehen.

    Abb. 5: Rolle und Personen: zentrale bis dezentrale Architekturarbeit

    In [7] erkläre ich Vor- und Nachteile dieser fünf Modelle, daher hier nur in Kurzform: Alle diese Arbeitsweisen haben sinnvolle Anwendungsbereiche. In Off- oder Nearshore-Situationen kann eine zentralisiert-monarchische Organisation sinnvoll sein, auch wenn sie für viele Entwicklungsteams eher nach Anti-Pattern aussieht. Eine einzelne Person trifft sicher konsistente Entscheidungen, ihr mangelt es aber möglicherweise an „Schwarmintelligenz“ und ehrlichem Feedback. Andererseits kann eine rein demokratische (dezentrale) Teamarchitektur zu beliebig viel Chaos führen, obwohl sie auf den ersten Blick für viele Teams attraktiv erscheint.

    Wie so oft in der IT gilt hier die „Kommt drauf an“-Regel: Jedes Team muss situativ die passende Arbeitsweise finden und für sich selbst die Frage beantworten: „Wie sollten wir Architekturentscheidungen treffen?“. Meine Vermutung (aus einigen Jahren Erfahrung): Das Modell der Agenten (also zwei bis drei Personen teilen sich die Architekturaufgaben) skaliert gut, liefert inhaltlich oftmals hervorragende Ergebnisse und trifft diese Entscheidungen recht schnell, d. h. eignet sich auch für zeitkritische Projekte.

    Weder SOLID noch Clean Code sind Architektur

    Die Einhaltung von Programmierregeln (wie Clean Code oder die SOLID-Prinzipien) allein machen keine solide Architekturarbeit aus. Sie können mit Clean Code unglaublich inperformanten Code schreiben oder gravierende Sicherheitslücken produzieren. Verständlich geschriebener Code gehört zu den wünschenswerten Eigenschaften von IT-Systemen, aber wenn höchste Performance gefragt ist, stehen Aspekte der Lesbarkeit und Verständlichkeit hinten an! Diese Regeln lassen die Gesamtstruktur von IT-Systemen komplett außer Acht. Sie sagen nichts über Deployment, technische Infrastruktur oder die systematische Anwendung übergreifender Konzepte. Insofern besitzen sie für Architektur wenig (!) Bedeutung, und ihre Anwendung stellt keineswegs gute oder solide Architekturarbeit sicher!

    Nehmen Sie sich etwas Zeit und genießen Sie Golo Rodens ausführliche Ausführungen zu diesen Themen [8].

    Fazit

    Jedes System besitzt interne Strukturen („Bausteine und deren Abhängigkeiten“) und verwendet bestimmte Technologien auf eine jeweils bestimmte Art und Weise („Querschnittliche Konzepte“). Die Entscheidungen über diese beiden Themen (Strukturen und Konzepte) können Teams gezielt treffen (sprich: die Architektur aktiv gestalten) oder dem Zufall überlassen (was langfristig viele Probleme verursachen wird).

    Mit aktiver Gestaltung steigt die Wahrscheinlichkeit, die notwendigen Anforderungen und insbesondere Qualitäten zu erreichen. Insofern haben Sie keine wirkliche Wahl – Architekturarbeit muss sein!

    Ob Sie diese Gestaltungsarbeit einer einzelnen Person überlassen, sie auf mehrere Schultern verteilen oder im Team abstimmen, sollten Sie situativ entscheiden. In jedem Fall wünsche ich Ihnen für Ihre Architekturarbeit viel Erfolg.

    Stay tuned

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

    [mc4wp-simple-turnstile]

     


    Links & Literatur

    [1] Amsterdam City Archive, Grundriss, Foto von unsplash: https://unsplash.com/de/fotos/wD9uk9fNcQU

    [2]: Starke, Gernot: „Grundlagen der Softwarearchitektur – Teil 1“. INNOQ-Blog: https://www.innoq.com/de/articles/2023/07/architektur-teil-1/

    [3] Q42 – das arc42 Qualitätsmodell: https://quality.arc42.org. Definiert Qualitätseigenschaften und zeigt viele konkrete Beispiele, wie entsprechende Anforderungen formuliert werden können („Qualitätsszenarien“).

    [4] arc42: https://arc42.org. Dokumentation mit vielen Beispielen unter https://docs.arc42.org. Kompaktfassung als „one pager“ unter https://canvas.arc42.org

    [5] Toth, Stefan: „Vorgehensmuster für Softwarearchitektur“, Carl Hanser Verlag, 2019

    [6] Hohpe, Gregor: „Organizing Architecture“: https://architectelevator.com/architecture/organizing-architecture/

    [7] Starke, Gernot: „Grundlagen der Softwarearchitektur, Teil 4: Wer macht das?“: https://www.innoq.com/de/articles/2023/10/grundlagen-der-softwarearchitektur-teil-4

    [8] Roden, Golo: „Architektur ist überbewertet“: https://www.youtube.com/watch?v=C7TMa_kYANA und als Artikel unter https://www.heise.de/blog/Architektur-ist-ueberbewertet-und-was-wir-daraus-lernen-koennen-10191624.html

    The post Moderne Softwarearchitektur verstehen appeared first on JAX.

    ]]>
    Architektur ist nicht Kubernetes: Diana Montalions Vision für Systemarchitektur auf der W-JAX https://jax.de/blog/moderne-systemarchitektur-systems-thinking-w-jax/ Tue, 19 Nov 2024 15:04:34 +0000 https://jax.de/?p=106834 In ihrer mitreißenden Keynote auf der W-JAX in München stellt Diana Montalion eine neue Sichtweise auf moderne Systemarchitektur vor. Sie zeigt, dass echte Innovation weit über Tools wie Kubernetes hinausgeht. Stattdessen rückt sie Systems Thinking und die kunstvolle, flexible Gestaltung von Beziehungen zwischen Systemkomponenten in den Fokus. Mit ihrer Vision macht sie deutlich, dass moderne Software-Architektur mehr ist als Technik – sie ist eine soziotechnische Disziplin, die technisches Know-how und menschliche Zusammenarbeit nahtlos miteinander vereint.

    The post Architektur ist nicht Kubernetes: Diana Montalions Vision für Systemarchitektur auf der W-JAX appeared first on JAX.

    ]]>

    Architektur neu denken!

    Eines der kontroversesten Wörter in der heutigen Technologiekultur ist „Architekt“. Was bedeutet Architektur wirklich? Diese Frage sorgt immer wieder für hitzige Diskussionen und Missverständnisse. In ihrer Keynote auf der W-JAX in München wirft die renommierte Expertin Diana Montalion einen frischen Blick auf das Thema. Sie verdeutlicht, dass Architektur weit mehr ist als die Implementierung von Tools wie Kubernetes. Stattdessen geht es um das Entwerfen von Beziehungen zwischen Systemkomponenten und die Fähigkeit, diese Muster flexibel an wechselnde Bedingungen anzupassen. Dies erfordert fundierte mentale Modelle und kollaboratives Arbeiten, um diese weiterzuentwickeln.

    Diana Montalion, Autorin des O’Reilly-Buchs Learning Systems Thinking: Essential Nonlinear Skills & Practices for Software Professionals, blickt auf über 18 Jahre Erfahrung in der Software-Entwicklung und -Architektur zurück. Sie hat unter anderem für Organisationen wie Stanford, die Gates Foundation und The Economist gearbeitet und war Principal Systems Architect für die Wikimedia Foundation.

    In ihrer Keynote betont Diana, dass moderne Architektur eine soziotechnische Disziplin ist – eine Mischung aus sozialen und technischen Fähigkeiten. Sie beschreibt, wie gutes Architektendenken effektives Systemdenken strukturiert und erklärt die fünf wesentlichen Qualitäten, die einen guten Architekten von einem großartigen unterscheiden.

    Stay tuned

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

    [mc4wp-simple-turnstile]

     

    Die wichtigsten Take-aways

    • Architekturdenken: Systeme sollten nicht isoliert betrachtet werden. Es ist entscheidend, das gesamte Ökosystem zu berücksichtigen und zu verstehen, wie verschiedene Komponenten zusammenarbeiten, um echte Effizienz und Effektivität zu erreichen.
    • Silos aufbrechen: Abteilungsübergreifende Zusammenarbeit ist notwendig, um die Integration verschiedener Technologien und Teams zu verbessern. Teams müssen effektiv kommunizieren und kooperieren können.
    • Kultureller Wandel: Ein grundlegender Kulturwandel in Unternehmen ist erforderlich, um moderne Architekturen erfolgreich zu implementieren. Vertrauen und Autonomie der Teams sowie die Bereitschaft, neue Wege zu gehen, sind hierbei essenziell.
    • Einsatz geeigneter Werkzeuge: Technologien wie Kubernetes können hilfreich sein, dürfen jedoch nicht das zentrale Element des Architekturdenkens werden. Sinnvolle und ganzheitliche Designansätze, die weit über spezifische Tools hinausgehen, sind entscheidend.

    The post Architektur ist nicht Kubernetes: Diana Montalions Vision für Systemarchitektur auf der W-JAX appeared first on JAX.

    ]]>
    Verdächtige Gerüche https://jax.de/blog/verdaechtige-gerueche-durch-code-smells-zum-besseren-code/ Fri, 03 Jun 2022 11:52:41 +0000 https://jax.de/?p=86883 Will man bestehenden Code verbessern, ist es nicht immer leicht, zu entscheiden, wann man mit dem Refactoring [1] anfängt und wann man es beendet. Die Idee der „Code Smells“ hilft dabei. Der Begriff beschreibt Strukturen im Code, die überarbeitet werden sollten. In diesem Artikel betrachten wir eine Sammlung wichtiger Code Smells und führen an einem Beispiel Schritt für Schritt ein Refactoring durch, durch das wir eine übelriechende Codestruktur verbessern.

    The post Verdächtige Gerüche appeared first on JAX.

    ]]>
    Die Idee der Code Smells ist über zwanzig Jahre alt, aber immer noch aktuell. Der Begriff wurde von Kent Beck geprägt und erlangte durch das Buch „Refactoring“ [2] von Martin Fowler eine weite Verbreitung. Fowler und Beck haben 22 Smells identifiziert und für deren Beseitigung einen umfangreichen Refactoring-Katalog erstellt. Laut der Software Craftsmanship Community gehört dieses Buch zu den Klassikern, die jeder Entwickler gelesen haben sollte, denn es verändere die Art und Weise, wie wir Software entwickeln [3]. Auch in dem Standardwerk „Clean Code“ von Robert C. Martin sind Code Smells ein fester Bestandteil. „Uncle Bob“ listet in seinem Buch nicht nur die Smells von Fowler und Beck auf, sondern ergänzt sie um seine eigenen Erfahrungen.

    Fünf Kategorien

    Die klassischen 22 Smells von Fowler und Beck werden ohne eine Kategorisierung oder Beziehung zueinander vorgestellt. Da einige Code Smells eng miteinander verwandt sind und die gleichen Symptome zeigen, haben Lassenius und Mäntylä in einer wissenschaftlichen Ausarbeitung vorgeschlagen, eine Kategorisierung einzuführen [4]. Das Ergebnis waren folgende fünf Kategorien: Bloaters, Change Preventers, Dispensables, Couplers und Object Orientation Abuser. Diese Kategorisierung ist mittlerweile sehr verbreitet und Code Smells lassen sich damit sehr einfach erklären. Abbildung 1 zeigt die fünf Kategorien mit der Einordnung der Code Smells, die wir in diesem Artikel näher betrachten werden.

    Das Ziel ist es, eine Übersicht über die große Anzahl von Code Smells zu bekommen und erste Hinweise mitzunehmen, anhand welcher Kriterien wir sie jeweils wiedererkennen.

    Abb. 1: Fünf Kategorien von Code Smells

    Bloaters

    Die Kategorie Bloaters umfasst Smells, die den Code aufblähen. Es handelt sich dabei um Strukturen, durch die der Code sehr groß und unbeweglich wird. Solche Strukturen entstehen nicht zwingend von Anfang an, sondern über eine gewisse Zeit, gerade, wenn technische Schulden nicht abgebaut werden. Ab einer bestimmten Größe des Codes wird es für Entwickler sehr schwer, Erweiterungen oder Modifikationen daran durchzuführen. Für sehr umfangreiche Codestrukturen ohne Modularität ist es schwierig bis unmöglich, sie unter Test zu bringen. Die Smells dieser Kategorie lassen sich aufgrund ihrer großen Präsenz sehr einfach erkennen.

    Eine davon ist die „lange Methode“. Es ist deutlich einfacher, die Logik einer kurzen Methode als die einer langen im Kopf zu behalten. Eine Methode sollte keine 100 Zeilen und bestenfalls keine 20 Zeilen groß sein, hier waren sich Kent Beck und Robert C. Martin bereits vor zwanzig Jahren einig [5]. Ihre Empfehlung liegt bei ca. fünf Zeilen pro Methode. Fakt ist: Je kleiner die Methoden sind, desto einfacher sind sie zu verstehen und desto einfacher lassen sie sich erweitern. Dies hat sich in vielen Projekten bewährt. In der Praxis, gerade in Legacy-Systemen, kommen leider schon mal Methoden mit 1 000 Zeilen vor. Hierbei handelt es sich dann um einen sehr übelriechenden Code, der nach Verbesserung geradezu schreit.

    Durch lange Methoden entstehen indirekt auch lange Parameterlisten, da die benötigten Werte auf irgendeinem Weg in die Methode gelangen müssen. Keine wirkliche Alternative ist die Verwendung globaler Daten, denn auch hier muss der Entwickler die Herkunft der Daten interpretieren und verstehen. Je länger die Parameterliste, desto höher die Komplexität. Aus Testsicht sind lange Parameterlisten sehr schwierig, denn es muss jede mögliche Konstellation mit Tests abgedeckt werden. Hier wird empfohlen, ab drei Parametern darüber nachzudenken, ob man zum Beispiel ein Parameterobjekt einführt [5].

     

    Die „umfangreiche Klasse“, auch als „Gottobjekt“ bekannt, bezeichnet eine Klasse, die zu viel weiß oder zu viel tut. Da in so einem Konstrukt die Übersicht schnell verloren geht, schleichen sich auch gern Redundanzen ein. Ein bekanntes Prinzip, um diesem Code Smell entgegenzuwirken, ist das Single Responsibility Principle (SRP). Jede Klasse sollte nur eine Verantwortlichkeit haben.

    Bei „Datenklumpen“ handelt es sich um verschiedene Datenelemente, die häufig zusammen auftreten, in Feldern, aber auch in Parameterlisten. Das Konstrukt ist meistens durch gleiche Präfixe oder Suffixe erkennbar (Abb. 2). Ein sehr einfacher Test, um Datenklumpen zu identifizieren, ist es, ein Feld aus dem Klumpen zu löschen. Wenn die Felder nicht für sich alleine stehen können, dann handelt es sich um einen Datenklumpen. Dieser Code Smell würde auch in die Kategorie „Object Orientation Abuser“ passen, denn aus jedem Datenklumpen wird eine eigene Klasse extrahiert.

    Abb. 2: Datenklumpen extrahieren

    Change Preventers

    Code Smells dieser Kategorie verhindern bzw. verlangsamen jegliche Erweiterung von Software. Bei einer Änderung an einer Stelle ist zwingend auch eine Änderung an einer anderen Stelle notwendig. Damit erhöht sich der Aufwand von Anpassungen drastisch. Diese Kategorie enthält zwei Code Smells, die zwar eng miteinander verwandt, aber im Grunde das genaue Gegenteil voneinander sind.

    Abb. 3: Change Preventers

    Wenn eine Klasse oder Methode aus verschiedenen Gründen und auf verschiedene Art und Weise immer wieder geändert wird, dann handelt es sich dabei um den Code Smell „divergierende Änderungen“. Eine Klasse wird zum Beispiel an drei Stellen geändert, um eine Datenbankänderung durchzuführen. Die gleiche Klasse muss aber auch an vier ganz anderen Stellen geändert werden, um einen neuen Datentyp einzuführen. Bei diesem Code wird das Single Responsibility Principle (SRP) verletzt. Die Klasse enthält mehrere Zuständigkeiten und sollte aufgeteilt werden.

    Genau das Gegenteil von divergierenden Änderungen ist die sogenannte „Chirurgie mit der Schrotflinte“ (engl. Shotgun Surgery). Hierbei müssen bei jeder Änderung zwingend auch Anpassungen an weiteren Stellen durchgeführt werden. Die Modifikationen für die Änderung sind weit verstreut, dadurch kann es äußerst aufwendig sein, alle relevanten Stellen zu identifizieren. Um diese komplexe Struktur aufzulösen, sollte man zuallerst die schlecht voneinander getrennte Programmlogik bündeln und im Anschluss in eigene Strukturen extrahieren. Denn das oberste Ziel sollte sein, eine Änderung immer nur an einer einzigen Stelle durchführen zu müssen.

    Dispensables

    Die Kategorie „Dispensables“ repräsentiert entbehrlichen und sinnlosen Code. Hierzu gehören Code Smells wie „redundanter Code“, „träges Element“, „spekulative Generalisierung“ und „Kommentare“. Das Fehlen dieser Konstrukte würde den Code deutlich sauberer und leichter verständlich machen.

    Redundanter Code kann in verschiedenen Ausprägungen vorkommen. Wenn es sich bei der Redundanz um komplett identischen Code handelt, dann lässt sich dieser sehr einfach identifizieren und beseitigen. Mittlerweile kann auch die IDE sehr gut unterstützen, um identischen Code über das gesamte Projekt zu finden. Handelt es sich bei den Redundanzen jedoch um ähnlichen, aber nicht vollständig identischen Code, dann kann das Identifizieren und Beseitigen schon aufwendiger werden. Eines der wichtigsten Prinzipien für sauberen Code ist das DRY-Prinzip (Don’t Repeat Yourself). Die Kernaussage ist, redundanten Code zu vermeiden.

    Wenn eine Klasse zu wenig tut, sollte ihre Daseinsberechtigung hinterfragt werden. Es könnte sein, dass sie in der Vergangenheit mehr Verantwortung hatte und durch eine Überarbeitung zu einem „trägen Element“ (engl. lazy class) geworden ist. Hierbei muss man konsequent sein und das Ziel haben, unnötige Komplexität zu entfernen.

    Die „spekulative Generalisierung“ entsteht, wenn Entwickler mit der Prämisse „Früher oder später brauchen wir die Funktionalität“ programmieren. Es wird Funktionalität umgesetzt, die nicht verwendet wird. Dies erhöht die Komplexität des Gesamtsystems unnötig und sollte vermieden werden. Hier gelten die zwei Prinzipien: KISS – Keep it Simple, Stupid und YAGNI – You ain’t gonna need it.

    Zu Kommentaren haben Fowler und Beck eine klare Meinung: Es handele sich um Deodorant für Code Smells und sei damit selbst ein solcher [2]. Wenn Entwickler übelriechenden Code schreiben und ihnen das bewusst ist, sprühen sie etwas Deodorant drüber, indem ein Kommentar dazu verfasst wird. Daher sollten Kommentare immer kritisch betrachtet werden: Was will man damit verbergen?

    Stay tuned

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

    [mc4wp-simple-turnstile]

     

    Couplers

    Die Code Smells dieser Kategorie führen zu einer übermäßigen Kopplung zwischen Klassen. Für die leichte Wandelbarkeit einer Software strebt man jedoch eine möglichst lose Kopplung zwischen Bausteinen an sowie eine hohe Kohäsion (starke Bindung) innerhalb eines Bausteins [6]. Enge Kopplung führt dazu, dass Änderungen in einer Klasse automatisch auch Änderungen in einer anderen Klasse verursachen.

    Zu dieser Kategorie gehören Smells wie „Featureneid“, Insiderhandel, Mitteilungsketten und Vermittler. Die letzten beiden entstehen, wenn die enge Kopplung durch eine übermäßige Delegation ersetzt wird.

    Abb. 4: Featureneid und Insiderhandel

    Wenn eine Methode mehr an den Daten einer anderen Klasse interessiert ist als an den Daten der eigenen Klasse, dann sprechen wir von Featureneid (Abb. 4). Ein typisches Szenario ist beispielsweise, wenn eine Methode eine Berechnung durchführen will und dafür ein Dutzend Getter-Methoden einer anderen Klasse aufruft, um sich die Daten für die Berechnung zu holen. Bei so viel Neid sollte hinterfragt werden, ob die Methode in der richtigen Klasse ist.

    Beim Insiderhandel tuscheln zwei Klassen hinter dem Rücken der offiziellen Schnittstelle. Diese Kopplung ist nicht auf Anhieb ersichtlich und nur schwer zu entdecken, da die Absprachen der Klassen heimlich erfolgen. Bei der Vererbung ist dieses Konstrukt oft zu beobachten, wenn die Unterklasse mehr über die Basisklasse weiß als notwendig. Daraufhin verschafft sich die Unterklasse einen Vorteil, in dem sie mit den Daten der Basisklasse arbeitet. In diesem Fall sollte die Vererbungshierarchie hinterfragt werden.

    Abb. 5: Mitteilungsketten und Vermittler

    Eine Mitteilungskette (Abb. 5) entsteht, wenn ein Objekt A die Daten von Objekt D benötigt und dies nur durch unnötige Navigation durch die Objekte B und C erreicht. Objekt A fragt nach Objekt B, danach das Objekt C und anschließend Objekt D mit den relevanten Daten.

    A.getB().getC().getD().getTheNeededData()

    Jede Änderung an den Beziehungen der beteiligten Objekte zwingt den Aufrufer ebenfalls zu einer Änderung. Durch die Refactoring-Maßnahme „Delegation verbergen“ kann die Mitteilungskette verkürzt werden. Bei Delegation verbergen muss der Aufrufer (Objekt A) nicht zwingend alle Objekte kennen, wenn zum Beispiel Objekt B die Verbindung zu Objekt C und D verbirgt.

    A.getB().getTheNeededDataThroughCandD()

    Somit muss bei Änderungen nicht immer der Aufrufer (Objekt A) geändert werden. Durch solch eine Maßnahme könnte aber auch der Code Smell „Vermittler“ entstehen. Wenn eine Klasse hauptsächlich aus einfachen Delegationen besteht und sonst keinen eigenen Mehrwert bietet, dann sprechen wir von einem Vermittler. Der Vermittler ist an der Stelle eine unnötige Komplexität, wir sollten versuchen, darauf zu verzichten.

    Object-oriented Abuser

    Diese Kategorie enthält übelriechenden Code, bei dem die objektorientierten Prinzipien falsch oder unvollständig umgesetzt wurden. Vererbung und Polymorphie sind die grundlegenden Konzepte der Objektorientierung. Wenn diese Konzepte falsch angewendet werden, so leiden darunter hauptsächlich die Wartbarkeit und Wiederverwendbarkeit. Object-oriented Abuser enthält Code Smells wie wiederholte Switch-Anweisung, alternative Klassen mit unterschiedlichen Schnittstellen und temporäres Feld.

    Wenn sich Switch-Anweisungen mit identischer Steuerungslogik wiederholen, dann ist dies ein Anzeichen für das Fehlen von objektorientiertem Design. Die gleiche Fallunterscheidung anhand eines Typs ist an verschiedenen Stellen im Code verteilt. Möchte man einen weiteren Typ für die Fallunterscheidung hinzufügen, so muss man zwingend alle Switch-Anweisungen finden und dort die Erweiterung durchführen. Das ist sehr mühsam, unübersichtlich und fehleranfällig, doch das Problem lässt sich sehr elegant durch Polymorphie lösen. Wir schauen uns im nächsten Abschnitt diesen Code Smell anhand eines Beispiels etwas genauer an.

    Beim ausgeschlagenen Erbe (engl. Refused Bequest) übernimmt eine Unterklasse nicht alle Daten oder Funktionen der Oberklasse, sondern unterstützt nur bestimmte Aspekte. Dazu kann es kommen, wenn die Vererbung verwendet wird, um bestimmte Aspekte der Oberklasse wiederzuverwenden, ohne dass die zwei Objekte etwas miteinander zu tun haben. Bei diesem Code Smell sollte die Vererbungshierarchie hinterfragt werden.

    MEHR PERFORMANCE GEFÄLLIG?

    Performance & Security-Track entdecken

     

     

    Klassen untereinander austauschen zu können, ist ein großer Vorteil der objektorientierten Programmierung. Dies wird durch die Verwendung von Schnittstellen erreicht. Um Klassen untereinander auszutauschen, sollte man aber auch die gleichen Schnittstellen verwenden. Der Code Smell „alternative Klassen mit unterschiedlichen Schnittstellen“ kann entstehen, wenn beim Erstellen einer Klasse eine äquivalente Klasse übersehen wird. Dann wird eine ähnliche Klasse mit einer anderen Schnittstelle erzeugt. Die Schnittstellen müssen in so einem Fall vereinheitlicht werden.

    Ein nur temporär genutztes Feld in einer Klasse kann zu großer Verwirrung führen. Der Entwickler muss die Fragen „Wo wird es verwendet?“ und „Warum wird es nur da verwendet?“ klären. Das erhöht unnötig die Komplexität, denn die Erwartungshaltung ist, dass ein Objekt alle seine Felder benötigt und benutzt und das nicht nur unter bestimmten Konstellationen. Ein temporäres Feld sollte am besten in eine eigene Klasse extrahiert werden und sämtlicher Code, der mit dem Feld arbeitet, sollte auch umgezogen werden.

    Der Weg zu besserem Code

    Das Erkennen von übelriechendem Code ist erst die halbe Miete. Uns ist dadurch zwar bewusst, welche Konstrukte überarbeitet werden sollten, doch nun stellt sich die Frage, wie wir übelriechenden Code beseitigen, ohne die Funktionalität der Software zu gefährden.

    Ein Refactoring sollte immer in kleinen Schritten durchgeführt werden. Ein großes Big Bang Refactoring geht selten gut. Bei der Überarbeitung ist eine entsprechende Testabdeckung sehr wichtig. Dies kann jedoch eine Herausforderung sein, speziell im Umfeld von Legacy Code. Aber auch für Legacy-Systeme gibt es Ansätze, um die Testabdeckung aufzubauen, z. B. kann man mit der umgedrehten Testpyramide beginnen und diese mit der Zeit drehen.

    Fowler und Beck haben für jeden identifizierten Code Smell eine Anleitung verfasst, welche Schritte notwendig sind, um ihn aufzulösen. Wir schauen uns weiter unten in Listing 1 exemplarisch so ein Vorgehen an. Da die Ausprägungen von Code Smells sehr unterschiedlich sein können, ist nicht immer eine Anleitung notwendig, gerade wenn es sich um einfache Smells handelt. Erfahrungsgemäß lassen sich diese bereits durch folgende toolgestützte Refactoring-Maßnahmen beseitigen:

    • Methode/Variable verschieben
    • Methode/Variable umbenennen
    • Methode/Variable extrahieren
    • Methode/Variable inline platzieren

    Diese Maßnahmen werden von der Entwicklungsumgebung (IDE) unterstützt. Es lohnt sich, diese Unterstützung der IDE im Detail zu kennen und zu verwenden, denn das gibt dem Entwickler eine zusätzliche Sicherheit beim Refactoring.

    Anhand des Beispiels in Listing 1 schauen wir uns den Code Smell „wiederholte Switch-Anweisungen“ etwas genauer an. Wir analysieren zum einen die übelriechenden Stellen im Code und schauen, wie wir ihn mit Hilfe der Anleitung aus dem Refactoring-Katalog [2] verbessern können. Bei dem Beispiel handelt es sich um das Parrot-Refactoring Kata [7], das in verschiedenen Programmiersprachen existiert.

    Die Klasse Parrot (engl. für Papagei) enthält zwei Methoden mit den redundanten Switch-Anweisungen. Die Methode getSpeed bestimmt die Geschwindigkeit des Papageis und die Methode getPlumage den Zustand des Federkleides. Es gibt die drei Papageientypen „European“, „African“ und „Norwegian Blue“, die unterschiedliche Ausprägungen für Geschwindigkeit und Gefieder haben. Würde ein neuer Papageientyp hinzukommen, so müsste man alle Fallunterscheidungen anpassen. Dies widerspricht dem Open Closed Principle (OCP) und lässt sich mit Polymorphie sehr gut lösen.

    public class Parrot {
    // ...
    double getSpeed(ParrotTypeEnum type) {
      switch (type) {
        case EUROPEAN:
          return getBaseSpeed();
        case AFRICAN:
          return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
        case NORWEGIAN_BLUE:
          return (isNailed) ? 0 : getBaseSpeed(voltage);
      }
      throw new RuntimeException("Should be unreachable");
    }
     
    String getPlumage(ParrotTypeEnum type) {
      switch (type) {
        case EUROPEAN:
          return "average";
        case AFRICAN:
          return numberOfCoconuts > 2 ? "tired" : "average";
        case NORWEGIAN_BLUE:
          return voltage > 100 ? "scorched" : "beautiful";
      }
      throw new RuntimeException("Should be unreachable");
    }
    // ...
    }
    

    Bedingung durch Polymorphie ersetzen

    Im Folgenden schauen wir uns in Kürze die Anleitung aus dem Refactoring-Katalog von Fowler und Beck an. Es handelt sich dabei um ein bewährtes Vorgehen zum Auflösen unseres Code Smells „wiederholte Switch-Anweisungen“.

    1. Klassen für die Polymorphie erstellen
    2. Fabrikfunktionen zur Rückgabe der richtigen Instanzen erstellen
    3. Im aufrufenden Code die Fabrikmethoden verwenden
    4. Methoden in der Unterklasse überschreiben
    5. Verschieben der bedingten Funktionen aus der Basisklasse in die Unterklassen

    Das ganze Refactoring ist auf sehr kleine Schritte aufgeteilt. Nach jedem Schritt wird geprüft, ob der Code kompilierbar ist und die automatisierten Tests noch funktionieren.

    Im ersten Schritt werden die Klassen für die Polymorphie erstellt. Das Ergebnis sind die Klassen EuropeanParrot, AfricanParrot und NorwegianParrot, die von der Oberklasse Parrot erben. Im zweiten Schritt werden die entsprechenden Fabrikfunktionen erstellt und im nächsten Schritt in die aufrufenden Stellen eingebunden. Die ersten drei Schritte haben nur die neue objektorientierte Klassenstruktur erzeugt. Die gesamte Logik ist weiterhin in der Oberklasse Parrot und wird von den Unterklassen verwendet. Dies hat den großen Vorteil, dass eine neue Klassenstruktur in bestehenden Code eingeführt werden kann, ohne die Funktionalität zu beeinträchtigen.

    Im vierten Schritt überschreiben die Unterklassen die Methoden getSpeed und getPlumage und rufen vorerst über super (Listing 2) die Methoden der Oberklasse auf, in der noch immer die Switch-Anweisungen ausgeführt werden.

    public class AfricanParrot extends Parrot {
    // ...
    @Override
    public double getSpeed() {
      return super.getSpeed();
    }
    @Override
    public double getPlumage() {
      return super.getPlumage();
    }
    }
    

    Erst im fünften Schritt wird die Logik aus den Switch-Anweisungen in die entsprechenden Unterklassen verschoben, und das Refactoring ist fast vollbracht. Zum Schluss haben die Methoden getSpeed und getPlumage in der Parrot-Klasse keine Funktionalität mehr und können damit abstract werden. Das Ergebnis aller Schritte inklusive der Bereinigung zum Schluss zeigt Listing 3.

    public abstract class Parrot {
     
      public static Parrot createEuropeanParrot() {
        return new EuropeanParrot();
      }
      public static Parrot createAfricanParrot( int numberOfCoconuts) {
        return new AfricanParrot(numberOfCoconuts);
      }
     
      public static Parrot createNorwegianBlueParrot(double voltage, 
    boolean isNailed) {
        return new NorwegianBlueParrot(voltage, isNailed);
      }
     
      public abstract double getSpeed();
      public abstract double getPlumage();
      // ...
    }
     
    public class AfricanParrot extends Parrot {
      // ...
      @Override
      public double getSpeed() {
        return Math.max(0, getBaseSpeed() - getLoadFactor() * numberOfCoconuts);
      }
     
      @Override
      public String getPlumage() {
        return numberOfCoconuts > 2 ? "tired" : "average";
      }
      // ...
    }
     
    public class EuropeanParrot extends Parrot{
      // ...
      @Override
      public double getSpeed() {
        return getBaseSpeed();
      }
     
      @Override
      public String getPlumage() {
        return "average";
      }
      // ...
    }
     
    public class NorwegianBlueParrot extends Parrot{
      // ...
      @Override
      public double getSpeed() {
        return (isNailed) ? 0 : getBaseSpeed(voltage);
      }
     
      @Override
      public String getPlumage() {
        return voltage > 100 ? "scorched" : "beautiful";
      }
      // ...
    }
    

    Dieses Vorgehen zeigt uns, wie iterativ eine komplexe Strukturänderung im Code erfolgen kann. Auch wenn die Schritte teilweise zu kleinteilig wirken, sind sie in der Praxis, wenn der Code deutlich umfangreicher ist, genau richtig. Nur durch solche kleinen Schritte und die ständige Ausführung der Tests kann ein komplexes Refactoring ohne Seiteneffekte erfolgreich durchgeführt werden.

    Fazit

    In diesem Artikel haben wir anhand von fünf Kategorien 18 Code Smells betrachtet. Um die schlechten Gerüche im Code wieder loszuwerden, sollte als Erstes die Testabdeckung sichergestellt werden. Weitere Sicherheit bieten toolgestützte Refactorings, mit denen bereits gute Ergebnisse erzielt werden. Bei komplexen Code Smells lohnt es sich, auf bewährtes Vorgehen zurückzugreifen. Ein Beispiel hierfür haben wir uns im Detail angeschaut.

    Mit der Metapher Code Smells steht uns seit mehr als zwanzig Jahren ein Werkzeug zur Verfügung, mit dem wir schlechten Code identifizieren und bereinigen können. Wir sollten von diesen Erfahrungen profitieren und nicht alle Fehler selbst machen. Sich mit den Code Smells auseinanderzusetzen, hat zwei wesentliche Vorteile. Zum einen erkennen wir leichter die Strukturen, die überarbeitet werden sollten, und zum anderen entwickeln wir ein besseres Bewusstsein dafür, welche Fehler wir vermeiden sollten, wenn wir neuen Code schreiben. Beide Aspekte führen zu dem Ergebnis, dass die Qualität der Software besser wird.

    Stay tuned

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

    [mc4wp-simple-turnstile]

     

     

    Links & Literatur

    [1] https://de.wikipedia.org/wiki/Refactoring

    [2] Fowler, Martin: „Refactoring: Improving the Design of Existing Code“; Addison-Wesley, 2018

    [3] Mancuso, Sandro: „The Software Craftsman: Professionalism, Pragmatism, Pride“, Pearson Education, 2014

    [4] Mäntylä, M. V. and Lassenius, C.: „Subjective Evaluation of Software Evolvability Using Code Smells: An Empirical Study“, Journal of Empirical Software Engineering, vol. 11, no. 3, 2006, pp. 395–431.

    [5] Martin, Robert C.: „Clean Code: A Handbook of Agile Software Craftsmanship“, Prentice Hall, 2008

    [6] Starke, Gernot: „Effektive Softwarearchitekturen: Ein praktischer Leitfaden“, Hanser Fachbuch, 2002

    [7] https://github.com/emilybache/Parrot-Refactoring-Kata.git

    The post Verdächtige Gerüche appeared first on JAX.

    ]]>
    Modularisierung und kognitive Psychologie https://jax.de/blog/modularisierung-und-kognitive-psychologie/ Mon, 06 Dec 2021 09:56:02 +0000 https://jax.de/?p=85348 Über Modularisierung wird viel und häufig gesprochen, aber die Gesprächspartner:innen stellen nach einiger Zeit fest, dass sie nicht dasselbe meinen. Die Informatik hat uns in den letzten fünfzig Jahren zwar eine Reihe guter Erklärungen geliefert, was Modularisierung ausmacht – aber reicht das, um wirklich zu den gleichen Schlüssen und Argumenten zu kommen?

    The post Modularisierung und kognitive Psychologie appeared first on JAX.

    ]]>
    Die wirkliche Begründung, warum Modularisierung so wichtig ist, habe ich erst bei der Beschäftigung mit der kognitiven Psychologie gefunden. In diesem Artikel werde ich deshalb Modularisierung und kognitive Psychologie zusammenführen und Ihnen die entscheidenden Argumente an die Hand geben, warum Modularisierung uns bei der Softwareentwicklung tatsächlich hilft.

    Parnas hat immer noch Recht!

    In den letzten 20 bis 30 Jahren haben wir viele sehr große Softwaresysteme in Java, C++ und auch in C# und PHP entwickelt. Diese Systeme enthalten sehr viel Business Value und frustrieren Ihre Entwicklungsteams, weil sie nur noch mit immer mehr Aufwand weiterentwickelt werden können. Das inzwischen 50 Jahre alte Rezept von David Parnas, um einen Ausweg aus dieser Situation zu finden, heißt Modularisierung. Haben wir eine modulare Architektur, so heißt es, dann haben wir unabhängige Einheiten, die von kleinen Teams verstanden und zügig weiterentwickelt werden können. Zusätzlich bietet eine modulare Architektur die Möglichkeit, die einzelnen Module getrennt zu deployen, sodass unsere Architektur skalierbar wird. Genau diese Argumente tauschen wir in Diskussionen unter Architekt:innen und Entwickler:innen aus und sind uns doch immer wieder nicht einig, was wir genau mit Modularität, Modulen, modularen Architekturen und Modularisierung meinen.

    In meiner Doktorarbeit habe ich mich mit der Frage beschäftigt, wie man Softwaresysteme strukturieren muss, damit Menschen bzw. unser menschliches Gehirn sich darin gut zurechtfinden. Das ist besonders deswegen wichtig, weil Entwicklungsteams einen Großteil ihrer Zeit mit dem Lesen und Verstehen von vorhandenem Code verbringen. Erfreulicherweise hat die kognitive Psychologie mehrere Mechanismen identifiziert, mit dem unser Gehirn komplexe Strukturen erfasst. Einer von ihnen liefert eine perfekte Erklärung für Modularisierung: Er heißt Chunking. Auf der Basis von Chunking können wir Modularisierung sehr viel besser beschreiben als durch Entwurfsprinzipien und Heuristiken, die sonst oft als Begründungen herangezogen werden [1]. Zusätzlich liefert uns die kognitive Psychologie zwei weitere Mechanismen: Hierarchisierung und Schemata, die weitere wichtige Hinweise für Modularisierung mitbringen.

    Stay tuned

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

    [mc4wp-simple-turnstile]

     

    Chunking ➔ Modularisierung

    Damit Menschen in der Menge der Informationen, mit denen sie konfrontiert sind, zurechtkommen, müssen sie auswählen und Teilinformationen zu größeren Einheiten gruppieren. Dieses Bilden von höherwertigen Abstraktionen, die immer weiter zusammengefasst werden, nennt man in der kognitiven Psychologie Chunking (Abb. 1). Dadurch, dass Teilinformationen als höherwertige Wissenseinheiten abgespeichert werden, wird das Kurzzeitgedächtnis entlastet und weitere Informationen können aufgenommen werden.

    Abb. 1: Chunking

    Als Beispiel soll hier eine Person dienen, die das erste Mal mit einem Telegrafen arbeitet. Sie hört die übertragenen Morsezeichen als kurze und lange Töne und verarbeitet sie am Anfang als getrennte Wissenseinheiten. Nach einiger Zeit wird sie in der Lage sein, die Töne zu Buchstaben – und damit zu neuen Wissenseinheiten – zusammenzufassen, sodass sie schneller verstehen kann, was übermittelt wird. Einige Zeit später werden aus einzelnen Buchstaben Wörter, die wiederum größere Wissenseinheiten darstellen, und schließlich ganze Sätze.

    Entwickler:innen und Architekt:innen wenden Chunking automatisch an, wenn sie sich neue Software erschließen. Der Programmtext wird im Detail gelesen, und die gelesenen Zeilen werden zu Wissenseinheiten gruppiert und so behalten. Schritt für Schritt werden die Wissenseinheiten immer weiter zusammengefasst, bis ein Verständnis des Programmtexts und der Strukturen, in die er eingebettet ist, erreicht ist.

    Diese Herangehensweise an Programme wird als Bottom-up-Programmverstehen bezeichnet und von Entwicklungsteams in der Regel angewendet, wenn ihnen ein Softwaresystem und sein Anwendungsgebiet unbekannt sind und sie sich das Verständnis erst erarbeiten müssen. Bei Kenntnis des Anwendungsgebiets und des Softwaresystems wird von Entwicklungsteams eher Top-down-Programmverstehen eingesetzt. Top-down-Programmverstehen bedient sich hauptsächlich der beiden strukturbildenden Prozesse Bildung von Hierarchien und Aufbau von Schemata, die in den folgenden Abschnitten eingeführt werden.

    Eine andere Form des Chunking kann man bei Expert:innen beobachten. Sie speichern die neuen Wissenseinheiten nicht einzeln im Kurzzeitgedächtnis ab, sondern fassen sie direkt durch Aktivierung bereits gespeicherter Wissenseinheiten zusammen. Wissenseinheiten können allerdings nur aus anderen Wissenseinheiten gebildet werden, die für die Versuchsperson sinnvoll zusammengehören. Bei Experimenten mit Experten eines Wissensgebiets und Anfängern wurden den beiden Gruppen Wortgruppen aus dem Wissensgebiet des Experten präsentiert. Die Experten konnten sich fünfmal so viele Begriffe merken wie die Anfänger. Allerdings nur, wenn die Wortgruppen sinnvoll zusammengehörige Begriffe enthielten.

    An Entwickler:innen und Architekt:innen konnten diese Erkenntnisse ebenfalls nachgewiesen werden. Chunking funktioniert auch bei Softwaresystemen nur dann, wenn die Struktur des Softwaresystems sinnvoll zusammenhängende Einheiten darstellt. Programmeinheiten, die beliebige Operationen oder Funktionen zusammenfassen, sodass für die Entwicklungsteams nicht erkennbar ist, warum sie zusammengehören, erleichtern das Chunking nicht. Der entscheidende Punkt dabei ist, dass Chunking nur dann angewendet werden kann, wenn sinnvolle Zusammenhänge zwischen den Chunks existieren.

    DIE KUNST DER SOTWARE-ARCHITEKTUR

    Architecture & Design-Track entdecken

     

    Module als zusammenhängende Einheiten

    Für Modularisierung und modulare Architekturen ist es also essenziell, dass sie aus Bausteinen wie Klassen, Komponenten, Modulen, Schichten bestehen, die in sinnvoll zusammenhängenden Elementen gruppiert sind. In der Informatik gibt es eine Reihe von Entwurfsprinzipien, die diese Forderung nach zusammenhängenden Einheiten einlösen wollen:

    • Information Hiding (Geheimnisprinzip): David Parnas forderte 1972 als Erster, dass ein Modul genau eine Entwurfsentscheidung verbergen soll und die Datenstruktur zu dieser Entwurfsentscheidung in dem Modul gekapselt sein sollte (Kapselung und Lokalität). Parnas gab diesem Grundsatz den Namen Information Hiding [2].

    • Separation of Concerns: Dijkstra schrieb in seinem auch heute noch lesenswerten Artikel mit dem Titel „A Discipline of Programming“ [3], dass verschiedene Teile einer größeren Aufgabe möglichst in verschiedenen Elementen der Lösung repräsentiert werden sollten. Hier geht es also um das Zerlegen von zu großen Wissenseinheiten mit mehreren Aufgaben. In der Refactoring-Bewegung sind solche Einheiten mit zu vielen Verantwortlichkeiten als Code Smell unter dem Namen God Class wieder aufgetaucht.

    • Kohäsion: In den 1970er-Jahren arbeitete Myers seine Ideen über den Entwurf aus und führte das Maß Kohäsion ein, um den Zusammenhalt innerhalb von Modulen zu bewerten [4]. Coad und Yourdon erweiterten das Konzept für die Objektorientierung [5].

    • Responsibility-driven Design (Entwurf nach Zuständigkeit): In die gleiche Richtung wie das Geheimnisprinzip und die Kohäsion zielt Rebecca Wirfs-Brocks Entwurfsheuristik, Klassen nach Zuständigkeiten zu entwerfen: Eine Klasse ist eine Entwurfseinheit, die genau eine Verantwortung erfüllen und damit nur eine Rolle in sich vereinigen sollte [6].

    • Single Responsibility Principle (SRP): Als Erstes legt Robert Martin in seinen SOLID-Prinzipien fest, dass jede Klasse nur eine fest definierte Aufgabe erfüllen soll. In einer Klasse sollten lediglich Funktionen vorhanden sein, die direkt zur Erfüllung dieser Aufgabe beitragen. Effekt dieser Konzentration auf eine Aufgabe ist, dass es nie mehr als einen Grund geben sollte, eine Klasse zu ändern. Dafür ergänzt Robert Martin auf Architekturebene das Common Closure Principle. Klassen sollen in ihren übergeordneten Bausteinen lokal sein, sodass Veränderungen immer alle oder keine Klassen betreffen [7].

    All diese Prinzipien wollen Chunking durch den inneren Zusammenhalt der Einheiten fördern. Modularität hat aber noch mehr zu bieten. Ein Modul soll nach Parnas außerdem mit seiner Schnittstelle eine Kapsel für die innere Implementierung bilden.

    Module mit modularen Schnittstellen

    Durch Schnittstellen kann Chunking erheblich unterstützt werden, wenn die Schnittstellen – welche Überraschung – sinnvolle Einheiten bilden. Die für Chunking benötigte Wissenseinheit kann in der Schnittstelle eines Moduls so gut vorbereitet werden, dass die Entwicklungsteams sich den Chunk nicht mehr durch die Analyse des Inneren des Moduls zusammensammeln müssen.

    Eine gute zusammenhängende Schnittstelle entsteht, wenn man die Prinzipien aus dem letzten Abschnitt nicht nur beim Entwurf des Inneren eines Moduls anwendet, sondern auch für seine Schnittstelle [1], [7], [8]:

    • Explizite und kapselnde Schnittstelle: Module sollten ihre Schnittstellen explizit machen, d. h., die Aufgabe des Moduls muss klar erkennbar sein, und von der internen Implementierung wird abstrahiert.

    • Delegierende Schnittstellen und das Law of Demeter: Da Schnittstellen Kapseln sind, sollten die in ihnen angebotenen Dienste so gestaltet sein, dass Delegation möglich wird. Echte Delegation entsteht, wenn die Dienste an einer Schnittstelle Aufgaben komplett übernehmen. Dienste, die dem Aufrufer Interna zurückliefern, an denen der Aufrufer weitere Aufrufe ausführen muss, um zu seinem Ziel zu gelangen, verletzen das Law of Demeter.

    • Explizite Abhängigkeiten: An der Schnittstelle eines Moduls sollte direkt erkennbar sein, mit welchen anderen Modulen es kommuniziert. Erfüllt man diese Forderung, dann wissen Entwicklungsteams, ohne in die Implementierung zu schauen, welche anderen Module sie verstehen oder erzeugen muss, um mit dem Modul zu arbeiten. Dependency Injection passt direkt zu diesem Grundprinzip, denn es führt dazu, dass alle Abhängigkeiten über die Schnittstelle in ein Modul injiziert werden.

    All diese Prinzipien haben als Ziel, dass Schnittstellen das Chunking unterstützen. Werden sie eingehalten, so sind Schnittstellen als eine Wissenseinheit schneller zu verarbeiten. Werden nun auch noch die Grundprinzipien der Kopplung beachtet, so haben wir für das Chunking beim Programmverstehen viel gewonnen.

    Module mit loser Kopplung

    Um ein Modul einer Architektur zu verstehen und ändern zu können, müssen sich Entwicklungsteams einen Überblick über das zu ändernde Modul selbst und seine benachbarten Module verschaffen. Wichtig sind dafür alle Module, mit denen das Modul zusammenarbeitet. Je mehr Abhängigkeiten es von einem Modul zum anderen gibt (Abb. 2), umso schwieriger wird es, die einzelnen Beteiligten mit der begrenzten Kapazität des Kurzzeitgedächtnisses zu analysieren und passende Wissenseinheiten zu bilden. Chunking fällt deutlich leichter, wenn weniger Module und Abhängigkeiten im Spiel sind.

    Abb. 2: Stark gekoppelte Klassen (links) oder Packages/Directories (rechts)

    Lose Kopplung ist das Prinzip in der Informatik, das an diesem Punkt ansetzt [9], [10], [11]. Kopplung bezeichnet den Grad der Abhängigkeit zwischen den Modulen eines Softwaresystems. Je mehr Abhängigkeiten in einem System existieren, desto stärker ist die Kopplung. Sind die Module eines Systems nach den Prinzipien der letzten beiden Abschnitte zu Einheiten und Schnittstellen entwickelt worden, so sollte das System automatisch aus lose gekoppelten Modulen bestehen. Ein Modul, das eine zusammenhängende Aufgabe erledigt, wird dazu weniger andere Module brauchen als ein Modul, das viele verschiedene Aufgaben durchführt. Ist die Schnittstelle nach dem Law of Demeter delegierend angelegt, dann braucht der Aufrufer nur diese Schnittstelle. Er muss sich nicht von Schnittstelle zu Schnittstelle weiterhangeln, um schließlich durch viel zusätzliche Kopplung seine Aufgabe abzuschließen.

    Chunking hat uns bis hierhin geholfen, Modularisierung für das Innere und das Äußere eines Moduls und für seine Beziehung zu betrachten. Spannenderweise spielt auch der nächste kognitive Mechanismus in das Verständnis von Modularisierung hinein.

    Modularisierung durch Muster

    Der effizienteste kognitive Mechanismus, den Menschen einsetzen, um komplexe Zusammenhänge zu strukturieren, sind sogenannte Schemata. Unter einem Schema werden Konzepte verstanden, die aus einer Kombination von abstraktem und konkretem Wissen bestehen. Ein Schema besteht auf der abstrakten Ebene aus den typischen Eigenschaften der von ihm schematisch abgebildeten Zusammenhänge. Auf der konkreten Ebene beinhaltet ein Schema eine Reihe von Exemplaren, die prototypische Ausprägungen des Schemas darstellen. Jeder von uns hat beispielsweise ein Lehrerschema, das abstrakte Eigenschaften von Lehrer:innen beschreibt und als prototypische Ausprägungen Abbilder unserer eigenen Lehrer:innen umfasst.

    Haben wir für einen Zusammenhang in unserem Leben ein Schema, können wir die Fragen und Probleme, mit denen wir uns gerade beschäftigen, sehr viel schneller verarbeiten als ohne Schema. Schauen wir uns ein Beispiel an: Bei einem Experiment wurden Schachmeister:innen und Schachanfänger:innen für ca. fünf Sekunden Spielstellungen auf einem Schachbrett gezeigt. Handelte es sich um eine sinnvolle Aufstellung der Figuren, so waren die Schachmeister:innen in der Lage, die Positionen von mehr als zwanzig Figuren zu rekonstruieren. Sie sahen Muster von ihnen bekannten Aufstellungen und speicherten sie in ihrem Kurzzeitgedächtnis. Die schwächeren Spieler:innen hingegen konnten nur die Position von vier oder fünf Figuren wiedergeben. Die Anfänger:innen mussten sich die Position der Schachfiguren einzeln merken. Wurden die Figuren den Schachexpert:innen und Schachlaien allerdings mit einer zufälligen Verteilung auf dem Schachbrett präsentiert, so waren die Schachmeister:innen nicht mehr im Vorteil. Sie konnten keine Schemata einsetzen und sich so die für sie sinnlose Verteilung der Figuren nicht besser merken.

     

    Die in der Softwareentwicklung vielfältig eingesetzten Entwurfs- und Architekturmuster nutzen die Stärke des menschlichen Gehirns, mit Schemata zu arbeiten. Haben Entwickler:innen und Architekt:innen bereits mit einem Muster gearbeitet und daraus ein Schema gebildet, so können sie Programmtexte und Strukturen schneller erkennen und verstehen, die nach diesen Mustern gestaltet sind. Der Aufbau von Schemata liefert für das Verständnis von komplexen Strukturen also entscheidende Geschwindigkeitsvorteile. Das ist auch der Grund, warum Muster in der Softwareentwicklung bereits vor Jahren Einzug gefunden haben.

    In Abbildung 3 sieht man ein anonymisiertes Tafelbild, das ich mit einem Team entwickelt habe, um seine Muster aufzunehmen. Auf der rechten Seite von Abbildung 3 ist der Source Code im Architekturanalysetool Sotograph in diese Musterkategorien eingeteilt und man sieht sehr viele grüne und einige wenige rote Beziehungen. Die roten Beziehungen gehen von unten nach oben gegen die durch die Muster entstehende Schichtung. Die geringe Anzahl der roten Beziehungen ist ein sehr gutes Ergebnis und zeugt davon, dass das Entwicklungsteam seine Muster sehr konsistent einsetzt.

    Abb. 3: Muster auf Klassenebene = Mustersprache

    Spannend ist außerdem, welchen Anteil des Source Codes man Mustern zuordnen kann und wie viele Muster das System schlussendlich enthält. Lassen sich 80 Prozent oder mehr des Source Codes Mustern zuordnen, dann spreche ich davon, dass dieses System eine Mustersprache hat. Hier hat das Entwicklungsteam eine eigene Sprache erschaffen, um sich die Diskussion über seine Architektur zu erleichtern.

    Die Verwendung von Mustern im Source Code ist für eine modulare Architektur besonders wichtig. Wir erinnern uns: Für das Chunking war es entscheidend, dass wir sinnvoll zusammenhängende Einheiten vorfinden, die eine gemeinsame Aufgabe haben. Wie, wenn nicht durch Muster, lassen sich die Aufgaben von Modulen beschreiben? Modularisierung wird durch den umfassenden Einsatz von Mustern vertieft und verbessert, wenn für die jeweiligen Module erkennbar ist, zu welchem Muster sie gehören, und die Muster konsistent eingesetzt werden.

    Hierarchisierung ➔ Modularisierung

    Der dritte kognitive Mechanismus, die Hierarchisierung, spielt beim Wahrnehmen und Verstehen von komplexen Strukturen und beim Abspeichern von Wissen ebenfalls eine wichtige Rolle. Menschen können Wissen dann gut aufnehmen, es wiedergeben und sich darin zurechtfinden, wenn es in hierarchischen Strukturen vorliegt. Untersuchungen zum Lernen von zusammengehörenden Wortkategorien, zur Organisation von Lernmaterialien, zum Textverstehen, zur Textanalyse und zur Textwiedergabe haben gezeigt, dass Hierarchien vorteilhaft sind. Bei der Reproduktion von Begriffslisten und Texten war die Gedächtnisleistung der Versuchspersonen deutlich höher, wenn ihnen Entscheidungsbäume mit kategorialer Unterordnung angeboten wurden. Lerninhalte wurden von den Versuchspersonen mit Hilfe von hierarchischen Kapitelstrukturen oder Gedankenkarten deutlich schneller gelernt. Lag keine hierarchische Struktur vor, so bemühten sich die Versuchspersonen, den Text selbstständig hierarchisch anzuordnen. Die kognitive Psychologie zieht aus diesen Untersuchungen die Konsequenz, dass hierarchisch geordnete Inhalte für Menschen leichter zu erlernen und zu verarbeiten sind und dass aus einer hierarchischen Struktur effizienter Inhalte abgerufen werden können.

    Die Bildung von Hierarchien wird in Programmiersprachen bei den Enthalten-Sein-Beziehungen unterstützt: Klassen sind in Packages oder Directories, Packages/Directories wiederum in Packages/Directories und schließlich in Projekten bzw. Modulen und Build-Artefakten enthalten. Diese Hierarchien passen zu unseren kognitiven Mechanismen. Sind die Hierarchien an die Muster der Architektur angelehnt, so unterstützen sie uns nicht nur durch ihre hierarchische Strukturierung, sondern sogar auch noch durch Architekturmuster.

    Schauen wir uns dazu einmal ein schlechtes und ein gutes Beispiel an: Stellen wir uns vor, ein Team hat für sein System festgelegt, dass es aus vier Modulen bestehen soll, die dann wiederum einige Submodule enthalten sollen (Abb. 4).

    Abb. 4: Architektur mit vier Modulen

    Diese Struktur gibt für das Entwicklungsteam ein Architekturmuster aus vier Modulen auf der obersten Ebene vor, in denen jeweils weitere Module enthalten sind. Stellen wir uns nun weiter vor, dass dieses System in Java implementiert und aufgrund seiner Größe in einem einzigen Eclipse-Projekt organisiert ist. In diesem Fall würde man erwarten, dass dieses Architekturmuster aus vier Modulen mit Submodulen sich im Package-Baum des Systems wiederfinden sollte.

    In Abbildung 5 sieht man den anonymisierten Package-Baum eines Java-Systems, für das das Entwicklungsteam genau diese Aussage gemacht hatte: „Vier Module mit Submodulen, das ist unsere Architektur!“. In der Darstellung in Abbildung 5 sieht man Packages und Pfeile. Die Pfeile gehen jeweils vom übergeordneten Package zu seinen Kindern.

    Abb. 5: Das geplante Architekturmuster ist schlecht umgesetzt

    Tatsächlich findet man die vier Module im Package-Baum. In Abbildung 5 sind sie in den vier Farben markiert, die die Module in Abbildung 4 haben (grün, orange, lila und blau). Allerdings sind zwei der Module über den Package-Baum verteilt und ihre Submodule sind zum Teil sogar unter fremden Ober-Packages einsortiert. Diese Umsetzung im Package-Baum ist nicht konsistent zu dem von der Architektur vorgegebenen Muster. Sie führt bei Entwicklern und Architekten zu Verwirrung. Das Einführen von jeweils einem Package-Root-Knoten für die orange- und die lilafarbene Komponente würde hier Abhilfe schaffen.

    Eine bessere Abbildung des Architekturmusters auf den Package-Baum sieht man in Abbildung 6. Bei diesem System ist das Architekturmuster symmetrisch auf den Package-Baum übertragbar. Hier können die Entwickler sich anhand der hierarchischen Struktur schnell zurechtfinden und vom Architekturmuster profitieren.

    Abb. 6: Gut umgesetztes Architekturmuster

    Wird die Enthalten-Sein-Beziehung richtig eingesetzt, so unterstützt sie unseren kognitiven Mechanismus Hierarchisierung. Für alle anderen Arten von Beziehungen gilt das nicht: Wir können beliebige Klassen und Interfaces in einer Source-Code-Basis per Benutzungs- und/oder per Vererbungsbeziehung miteinander verknüpfen. Dadurch erschaffen wir verflochtene Strukturen (Zyklen), die in keiner Weise hierarchisch sind. Es bedarf einiges an Disziplin und Anstrengung, Benutzungs- und Vererbungsbeziehung hierarchisch zu verwenden. Verfolgt das Entwicklungsteam von Anfang an dieses Ziel, so sind die Ergebnisse in der Regel nahezu zyklenfrei. Ist der Wert von Zyklenfreiheit nicht von Anfang an klar, entstehen Strukturen wie in Abbildung 7.

    Abb. 7: Zyklus aus 242 Klassen

    Der Wunsch, Zyklenfreiheit zu erreichen, ist aber kein Selbstzweck! Es geht nicht darum, irgendeine technisch strukturelle Idee von „Zyklen müssen vermieden werden“ zu befriedigen. Vielmehr wird damit das Ziel verfolgt, eine modulare Architektur zu entwerfen.

    Achtet man bei seinem Entwurf darauf, dass die einzelnen Bausteine modular, also jeweils genau für eine Aufgabe zuständig sind, dann entstehen in der Regel von selbst zyklenfreie Entwürfe und Architekturen. Ein Modul, das Basisfunktionalität zur Verfügung stellt, sollte nie Funktionalität aus den auf ihm aufbauenden Modulen benötigen. Sind die Aufgaben klar verteilt, dann ist offensichtlich, welches Modul welches andere Modul benutzen muss, um seine Aufgabe zu erfüllen. Eine umgekehrte und damit zyklische Beziehung entsteht erst gar nicht.

     

    Stay tuned

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

    [mc4wp-simple-turnstile]

     

    Zusammenfassung: Regeln für Modularisierung

    Mit den drei kognitiven Mechanismen Chunking, Schemata und Hierarchisierung haben wir das Hintergrundwissen bekommen, um Modularisierung in unseren Diskussionen klar und eindeutig zu verwenden. Eine gut modularisierte Architektur besteht aus Modulen, die den Einsatz von Chunking, Hierarchisierung und Schemata erleichtern. Zusammenfassend können wir die folgenden Regeln festlegen: Die Module einer modularen Architektur müssen

    1. In ihrem Inneren ein zusammenhängendes, kohärentes Ganzes bilden, das für genau eine klar definierte Aufgabe zuständig ist (Einheit als Chunk),

    2. Nach außen eine explizite, minimale und delegierende Kapsel bilden (Schnittstelle als Chunk),

    3. Nach einheitlichen Mustern durchgängig gestaltet sein (Musterkonsistenz) und

    4. Mit anderen Modulen minimal, lose und zyklenfrei gekoppelt sein (Kopplung zur Chunk-Trennung und Hierarchisierung).

    Sind dem Entwicklungsteam diese Mechanismen und ihre Umsetzung in der Architektur klar, ist eine wichtige Grundlage für Modularisierung gelegt.

     

    Links & Literatur

    [1] Dieser Artikel ist ein überarbeiteter Auszug aus meinem Buch: Lilienthal, Carola: „Langlebige Softwarearchitekturen. Technische Schulden analysieren, begrenzen und abbauen“; dpunkt.verlag, 2019

    [2] Parnas, David Lorge: „On the Criteria to be Used in Decomposing Systems in-to Modules“; in: Communications of the ACM (15/12), 1972

    [3] Dijkstra, Edsger Wybe: „A Discipline of Programming“; Prentice Hall, 1976

    [4] Myers, Glenford J.: „Composite/Structured Design“; Van Nostrand Reinhold, 1978

    [5] Coad, Peter; Yourdon, Edward: „OOD: Objektorientiertes Design“; Prentice Hall, 1994

    [6] Wirfs-Brock, Rebecca; McKean, Alan: „Object De-sign: Roles, Responsibilities, and Collaborations“; Pearson Education, 2002

    [7] Martin, Robert Cecil: „Agile Software Development, Principles, Patterns, and Practices“; Prentice Hall International, 2013

    [8] Bass, Len; Clements, Paul; Kazman, Rick: „Software Architecture in Practice“; Addison-Wesley, 2012

    [9] Booch, Grady: „Object-Oriented Analysis and Design with Applications“; Addison Wesley Longman Publishing Co., 2004

    [10] Gamma, Erich; Helm, Richard; Johnson, Ralph E.; Vlissides, John: „Design Patterns. Elements of Reusable Object-Oriented Software“; Addison-Wesley, 1994

    [11] Züllighoven, Heinz: „Object-Oriented Construction Handbook“; Morgan Kaufmann Publishers, 2005

    The post Modularisierung und kognitive Psychologie 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.

    ]]>
    Software-Architektur heute: „Es geht um Menschen, Technologien sind sekundär“ https://jax.de/blog/software-architecture-design/software-architektur-heute-es-geht-um-menschen-technologien-sind-sekundaer/ Thu, 25 Oct 2018 11:26:39 +0000 https://jax.de/?p=65554 Software-Architektur galt lange als die Disziplin, um in Software-Projekten für einen kohärenten Zusammenhang zu sorgen: Stabilität, Sicherheit, Planbarkeit stand im Vordergrund. Wir haben uns mit Henning Schwentner darüber unterhalten, wie sich dieses Bild verändert hat und welche Rolle Trends wie DevOps und DDD dabei spielen.

    The post Software-Architektur heute: „Es geht um Menschen, Technologien sind sekundär“ appeared first on JAX.

    ]]>
    W-JAX: Software-Architektur galt lange als die Disziplin, in Software-Projekten für einen kohärenten Zusammenhang zu sorgen: Es geht darum, Stabilität und Langlebigkeit zu gewährleisten, Standards einzuführen, für Sicherheit zu sorgen, Pläne und Dokumentationen zu erstellen, etc. Heute wird Software-Architektur oft auch anders diskutiert, und zwar im Sinne eines Change Management: Architekturen sollen flexibel, erweiterbar, austauschbar sein. Wie siehst du dich: Wie viel in deiner Arbeit ist Kirchenbauer, wie viel Change Manager?

    Henning Schwentner: Beide Rollen sind untrennbar. Stabilität im Großen bekommen wir über Flexibilität im Kleinen. Das Interessante ist ja, dass insbesondere die großen Systeme, weil sie am längsten leben, gerade deshalb am flexibelsten sein müssen. Deswegen finde ich es gut, dass Trends wie Microservices und Self-Contained Systems die (im Prinzip alten) Ideen, wie man ein System vernünftig modularisiert, im Mainstream ankommen lassen. Der wichtigste Punkt ist: Wir wollen nicht ein großes Domänenmodell haben, sondern mehrere kleine. Die kleinen Domänenmodelle können dann verständlich, beherrschbar und flexibel sein.

    W-JAX: Wie schafft man es, den richtigen Mix aus Stabilität und Flexibilität zu finden?

    Henning Schwentner: Erstens: sich immer und immer wieder klar machen, dass Software kein Selbstzweck ist. Software (und auch Softwarearchitektur) bauen wir nicht für uns selbst, sondern für den Fachexperten. Zweitens: nicht nach dem einen Domänenmodell streben, das alle Probleme auf einmal löst. Das wird nämlich viel zu groß, fehleranfällig und schwer verständlich. Interessanterweise also gleichzeig instabil und unflexibel. Stattdessen wollen wir mehrere kleine Modelle.

    W-JAX: Im Zuge der DevOps-Bewegung erweitert sich das Bild des Software-Architekten noch um eine weitere Facette: Es geht nämlich nicht nur um Anwendungsentwicklung, sondern immer mehr auch darum, wie sich Anwendungen in einer Continuous-Delivery-Landschaft einbetten. „You build it, you run it“ heißt da das Stichwort. Wie hat die DevOps-Bewegung die Rolle des Software-Architekten verändert? Was musst du als Architekt heute anders machen, als früher, als man die Anwendungen noch einfach über den Zaun hin zum Ops-Team geworfen hat?

    Henning Schwentner: Mir geht es da wie vielen: Ich freue mich, dass die unnatürliche Trennung von Entwicklung und Betrieb aufgehoben wird. Als Entwickler und Architekt wird man jetzt von Anfang an darauf fokussiert, nicht eine ausführbare Datei, sonderen laufende Software auszuliefern. Und nur die hat Wert für den eigentlich wichtigen Menschen – unseren Anwender.

    W-JAX: Ein weiterer Trend ist aktuell, das Design einer Software stark an den fachlichen Domänen auszurichten. Neben DDD als Theorie erobern gerade Microservices-Architekturen die Praxis. Neben den technologischen Aspekten, die Domänen-fokussierte Anwendungen mit sich bringen, geht es hier zentral auch darum, die beteiligten Leute erst einmal in ein Boot zu holen: Fachexperten, Entwickler und natürlich auch die Geschäftsleitung und Anwender bzw. Kunden. Ist man da als Software-Architekt nicht eigentlich zu 80% Projektmanager? Wie hältst du das persönlich: Wie stark nimmst du die Rolle des Projektmanagers ein, wie viel konzentrierst du dich auf Technologien?

    Henning Schwentner: Von Jerry Weinberg wissen wir: »No matter how it looks at first, it’s always a people problem.« Die erste Aufgabe jedes Menschen, der mit Softwareentwicklung beschäftigt ist, ob er nun Programmierer, Architekt, Projektmanager oder wie auch immer heißt, ist, mit anderen Menschen zu kommunizieren. Mit Computern zu kommunizieren kommt frühestens auf Platz zwei.

    Technologien sind nicht unwichtig, aber sekundär. Frag dich mal selbst: Von wievielen Programmen und Apps, die du einsetzt, weißt du, welche Programmiersprache, Frameworks usw. darin verwendet wird? Ist dir das wichtig? Oder willst du lieber, dass die ihren Job machen?

    Die Gemeinheit ist: In der Software-Entwicklung gilt das Anna-Karenina-Prinzip, d.h. ein Projekt kann nur dann erfolgreich sein, wenn alle Faktoren stimmen. Technologien kennen und beherrschen wird deshalb immer wichtig und nötig sein.

    W-JAX: Auf der W-JAX wird es ein Lab zum Thema „Event Storming“ geben. Wie funktioniert Event Storming – und weshalb sollte man es machen?

    Henning Schwentner: Die Grundaufgabe von Software ist, unseren Anwender bei seiner Arbeit zu unterstützen. Damit wir das tun können, müssen wir seine Arbeit (d.h. die Domäne) verstehen. Event Storming ist ein Werkzeug, das uns dabei hilft. Bewaffnet mit haufenweise Klebezetteln wird Wissen aufgebaut, ausgetauscht und vertieft. Es hilft uns dabei, eine gemeinsame Sprache, Kontextgrenzen und Domänenmodelle zu finden.

    W-JAX: Welchen Trend findest du im Bereich der Software-Architektur momentan besonders spannend – und warum?
    Henning Schwentner: Mir ist das Thema „Domäne verstehen, um die richtige Software zu bauen“ sehr wichtig. Neben Event Storming ist Domain Storytelling ein tolles Werkzeug dafür. Wir lassen die Anwender ihre Geschichte erzählen. Dabei zeichnen wir sie mit einer einfach zu verstehenden Bildsprache auf. Die entstehenden Bilder (die sogenannten Domain Stories) verwendet man, um direkt rückzukoppeln, ob wir die Anwender richtig verstanden haben. Mehr Infos gibt’s auf domainstorytelling.org und hoffentlich bald in dem Buch, das Stefan Hofer und ich gerade darüber schreiben.

    W-JAX: Vielen Dank für dieses Interview!

     

    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 Software-Architektur heute: „Es geht um Menschen, Technologien sind sekundär“ appeared first on JAX.

    ]]>
    Was Sie bei der Einführung von APIs wissen sollten https://jax.de/blog/software-architecture-design/was-sie-bei-der-einfuehrung-von-apis-wissen-sollten/ Thu, 04 Oct 2018 13:02:31 +0000 https://jax.de/?p=65319 Jede noch so gut definierte Schnittstelle kann an einen Punkt kommen, an dem sie weiterentwickelt werden muss. Welche Herausforderungen bei der Einführung von APIs zu bewältigen sind, verrät uns Arne Limburg im Interview.

    The post Was Sie bei der Einführung von APIs wissen sollten appeared first on JAX.

    ]]>
    W-JAX: Viele Unternehmen führen derzeit APIs ein, die ältere technische Lösungen wie SOA, ESBs und/oder monolithische Systeme ersetzen. Weshalb eigentlich? Warum hat die sogenannte API Economy momentan Konjunktur?

    Arne Limburg: Das hat mehrere Aspekte. Da ist zum Einen der technologische Aspekt. Moderne REST-APIs sind viel schlanker als frühere Lösungen wie SOAP oder XML-RPC und das sowohl im Design als auch bei der Datenmenge, die über die Leitung geht.

    Dann gibt es natürlich den architektonischen Aspekt. Man hat mittlerweile erkannt, dass Architekturen, die auf einem ESB basieren, nicht so leicht zu warten und weiterzuentwickeln sind, als wenn sich die Services direkt unterhalten. Damit das gelingt, benötigt man aber gute APIs, die auch stabil bleiben. Da spielt das Thema Abwärtskompatibilität und Versionierung eine wichtige Rolle.

    Und last but not least haben viele Unternehmen erkannt, dass sich mit gut definierten Public APIs auch Geld verdienen lässt. Unternehmen erschließen sich neue Vertriebskanäle, in dem sie APIs anbieten, über die z.B. Mobile Clients angebunden werden können (Stichwort: Multi-Channel-Strategie). Die Clients müssen dann nicht immer vom Unternehmen selbst gebaut werden. Es gibt auch interessante Konstellationen, in denen Drittanbieter an dem Unternehmensumsatz partizipieren können.

    W-JAX: Deine Session auf der W-JAX heißt „Abwärtskompatible APIs – Strategien für den Projektalltag.“ Dabei gehst du darauf ein, wie man Schnittstellen sinnvoll weiterentwickelt, wenn sich beispielsweise die Anforderungen verändert haben. Warum genügt es nicht einfach, eine Versionierung für APIs einzuführen?

    Arne Limburg: Es geht vor allem um die Pflege alter Versionen. Wenn ich ein Public API betreibe, kann ich nicht einfach eine Version 2 des API zur Verfügung stellen und Version 1 abschalten. Damit erzeuge ich hohen Aufwand bei meinen Clients, die dann zeitgleich updaten müssten. Auf Dauer macht das kein externer Client-Anbieter mit.

    Die Erfahrung zeigt aber, dass die Pflege alter Versionen serverseitig sehr aufwendig ist, wenn man es nicht richtig angeht. Es geht also darum, einen Weg zu finden, serverseitig alte Versionen einfach pflegen zu können und gleichzeitig den Clients leichte Wege zu eröffnen, auf die neueste Version zu aktualisieren.

    W-JAX: Kannst du einmal einen Tipp geben, wie man Abwärtskompatibilität von APIs sicherstellen kann, ohne sich im Support alter Versionen zu verlieren?

    Arne Limburg: In aller Ausführlichkeit erkläre ich das natürlich in meinem Talk auf der W-JAX. Kurz gesagt, geht es darum, einerseits gewisse Anforderungen an den Client zu stellen (Stichwort: Tolerant Reader Pattern) aber andererseits auf dem Server auch dafür zu sorgen, dass die API kompatibel bleibt, in dem innerhalb einer Version nur Attribute hinzukommen, aber niemals welche entfernt werden. Beim Versionssprung ist es wichtig, dass das Mapping zwischen alter und neuer Version nicht zu aufwendig ist.

     

     

    W-JAX: In den meisten Fällen haben Unternehmen noch Legacy-Systeme am Laufen, die bei der Einführung von APIs integriert werden müssen. Welche technologischen Herausforderungen gilt es dabei zu meistern?

    Arne Limburg: Hier gibt es zwei Arten von Legacy-Systemen. Für die einen ist das Unternehmen im Besitz des Source Codes und hat auch das Know-How, um die Systeme weiterzuentwickeln. Hier empfehlen wir immer, ein sauberes RESTful API für das Legacy-System zu realisieren, um es in die „neue Welt“ einzubinden. Häufig ist das gar nicht so schwer.

    Sollte sich das nicht realisieren lassen, empfehlen wir einen Anti-Corruption-Layer, der dann die saubere Schnittstelle zur Verfügung stellt und eigentlich nichts anderes macht als zwischen Legacy und neuer Welt hin- und herzumappen. Das kann dann z.B. auch ein Caching beinhalten, wenn das Legacy-System nicht auf eine so hohe Anzahl von Requests ausgelegt ist oder wenn es sogar nur im Batch-Betrieb läuft.

    W-JAX: Bei der Einführung von APIs bleibt es aber ja nicht bei den technologischen Herausforderungen. Weshalb hat das auch Konsequenzen auf die gesamte Organisation eines Unternehmens?

    Arne Limburg: In vielen Unternehmen ist es nach wie vor so, dass die Entwicklung sehr Projekt-getrieben ist. Die Einführung eines neuen Features ist ein eigenes Projekt, für das es ein separates Budget gibt und häufig auch noch ein Plichten- und Lastenheft.

    Moderne APIs müssen allerdings als Produkt betrieben werden, das kontinuierlich weiterentwickelt wird und auf diese Weise benötigte Features zur Verfügung stellt. Die Art und Weise, wie neue Themen in die IT eingebracht werden, muss sich daher häufig komplett ändern.

    W-JAX: Wie sollte man ein API-basiertes Projekt deiner Erfahrung nach angehen? Es gibt da ja verschiedenste Ansätze: Startet man technologisch, oder muss man zuerst das Unternehmen umstrukturieren? Braucht es zunächst ein ausgefeiltes Konzept zum API Management, oder ist der MVP-Ansatz hier besser: erstmal klein starten, Feedback einholen, weiterentwickeln?
    Arne Limburg: Das Vorgehen, erst einmal klein anzufangen, um Erfahrung zu sammeln, ist auf jeden Fall ein Vorgehen, das sich bewährt hat. Dennoch sollte man sich beim Design einer API nicht von der Technologie treiben lassen. Es geht ja nicht primär darum, wie ich den Server schnell realisieren kann, sondern darum, eine API so aufzubauen, dass viele unbekannte Clients sie leicht nutzen können und gerne nutzen. Mein Ansatz ist deshalb immer der sogenannte Contract-First-Ansatz, wobei der Name etwas irreführend ist, weil er nicht das Ziel widerspiegelt. Eigentlich müsste man den Ansatz Client-First nennen. Gute APIs sind in der Regel die, bei denen das Design mit der Überlegung ausgeführt wurde: Was benötigt der Client?

    W-JAX: Vielen Dank für dieses Interview!

     

     

    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 Was Sie bei der Einführung von APIs wissen sollten appeared first on JAX.

    ]]>
    Microservices sind kein Allheilmittel! https://jax.de/blog/software-architecture-design/microservices-sind-kein-allheilmittel/ Tue, 11 Sep 2018 09:16:40 +0000 https://jax.de/?p=65214 In Zeiten von Agile, DevOps und DDD verändert sich auch die Rolle des Software-Architekten. Wir haben uns mit Ralf D. Müller, darüber unterhalten, wie man als Software-Architekt den richtigen Mix aus Stabilität und Flexibilität findet, welche Impulse von der DevOps-Bewegung ausgehen und wie DDD dabei hilft, wertschöpfende Software zu bauen.

    The post Microservices sind kein Allheilmittel! appeared first on JAX.

    ]]>
    W-JAX: Software-Architektur galt lange als die Disziplin, in Software-Projekten für einen kohärenten Zusammenhang zu sorgen: Es geht darum, Stabilität und Langlebigkeit zu gewährleisten, Standards einzuführen, für Sicherheit zu sorgen, Pläne und Dokumentationen zu erstellen, etc. Heute wird Software-Architektur oft auch anders diskutiert, und zwar im Sinne eines Change Management: Architekturen sollen flexibel, erweiterbar, austauschbar sein. Wie siehst du dich: Wie viel in deiner Arbeit ist Kirchenbauer, wie viel Change Manager?

    Ralf D. Müller: Beide Aspekte der Architektur – Stabilität und Flexibilität – müssen wie immer ausgewogen vorhanden sein und bauen aufeinander auf. Erst wenn gewisse Standards existieren und die Vorgehensweise, Schnittstellen etc. dokumentiert sind, lässt sich eine Architektur auch gut ändern, ohne die Stabilität zu riskieren. Deshalb ist es ja auch so wichtig, dass die Architektur gut kommuniziert und die Pfade zur Umsetzung der architekturellen Aspekte ausgetrampelt werden.

    Nur wenn jeder im Team weiß, auf was es architekturell ankommt, entsteht die benötigte Stabilität, um später flexibel auf geänderte Anforderungen reagieren zu können. Das arc42-Template von Gernot Starke und Peter Hruschka hilft hier bei der Strukturierung der Dokumentation.

    W-JAX: Wie schafft man es, den richtigen Mix aus Stabilität und Flexibilität zu finden? Hast du da vielleicht einen Tipp aus deinen langjährigen Erfahrungen?

    Ralf D. Müller: Jedes Projekt ist anders und bringt unterschiedliche Anforderungen bezüglich Stabilität und Flexibilität mit. Deswegen ist es wichtig, einen Blick auf die Requirements zu werfen und nicht einfach eine interessante Architektur eines anderen Projekts zu übernehmen. Die Requirements geben meist vor, welcher Teil der Architektur flexibel und welcher stabil sein muss.

    Soll z.B. ein White-Label Produkt erstellt werden, dann ist das Design sicherlich flexibler zu halten als bei einer Anwendung zur internen Verwendung. Aber die beiden Attribute müssen sich auch nicht widersprechen: Erst die Stabilität in den Schnittstellen zwischen wohldefinierten Modulen ermöglicht die Flexibilität zur Änderung einzelner Module.

     

    W-JAX: Im Zuge der DevOps-Bewegung erweitert sich das Bild des Software-Architekten noch um eine weitere Facette: Es geht nämlich nicht nur um Anwendungsentwicklung, sondern immer mehr auch darum, wie sich Anwendungen in einer Continuous-Delivery-Landschaft einbetten. „You build it, you run it“ heißt da das Stichwort. Wie hat die DevOps-Bewegung die Rolle des Software-Architekten verändert? Was musst du als Architekt heute anders machen, als früher, als man die Anwendungen noch einfach über den Zaun hin zum Ops-Team geworfen hat?

    Ralf D. Müller: Hat man das früher gemacht – Anwendungen einfach über den Zaun hin zum Ops-Team geworfen?  Der Betrieb der Software war schon immer ein wichtiger Aspekt der Architektur. Eine Applikation wird meist länger betrieben, als entwickelt. Somit ist der Aspekt des Betriebs für den Erfolg der Software mindestens genauso wichtig wie z.B. der Aspekt des Clean Code.

    Aus meiner Sicht hat sich in diesem Bereich die wichtigste Änderung nicht direkt durch DevOps ergeben, sondern durch die iterativen Entwicklungszyklen eines agilen Projekts. Es gibt nicht mehr das Upfront-Design der Architektur, sondern man kann ein Projekt nun über mehrere Release-Zyklen begleiten. Dadurch sieht der Architekt vor allem, wie die Architektur die Qualitätskriterien des Projekts auch tatsächlich implementiert und kann die Architektur entsprechend anpassen.

    W-JAX: Ein weiterer Trend ist aktuell, das Design einer Software stark an den fachlichen Domänen auszurichten. Neben DDD als Theorie erobern gerade Microservices-Architekturen die Praxis. Neben den technologischen Aspekten, die Domänen-fokussierte Anwendungen mit sich bringen, geht es hier zentral auch darum, die beteiligten Leute erst einmal in ein Boot zu holen: Fachexperten, Entwickler und natürlich auch die Geschäftsleitung und Anwender bzw. Kunden. Ist man da als Software-Architekt nicht eigentlich zu 80% Projektmanager? Wie hältst du das persönlich: Wie stark nimmst du die Rolle des Projektmanagers ein, wie viel konzentrierst du dich auf Technologien?

    Ralf D. Müller: Es stimmt schon, dass Software-Architektur streckenweise mehr mit Management als mit Technologie zu tun hat. Aber wie bei allem hängt es sehr stark vom eigentlichen Projekt und des Typs „Architekt“ ab, den man verkörpert. Mich selbst würde ich weniger als Projekt-, sondern mehr als Architekturmanager sehen. Die Architektur, die in meinem Kopf ist, muss irgendwie raus in die Umsetzung. Das geschieht durch Dokumentation, Kommunikation und auch Management.

     

     

    W-JAX: Auf der W-JAX hältst du einen Talk namens „Docs-as-Code“. Wo liegt der große Unterschied zwischen dem Docs-as-Code-Ansatz, den ihr beschreibt, und der traditionellen Art und Weise, Software zu dokumentieren?

    Ralf D. Müller: Der Unterschied ist recht groß und vielfältig. Ich denke, jeder kennt die klassische, mit einer Textverarbeitung erstellte Dokumentation, die getrennt vom Code verwaltet wird. Dokumentation gehört aber, wie Tests auch, zum Code und sollte mit diesem verwaltet werden. Dadurch ist immer klar, wo die aktuelle Version liegt.

    Sobald die Dokumentation zusammen mit dem Code verwaltet wird, können auch weitere Aspekte der Softwareentwicklung auf die Dokumentation übertragen werden. So kann z.B. das Aktualisieren von Diagrammen automatisiert im Build umgesetzt und die Dokumentation sogar automatisiert getestet werden. Wird eine Änderung am Code vorgenommen, so gehört es mittlerweile zur Definition of Done, auch die Tests anzupassen. Mit Docs-as-Code wird im gleichen Schritt auch die Dokumentation gepflegt, weil ein Pull-Request sonst nicht als vollständig im Sinne der DoD angesehen wird.

    W-JAX: Welchen Trend findest du im Bereich der Software-Architektur momentan besonders spannend – und warum?

    Ralf D. Müller: Ich beobachte momentan, wie der Ansatz der Microservices reift. Zum Einen setzt sich die Erkenntnis durch, dass auch Microservices nicht die Lösung für jedes Problem sind und man abwägen muss. Aber auch die Art der Implementierung von Microservices auf der JVM entwickelt sich weiter. So steht mit micronaut.io mittlerweile ein Framework zur Verfügung, welches zielgerichtet auf Microservices hin entwickelt wurde und nicht als Full-Stack Framework entstand. Auch der Serverless-Ansatz ist in diesem Zusammenhang spannend. Solche Entwicklungen sorgen dafür, dass die Arbeit als Software-Architekt immer spannend bleiben wird.

    Vielen Dank für dieses Interview!

     

    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 Microservices sind kein Allheilmittel! appeared first on JAX.

    ]]>