Blog

Fitness Functions: ausgewählte Architektureigenschaften prüfen

Fitness Functions machen ausgewählte Eigenschaften wie Kopplung, Abhängigkeiten und Latenz kontinuierlich prüfbar. Fachliche Qualität und Architekturabwägungen ersetzen sie nicht.

≈ 9 Min. Lesezeit

Diesen Beitrag anhören (13 Min.)

MP3 herunterladen

Eine Fitness Function ist kein vollständiges Qualitätsmodell. Sie bewertet gezielt eine operationalisierbare Architektureigenschaft – etwa Zyklenfreiheit, Kopplung, Latenz oder Verträglichkeit zwischen zwei Services. In diesem Beitrag konzentriere ich mich auf automatisierte, möglichst eindeutige Prüfungen, die bei jedem Commit ein eng begrenztes Urteil fällen. Eigenschaften wie Verständlichkeit oder fachliche Angemessenheit brauchen weiterhin menschliche Bewertung.

Wie groß der Unterschied zur üblichen Praxis ist, zeigt ein Satz, der in Architektur-Reviews regelmäßig fällt: „Die Domäne darf natürlich nicht auf die Infrastruktur zugreifen.“ Alle nicken. Es wird ein Diagramm gezeichnet, vielleicht ein Confluence-Eintrag angelegt. Drei Sprints später zeigt eine Import-Anweisung von der Domain-Schicht direkt in den Datenbank-Adapter. Niemand hat das böswillig getan. Es war Freitagnachmittag, der Bugfix musste raus, und die Regel stand nun mal nicht im Code, sondern in einem Protokoll, das keiner mehr liest. Wo sich eine Architektureigenschaft eindeutig prüfen lässt, ist ein automatischer Check verlässlicher als die Erinnerung im Review.

Eine Fitness Function schließt den Weg von einer gewünschten Eigenschaft bis zu einer wiederholbaren Entscheidung.

  1. Eigenschaft · eine fachlich relevante Architekturqualität benennen
  2. Beobachtung · ein reproduzierbares Signal dafür auswählen
  3. Schwelle · den noch akzeptablen Bereich ausdrücklich festlegen
  4. Prüfung · das Signal automatisch und wiederholbar auswerten
  5. Gate · eine Verletzung sichtbar machen oder die Änderung passieren lassen
  6. Anpassung · Eigenschaft und Schwelle bei veränderten Bedingungen erneut prüfen

Die Reihenfolge bewahrt den fachlichen Grund der Messung. Ein vorhandenes Tool allein definiert noch keine sinnvolle Fitness Function.

Vom Wunsch zur mechanischen Prüfung

