Zyklische Abhängigkeiten – eine Architektur-Todsünde?

Continuous Architecture  –  19 Kommentare

Kaum etwas ist schlimmer bei einer Softwarearchitektur als zyklische Abhängigkeiten. Aber warum eigentlich? Und stimmt das?

Softwarearchitekturen teilen ein System in Module auf und definieren Beziehungen zwischen Modulen. Ziel ist, die Module möglichst unabhängig entwickeln zu können. Bestimmte Abhängigkeiten zwischen Modulen sind erlaubt und andere nicht. Durch die Abhängigkeiten können sich Module nämlich beeinflussen: Wenn das Modul "Rechnung" das Modul "Bestellung" nutzt, dann kann eine Änderung am Modul "Bestellung" eine Änderung an Modul "Rechnung" erzwingen. Das ist beispielsweise der Fall, wenn sich die Schnittstelle ändert. Durch die Abhängigkeit ist also eine unabhängige Entwicklung nicht immer möglich.

Wenn Module unabhängig entwickelt werden sollen, müssen Abhängigkeiten vermieden werden. Aber alle Module sollen ein System ergeben. Das geht nur, wenn Module andere Module nutzen, um so das System zu ergeben. Einige Abhängigkeiten sind also notwendig.

Oft ist die Architektur eines Systems problematisch: Änderungen sind schwierig und ziehen sich durch viele Module. Die Ursache dafür können zyklische Abhängigkeiten sein. Bei einer zyklischen Abhängigkeit würde nicht nur das Modul "Rechnung" das Modul "Bestellung" nutzen, sondern auch "Bestellung" das Modul "Rechnung". Eine Änderung an "Rechnung" oder "Bestellung" kann also das jeweils andere Modul beeinflussen. Aber eigentlich sollten die beiden Module getrennt sein. Deswegen sind sie ja in der Architektur separiert. Durch die zyklische Abhängigkeit sind sie nur gemeinsam änderbar. Daher halten viele zyklische Abhängigkeiten für eine Art Todsünde.

Ein System ohne zyklische Abhängigkeiten ist also sicherlich einfacher änderbar, und daher kann das Beseitigen der zyklischen Abhängigkeiten ein sinnvolles Ziel für eine Architektur-Verbesserung sein.

Aber wie schlimm ist die zyklische Abhängigkeit wirklich? Es kann sein, dass Änderungen an Modulen in einer zyklischen Abhängigkeit in der Realität nicht immer dazu führen, dass beide Module gemeinsam geändert werden müssen. Schließlich kann man Glück haben und die Änderung bleibt auf ein Modul beschränkt und beeinflusst kein abhängiges Module. Das kann der Fall sein, wenn die Schnittstelle unverändert bleibt. Eine zyklische Abhängigkeit muss also nicht immer zu einem Problem werden.

Wenn "Bestellung" und "Rechnung" keine zyklische Abhängigkeit haben, können die Abhängigkeiten dennoch ein Problem sein: Wenn fast alle Änderungen von "Bestellung" auch eine Änderung an der Schnittstelle und damit an "Rechnung" erzwingen, dann ist die getrennte Entwicklung praktisch nicht möglich, obwohl es keine zyklische Abhängigkeit gibt. Immerhin kann "Rechnung" noch geändert werden, ohne das "Bestellung" zu ändern ist, weil in diese Richtung keine Abhängigkeit existiert. Aber dass eine Änderung in einem Modul fast immer eine Änderung in einem andere Modul verursacht, kann schlimmer sein als eine zyklische Abhängigkeit, wenn Änderungen in der Realität immer auf ein Modul beschränkt bleiben.

Nur zyklische Abhängigkeiten zu beseitigen, löst also nur einen Teil des Problems und gegebenenfalls noch nicht einmal den wichtigsten Teil.

Kann man Zyklen eigentlich vollständig eliminieren? Selbst vorbildliche Systeme wie das Spring Framework haben zwischen Klassen Zyklen. Nur zwischen Java-Packages erreicht es tatsächlich Zyklenfreiheit. Architektur-Management-Werkzeuge erlauben es, solche Package-Zyklen zu finden und zu eliminieren. Manchmal ist das ganz einfach: Man verschiebt eine Klasse oder ein paar Klassen in ein anderes Package. Wenn nur diese Klassen eine Abhängigkeit in die falsche Richtung hatten, kann die zyklische Abhängigkeit so verschwinden. Oder man führt eine Schnittstelle ein. Aber soll das Verschieben von Klassen oder ein Interface wirklich die unabhängige Entwicklung in einem System ermöglichen?

Diese Ansätze sind rein technisch und setzten kein Wissen über die Domäne voraus. Einige Werkzeuge können sogar eine "perfekte" Struktur automatisiert ermitteln. Aber die Kunst bei der Software-Architektur ist es, die Logik für die Domäne zu strukturieren. Ein solcher, rein technischer Ansatz kann also eigentlich nicht der richtige Weg sein.

Also gilt auch hier wie bei anderen Metriken: Zyklische Abhängigkeiten können ein Hinweis auf ein Problem sein – oder auch nicht. Sie nur zu eliminieren, um den Metrik-Fetischismus zu befriedigen, ist nicht sinnvoll. Man sollte sie nur angehen, wenn die Änderbarkeit des System tatsächlich aufgrund der zyklischen Abhängigkeiten schlecht ist. Und dann ist der richtige Weg, die Domänen-Logik besser zu strukturieren und nicht einfach nur die Metriken zu optimieren.

Am Ende nimmt einem also auch beim Managen der Abhängigkeiten niemand das Denken ab und man muss selbst die richtigen Maßnahmen ergreifen.

Zyklische Abhängigkeiten können die Änderbarkeit einer Software negativ beeinflussen, aber den Ruf, eine Todsünde zu sein, haben sie zu Unrecht.