This blog post is also available in English

Die Übersetzung dieses Artikels aus dem englischen Original erfolgte mit KI-Unterstützung.

In meinem aktuellen Projekt arbeiten wir mit mehreren Bounded Contexts, die jeweils als Self-Contained Systems umgesetzt sind. Eine der architekturellen Leitplanken, die wir früh festgelegt haben, lautet: Alle Systeme verwenden eine Ports-and-Adapters-Architektur – also eine saubere Trennung von Fachlogik (Use Cases) und technischen Belangen (z. B. Controller als Adapter) über definierte Schnittstellen (Ports).

Um diese Leitplanke abzusichern, nutzen wir Fitness Functions, die wir als ArchUnit-Tests implementiert haben. Diese Tests finden eine ganze Reihe potenzieller Verstöße. Beispielsweise schlagen sie fehl, wenn eine Klasse aus dem Domain-Modell von einem Adapter, Port oder Use Case abhängt. Allerdings verhindern sie nicht, dass Controller von Domain-Interfaces abhängen. Diese Lücke – kombiniert mit Lieferdruck – führte dazu, dass sich nach und nach immer mehr Geschäftslogik in den Controllern sammelte. Nicht aus böser Absicht oder aufgrund mangelnder Kompetenz, sondern weil die üblichen Lieferzwänge uns oft dazu bringen, kurzfristige Geschwindigkeit über langfristige Wartbarkeit zu stellen.

Schon vor einiger Zeit habe ich begonnen, die Scout Rule – oder auch Campsite Rule – bewusst in meinen Arbeitsalltag zu integrieren: Hinterlasse einen Ort besser, als du ihn vorgefunden hast. Für Softwareentwickler:innen bedeutet das: Hinterlasse eine Codebasis besser, als du sie vorgefunden hast. Das heißt nicht, dass man jede Woche viele Stunden in reine Verbesserungsaufgaben investieren muss. Stattdessen geht es darum, sich anzugewöhnen, jeden Tag kleine Optimierungen vorzunehmen. Diese dauern oft nur 10 Minuten und lassen sich gut „unterwegs“ erledigen – zum Beispiel während der Implementierung eines neuen Features.

Ein Praxisbeispiel: Refactoring in Richtung Clean Architecture

In meiner Rolle im Bereich Datenprodukte muss ich sicherstellen, dass alle geschäftsrelevanten Ereignisse unserer Systeme verfügbar gemacht werden. Dafür muss ich operative Systeme gelegentlich dazu bringen, neue Domain Events an unseren Message Broker zu veröffentlichen. Genau aus diesem Grund musste ich vor ein paar Wochen Änderungen an dem erwähnten System vornehmen.

Als ich analysierte, wo das neue Domain Event veröffentlicht werden musste, stellte ich fest: Es war ein Controller. Zunächst implementierte ich die Logik zum Publizieren des neuen Events dort direkt. Doch nach einem Moment des Innehaltens wurde mir klar, dass das an dieser Stelle viel zu viel Geschäftslogik ist. Also machte ich ein kleines Refactoring: Ich führte ein neues Interface als Inbound Port ein und implementierte es in einer neuen Use-Case-Klasse. Die gesamte Geschäftslogik aus der Controller-Methode verschob ich in diese neue Klasse. Das war meine Scout-Rule-Aufgabe für diesen Tag.

Wenn frühere Aufräumaktionen sich auszahlen

Ein paar Wochen später erreichte uns eine neue fachliche Anforderung: In bestimmten Fällen sollte eine Aktion nicht länger manuell über die Weboberfläche von Mitarbeitenden ausgelöst werden müssen. Stattdessen sollte sie automatisch ausgeführt werden, sobald die entsprechenden Bedingungen erfüllt sind. Während unseres Refinements stellte sich heraus, dass diese neue Anforderung aus zwei Gründen nahezu trivial umzusetzen war:

Erstens: Genau die Aktion, die nun automatisch ausgelöst werden sollte, war dieselbe, deren Logik ich vor einigen Wochen aus dem Controller in einen dedizierten Use Case verschoben hatte – einfach nur, weil ich regelmäßig kleine Verbesserungen einbaue.

Zweitens: Die Bedingung, unter der die Aktion automatisch ausgeführt werden sollte, war bereits durch ein Domain Event abgebildet, das veröffentlicht wird, wenn genau diese Bedingung eintritt. Dieses Domain Event hatte ich vor einigen Monaten ergänzt – allerdings nicht, weil es für die operativen Systeme notwendig war, sondern weil ich es zur Implementierung eines Datenprodukts brauchte.

Die Kombination aus meinem Scout-Rule-Refactoring und der Existenz eines Domain Events, das bislang ausschließlich für Reporting-Zwecke genutzt wurde, machte es möglich, die neue Anforderung mit minimalen Änderungen umzusetzen. Einen Event-Consumer hinzuzufügen ist trivial – es ist nur ein weiterer Adapter, der denselben Use Case aufruft. Aus einer manuellen Interaktion im Web-UI wurde automatisiertes, eventgetriebenes Verhalten – ohne Code-Duplikation zwischen Controller und Consumer und ohne architektonische Anpassungen.

Der Zinseszinseffekt kontinuierlicher Architekturarbeit

Die Scout Rule zu praktizieren ist ohnehin eine gute Idee, aber in Momenten wie diesem wird besonders deutlich, wie viel tägliche, kontinuierliche Architekturarbeit langfristig ausmacht – und wie stark sie sich potenzieren kann. Es zeigt auch, dass die Vorteile einer Ports-and-Adapters-Architektur nicht nur theoretischer Natur sind, sofern man diese konsequent umsetzt. Wie mein Kollege Fabian begeistert sagte: „Jetzt fügt sich alles zusammen!“