Der Begriff stammt aus Building Evolutionary Architectures von Neal Ford, Rebecca Parsons und Patrick Kua (O'Reilly, 2017), entliehen aus dem Evolutionary Computing, wo eine Fitness-Funktion misst, wie nah eine Lösung am Ziel ist. Übertragen auf Software heißt das: Eine architectural fitness function ist ein objektiver, mechanischer Integritätstest für ein oder mehrere architectural characteristics – also für die Eigenschaften, die man früher etwas sperrig "-ilities" nannte: Performance, Skalierbarkeit, Kopplung, Sicherheit.

Der Perspektivwechsel, der in der Praxis den Unterschied macht, ist folgender: Architektur wird nicht länger als Zustand behandelt, den man einmal beschließt, sondern als Eigenschaft, die man kontinuierlich einhält. Das klingt nach einer Nuance, ist aber ein Bruch im Arbeitsmodus. Ein Diagramm beschreibt, wie das System aussehen soll. Eine Fitness Function beschreibt, wie es aussehen muss – und bricht den Build, wenn es das nicht tut.

Damit wandert ein Teil der Governance von der Sitzung in die Pipeline. Die automatisierbaren Regeln laufen bei jedem Commit mit; Reviews bleiben dort nötig, wo Zielkonflikte, Kontext und fachliche Angemessenheit zählen. Der Gewinn ist vor allem das frühe Feedback: Eine explizit vereinbarte Regel hängt nicht davon ab, ob sie im nächsten Review wieder auffällt. Wer schon einmal erlebt hat, wie ein einziger schleichender Kopplungsverstoß über Monate ein Refactoring blockiert, kennt den Preis der nachgelagerten Kontrolle.

Die erste Fitness Function ist ein Unit-Test

Der Einstieg ist unspektakulär, und das ist die gute Nachricht. Die einfachste Kategorie sind statische, atomare Fitness Functions – Regeln über die Struktur des Codes, die sich als ganz normaler Test ausdrücken lassen. In der Java-Welt hat sich dafür ArchUnit von TNG etabliert, das über Bytecode-Analyse Paket- und Schichtabhängigkeiten prüft. Für .NET gibt es NetArchTest, im JavaScript-Umfeld den Dependency-Cruiser. Das Prinzip ist überall dasselbe.

Die eingangs beschworene Regel – Domäne kennt keine Infrastruktur – sieht als Fitness Function so aus:

@ArchTest
static final ArchRule domain_must_not_depend_on_infrastructure =
    noClasses().that().resideInAPackage("..domain..")
        .should().dependOnClassesThat().resideInAPackage("..infrastructure..");

@ArchTest
static final ArchRule no_cyclic_dependencies =
    slices().matching("..(*)..").should().beFreeOfCycles();

Das Schöne daran: Diese Regel ist selbst dokumentierend. Sie steht im Repository, sie läuft in der CI, und sie ist nicht verhandelbar, ohne dass jemand sie sichtbar ändert. Der Confluence-Eintrag von oben beschreibt eine Absicht. Der Test macht dieselbe gewählte Regel ausführbar und ihre Verletzung sichtbar.

Der zweite Block – no_cyclic_dependencies – adressiert einen Klassiker, der in Reviews notorisch schwer zu erkennen ist. Zyklische Abhängigkeiten entstehen selten in einem großen Schritt. Sie wachsen aus vielen kleinen, je für sich harmlosen Importen. Ein Mensch sieht den Zyklus im Diagramm nicht mehr, wenn er über sieben Module läuft. Die Maschine schon. Genau solche Eigenschaften – afferent/efferent coupling, Instabilität, Zyklenfreiheit – sind der Idealfall für statische Fitness Functions, weil sie deterministisch und billig zu prüfen sind.

Mehr als eine Werkzeugliste

Wer bei Fitness Functions nur an ArchUnit denkt, verkennt die Reichweite des Konzepts. Ford, Parsons und Kua schlagen eine Taxonomie vor, die sich gut als Sortierhilfe eignet, weil sie den richtigen Testtyp für die jeweilige Eigenschaft nahelegt. Zwei Achsen genügen für den Anfang:

quadrantChart
    title Einordnung von Fitness Functions
    x-axis "atomic (ein Merkmal)" --> "holistic (Zusammenspiel)"
    y-axis "triggered (bei Bedarf)" --> "continuous (laufend)"
    quadrant-1 "Latenz-/Lasttests in Prod"
    quadrant-2 "Chaos-/Resilienz-Checks"
    quadrant-3 "ArchUnit-Dependency-Rules"
    quadrant-4 "Security-Scan pro Commit"

Atomic misst eine einzelne Eigenschaft isoliert, holistic prüft das Zusammenspiel mehrerer – etwa Sicherheit und Latenz gemeinsam unter Last. Triggered läuft auf Anstoß, etwa im Build, continuous läuft dauerhaft gegen das produktive System. Dazu kommen weitere Dimensionen aus dem Buch: static versus dynamic, automated versus manual, sowie temporal fitness functions, die bewusst nur für eine begrenzte Zeit gelten – etwa eine Erinnerung, eine Bibliotheksmigration bis zu einem Stichtag abzuschließen.

Der praktische Wert dieser Einteilung liegt nicht in der Klassifikation um ihrer selbst willen. Er liegt darin, dass sie eine Frage erzwingt, der man sonst ausweicht: An welches konkrete architectural characteristic ist diese Prüfung eigentlich gekoppelt? Wer das nicht beantworten kann, baut vermutlich gerade eine Vanity-Metrik.

Wenn die Eigenschaft dynamisch ist

Nicht alles lässt sich statisch prüfen. Latenz zum Beispiel ist keine Eigenschaft des Quelltexts, sondern des laufenden Systems. Hier kommen dynamische Fitness Functions ins Spiel, die gegen ein Deployment messen. Konzeptionell bleibt es dasselbe – ein Schwellwert, ein Vergleich, ein Urteil:

def latency_fitness(service):
    p95 = measure_p95_ms(service, window="5m")
    assert p95 < LATENCY_BUDGET_MS, f"p95 {p95} exceeds budget"

So sauber das aussieht, so gefährlich ist es, diesen Assert blind als hartes Gate in die CI zu hängen. Latenzwerte schwanken. Ein p95 über ein Fünf-Minuten-Fenster kann durch einen einzelnen Garbage-Collection-Peak oder einen langsamen Nachbarn im Cluster reißen, ohne dass sich an deiner Software irgendetwas geändert hätte. Wer das ignoriert, produziert flaky builds – und ein flaky gate wird binnen zwei Wochen weggeklickt oder auskommentiert. Dann hast du die Reibung der Prüfung, aber nicht mehr ihren Nutzen.

Die pragmatische Antwort ist eine Trennung: Statische, deterministische Eigenschaften dürfen den Build blockieren. Dynamische, statistisch schwankende Eigenschaften laufen als beobachtende, getriggerte Checks mit statistischen Fenstern und alarmieren, statt hart zu brechen. Dieselbe Idee, zwei Betriebsmodi – je nachdem, wie deterministisch das gemessene Merkmal ist.

Für resilienzbezogene Eigenschaften lohnt der Blick in Michael Nygards Release It!. Die dort beschriebenen Stability Patterns – Circuit Breaker, Bulkhead, Timeout – liefern nicht nur Bausteine für robuste Systeme, sondern auch prüfbare Erwartungen: Öffnet der Circuit Breaker unter simulierter Überlast wie vorgesehen? Auch das ist eine Fitness Function, nur eben eine dynamische.

Kopplung zwischen Services messen

Die interessanteste Kopplung in verteilten Systemen verläuft nicht innerhalb eines Deployables, sondern zwischen ihnen. Zwei Teams, zwei Services, ein Vertrag – und die stille Annahme, dass der andere sein API nicht kaputtmacht. Consumer-Driven Contract Testing macht diese Annahme prüfbar. Mit Pact und einem Pact Broker beschreibt der Konsument seine Erwartung an den Provider, und der Provider verifiziert gegen genau diese Erwartung.

Als Fitness Function landet das direkt als Schritt in der Deployment-Pipeline:

pact-broker can-i-deploy \
    --pacticipant Checkout \
    --version "$GIT_SHA" \
    --to-environment production

Die Frage "kann ich das deployen, ohne einen Konsumenten zu brechen?" wird damit von einer Bauchentscheidung zu einer mechanischen Antwort. Aus der Annahme, kompatibel zu bleiben, wird ein Nachweis – Kopplung als Governance-as-Code. In der Praxis ist das oft der Moment, in dem Teams zum ersten Mal wirklich unabhängig deployen können, weil die Angst vor dem unbemerkten Bruch aus dem Prozess genommen ist.

Der Ort, an dem alles zusammenläuft

Eine lose Sammlung von Regeln entfaltet wenig Wirkung. Wirksam werden Fitness Functions erst als Gate in der Auslieferung. Die Deployment-Pipeline – konzeptuell seit Humble und Farley etabliert – ist der natürliche Ort dafür. Zwischen Build und Deploy liegt eine Barriere, die mehrere Kategorien parallel prüft:

flowchart LR
    commit["commit"] --> build["build"]
    build --> gate{"Fitness<br/>Functions<br/>Gate"}
    gate -->|static / atomic| s["Dependency &amp; Cycle Rules"]
    gate -->|dynamic / holistic| d["Latency p95 &amp; Load"]
    gate -->|contract| c["Pact can-i-deploy"]
    s -->|fail = break build| commit
    d -->|fail = break build| commit
    c -->|fail = break build| commit
    gate -->|all green| deploy["deploy"]

Die roten Rückpfeile sind der Kern der Idee: Ein Verstoß führt nicht zu einem Ticket, das irgendwann priorisiert wird, sondern zu einem gebrochenen Build, den derjenige repariert, der ihn verursacht hat – sofort, mit vollem Kontext, bevor der Fehler in die Historie einsintert. Das ist derselbe Mechanismus, der Continuous Integration wirksam macht, angewandt auf Architektureigenschaften.

Und hier schließt sich der Kreis zur messbaren Auslieferungsqualität. Accelerate von Forsgren, Humble und Kim und die DORA Four Key Metrics – Deployment Frequency, Lead Time for Changes, Change Failure Rate, Time to Restore – liefern Metriken für die Delivery-Ebene. Fitness Functions können dazu passende technische Leitplanken prüfen. Eine verletzte Kopplungsregel fällt dann früh auf; ob dadurch Lead Time oder Fehlerrate sinken, muss das Team an den eigenen Daten überprüfen.

Wo es in der Praxis kippt

So überzeugend das Konzept ist, so zuverlässig scheitert seine naive Umsetzung an drei Stellen – und die habe ich in Projekten alle gesehen.

Erstens die Vanity-Metrik. Eine grüne Fitness Function, die eine reine Test-Coverage-Schwelle prüft, fühlt sich nach Sicherheit an und misst doch nichts Architektonisches. Coverage sagt, wie viel Code ausgeführt wird, nicht, ob die Abhängigkeiten stimmen. Jede Function muss an ein konkretes architectural characteristic gekoppelt sein, sonst ist sie Theater.

Zweitens die Wartungslast. Fitness Functions sind Code und altern wie Code. Ich habe Codebasen gesehen, in denen jede zweite Regel ein @ArchIgnore trug, weil es unter Termindruck schneller ging, die Ausnahme einzutragen, als das Problem zu lösen. Ohne Ownership und regelmäßiges Aufräumen der Ausnahmeliste erodiert die Aussagekraft, bis nur noch grünes Rauschen bleibt. Diese Regeln brauchen dieselbe Pflege wie Produktionscode – sonst sind sie schlimmer als nichts, weil sie falsche Sicherheit erzeugen.

Drittens der zu späte Einstieg. Wer Fitness Functions auf eine gewachsene Legacy-Codebasis loslässt, bekommt beim ersten Lauf Tausende Violations – und garantiert Frust. Der Ausweg ist eine Baseline: Man friert den Ist-Zustand ein, deklariert Fitness von jetzt an und lässt nur noch Verbesserung zu, keine Verschlechterung. "Declare fitness from now on" ist kein Betrug an der Reinheit. In gewachsenen Systemen ist es die einzige Art, wie so etwas überhaupt anläuft.

Fazit

Der rote Faden dieser drei Bausteine – statische Regeln, dynamische Messungen, Vertragsprüfungen – ist ein einziger Gedanke: Architektur ist eine Eigenschaft, die man einhält, keine Entscheidung, die man einmal trifft. Fitness Functions verschieben die Kontrolle vom Review in die Pipeline und damit von der Hoffnung zur Gewissheit.

Der Gewinn ist selten die einzelne Regel. Er liegt im veränderten Arbeitsmodus. Teams, die ihre wichtigsten Architektureigenschaften mechanisch absichern, diskutieren in Reviews nicht mehr über Importrichtungen, sondern über Fachlichkeit. Sie deployen öfter, weil die stille Angst vor dem unbemerkten Bruch aus dem Prozess genommen ist. Und sie refaktorieren mutiger, weil ein Netz aus grünen Checks sofort meldet, wenn eine Umstrukturierung eine Eigenschaft verletzt.

Der Anfang darf klein sein: eine einzige atomare, statische Regel, die eine Eigenschaft absichert, an der dir wirklich etwas liegt. Eine Abhängigkeitsregel, die den Build bricht, ist mehr wert als zwanzig Regeln in einem Dokument, das keiner liest. Der Rest wächst von dort – als schlichter Code, der bei jedem Build mitläuft und niemandem einen zusätzlichen Prozess aufzwingt.

Weiterführende Quellen

  • Neal Ford, Rebecca Parsons, Patrick Kua: Building Evolutionary Architectures (O'Reilly, 2017) – Kapitel zu Fitness Functions
  • ArchUnit (TNG) – statische Architekturtests für Java
  • Nicole Forsgren, Jez Humble, Gene Kim: Accelerate (2018) und die DORA Four Key Metrics – martinfowler.com: StateOfDevOpsReport
  • Michael Nygard: Release It! (2. Auflage, 2018) – Stability Patterns für resilienzbezogene Fitness Functions
  • Pact – Consumer-Driven Contract Testing mit can-i-deploy

Wie fandest du diesen Beitrag?

Kommentare