Immer wieder gibt es Uneinigkeit darüber, wie zustandsbehaftete Domänenklassen mit Funktionalität ausgestattet werden sollen. Ist es eine Tugend, ein Kundenobjekt nach seiner Bonität befragen zu können? Die vorherrschende Meinung sieht das wohl so. Und wo sie zustandsbehaftete Domänenklassen auf die Datenhaltung reduziert sieht, spricht sie von einem anämischen Domänenmodell. Gestern bei einem TDD-Training und auch heute beim Architecture Open Space gab es dazu wieder einige Diskussion.
Aber warum wird denn überhaupt dazu soviel noch diskutiert? Ist es denn nicht klar, was richtig ist? Hat die Objektorientierung nicht schon seit Jahrzehnten eine gute Antwort gefunden? Anscheinend nicht. Es gibt immer noch oder schon wieder zwei Lager. Die einen bemühen sich, Domänenmodelle reich zu machen; da bekommt der Kunde seine Bonitätsprüfungsfunktion. Die anderen sehen das als kontraproduktiv an und argumentieren, Geschäftsregeln und Datenstrukturen seien auseinander zu halten. Ich gehörer letzterer Fraktion an, wie ich in früheren Blogartikeln schon klar gemacht habe.
Leider kann ich jedoch mit meinen Argumentationsversuchen nicht immer so leicht punkten, wie ich es mir wünsche. Deshalb bin ich immer auf der Suche nach neuen Gesichtspunkten, die meine Position stützen und erklären helfen – oder, ja, von mir aus auch einfach begreifbar widerlegen.
Heute nun habe ich wieder einen solchen Gesichtspunkt entdeckt. Und hätte der Architecture Open Space nicht auch sonst schon wegen des Community Erlebnisses Spaß gemacht, dann wäre er deshalb lohnenswert gewesen. Hier meine Erkenntnis:
Anämische Domänenmodelle sind eine Tugend, weil sie entkoppeln.
Ha! Wer hätte das gedacht? Wieder ist die Antwort auf hartnäckige Fragen “Entkopplung”. Abhängigkeiten, also Kopplung, ist einfach eines der Grundübel der Softwareentwicklung.
Meine Argumentation:
Abhängigkeiten jeder Art behindern die Evolvierbarkeit. Sie sind deshalb zu vermeiden oder zumindest zu minimieren.
Wenn eine Funktionseinheit A von vielen anderen Funktionseinheiten abhängig ist – U1, U2, U3, … –, d.h. eine hohe efferente Kopplung hat, dann ist das Risiko groß, dass A sich ändern muss, weil irgendwo bei U1, U2, U3 usw. eine Änderung vorgenommen wurde.
Wenn andererseits eine Funktionseinheit U von vielen abhängigen Funktionseinheiten A1, A2, A3 usw. gebraucht wird, d.h. eine hohe afferente Kopplung hat, dann ist das Risiko groß, dass eine Änderung an U sich auf alle A* auswirkt.
Wir können es also drehen und wenden, wie wir wollen, eine große Anzahl von Abhängigkeiten koppelt eng, weil die Abhängigkeitslinien Wege für die Ausbreitung von “Veränderungsschockwellen” sind.
Nun ist es natürlich unzweifelhaft, dass Abhängigkeiten nötig sind. Softwaresysteme wären keine Systeme, wenn sie nur Haufen unverbundener Funktionseinheiten wären. Also ist die Frage, wie können wir den potenziellen Schaden von Abhängigkeiten in Grenzen halten?
- Die Kopplung sollte so gering wie möglich und so stark wie nötig sein. Bewusstheit beim Aufbau von Abhängigkeiten ist also gefragt.
- Je größer die Kopplung, desto einfacher sollten die gekoppelten Funktionseinheiten sein. Kopplung und Kompliziertheit sollten umgekehrt proportional sein.
Einfachheit lässt sich herstellen durch: - fokussieren der Verantwortlichkeit (Single Responsibility Principle)
- verbergen von Details (Encapsulation)
Die zweite Regel verdient besondere Aufmerksamkeit. Schauen wir uns an, welche “Verhältnisse” sich ergeben, jenachdem ob die gekoppelten Funktionseinheiten einfach oder kompliziert sind:
Efferente Kopplung | U* einfach | U* kompliziert |
A einfach | überschaubar | überschaubar |
A kompliziert | kompliziert | komplex |
Eine hohe efferente Kopplung ist unkritisch, wenn die abhängige Funktionseinheit einfach ist. Dann können sogar die unabhängiggen Funktionseinheiten kompliziert sein, denn falls Änderungen an ihnen “durchschlagen”, muss nur Einfaches angepasst werden - wenn überhaupt; was zu tun ist, ist dann überschaubar.
Ist die abhängige Funktionseinheit jedoch kompliziert, dann werden die Verhältnisse komplex. Denn dann ist nur schwer abschätzbar, was überhaupt als Anpassung zu tun ist, falls die Unabhängigen sich ändern.
Etwas schlimmer sieht es sogar noch bei der afferenten Kopplung aus:
Afferente Kopplung | U einfach | U kompliziert |
A* einfach | überschaubar | komplex |
A* kompliziert | kompliziert | komplex |
Hier sind die Verhältnisse immer komplex, wenn die unabhängige Funktionseinheit kompliziert ist. Änderungen wirken sich ja potenziell auf viele andere Funktionseinheiten aus. Das ist schwer abzuschätzen.
Jetzt die Übersetzung auf die Modellierung:
- Event-based Components (EBC) packen das Thema Kopplung/Abhängigkeiten bei den Hörnern, indem sie Funktionseinheiten im Sinne der Funktionalität statisch und dynamisch unabhängig voneinander machen. Logische Kopplung existiert zwar weiterhin, aber zumindest müssen Bauteile sich nicht mehr untereinander kennen. Das ist ein guter Schritt voran bei der Entkopplung. Unmittelbar ist das daran zu erkennen, dass Sie zum automatisierten Testen von EBC-Bauteilen keine Mock-Frameworks brauchen.
- EBC entschärfen die efferente Kopplung, indem sie Abhängigkeiten auf die Verdrahtung beschränken. Nur Platinen sind abhängig von anderen Funktionseinheiten. Das mögen dann auch viele sein – doch das macht nichts, weil Platinen denkbar einfach sind. Ihr einziger Zweck ist die Verdrahtung mit trivialem Code. Die Verhältnisse sind überschaubar, auch wenn die verdrahteten Funktionseinheiten kompliziert sind (s. rechte obere Tabellenzelle bei der efferenten Kopplung).
- EBC geben Domänendatenmodellen einen klaren Platz in der Modellierung: als Typen für die Daten, die zwischen EBC-Funktionseinheiten fließen.
Das bedeutet jedoch, dass viele Funktionseinheiten von ihnen abhängig sind. Im Bild müssen A, B, C und D den Domänenobjektmodelltyp d kennen. Die afferente Kopplung von d ist also hoch.
Das bedeutet – und das ist meine heutige Erkenntnis auf dem Architecture Open Space –, dass d nicht kompliziert sein darf.
Domänendatenobjekte haben per definitionem eine hohe afferente Kopplung. Das ist für mich der wesentliche Grund, warum sie so einfach wie möglich gehalten werden sollten. Prinzip schlägt Objektorientierung, möchte ich sagen. Es ist mir also egal, ob ein Domänenobjektmodell als anämisch angesehen wird oder nicht. Objektorientierung ist ein Tool. Mit diesem Tool sollte ich nicht gegen fundamentale Prinzipien – hier: Abhängigkeiten erzeugen Komplexität; Entkopplung reduziert Komplexität – verstoßen, auch wenn eine bestimmte Benutzung des Tools noch so toolgemäß aussehen mag.
Wo hohe Abhängigkeiten von zwischen Objekten bestehen, da muss auf die Kompliziertheit der Beteiligten sehr genau geachtet werden. Mit EBC sind wir da auf einem sehr guten Weg, würde ich sagen. Aber nun weiß ich auch, warum es nicht schlimm ist, wenn EBC irgendwie gegen die heilige Kuh “reichhaltiges Domänenobjektmodell” verstößt, weil Domänenobjektmodelle, die auf Drähten fließen, immer irgendwie blutleer wirken. Auch das ist nämlich eine Tugend, weil ihre Datentypen so weitreichend gekannt werden. Anämie kann also auch mal gesund sein, wie hier zu sehen ist :-)
Objektorientiertes Nachspiel
Mir ist jetzt auch klarer, wo Chancen und Grenzen der Objektorientierung liegen. Gegenüber der prozeduralen Programmierung bietet Objektorientierung Kapselung.
Wo früher ein Record/struct nur Daten gehalten hat und die Funktionalität, die auf diesen Daten auch nur das Einfachste tun sollte, davon getrennt war, hat Objektorientierung Daten und Funktionalität zusammengezogen und unter einer “kleineren” Oberfläche verborgen. Die afferente Kopplung sinkt damit, weil U (s. zweite Abbildung oben) einfacher wird. Das ist gut so. Danke, Objektorientierung! Da liegt die Chance für uns mit der Objektorientierung.
Die Grenze der Objektorientierung ist jedoch erreicht, wenn ihre Möglichkeit zur Zusammenfassung von Daten und Funktionalität darüber hinaus gedehnt werden. Die Objektorientierung hat angefangen mit dem Begriff des ADT (Abstrakter Datentyp), d.h. einem “Objektmodell”, das Zustand hat und auf sich auch operieren kann. Beispiele dafür sind Stack, Baum oder Priority Queue usw. Gern auch ein “anämisches Objektmodell” mit Funktionen, die nicht über sich selbst hinausgreifen und keinen Zweck jenseits des “Zustandsorganisation” haben, der Konsistenzhaltung.
Wenn nun Objektorientierung über den ADT hinausgeht und ein reichhaltiges Domänenobjektmodell kreiert, dann mag die Kapselung immer noch ordentlich sein, auch wenn sie schwieriger wird. Vor allem aber wird der “Single Purpose” auf die Probe gestellt und die Kompliziertheit steigt schnell an.
Und genau das ist es, wo dann die Werte der Objektorientierung durch höhere Werte überstimmt werden. Wenn Objektorientierung die Entkopplung gefährdet, dann hat sie ihre Grenze erreicht. Das sehe ich beim reichhaltigen Domänenobjektmodellen. Sie überspannen den objektorientierten Bogen. Sie erzeugen – im allerbesten Willen – ungünstig hohe afferente Kopplung.
Objektorientierung ist nur ein Tool. Wer professionell Software entwickeln will, muss ihre Werte abwägen gegen andere. EBC tut das mit den bisherigen Übersetzungsvorschlägen für Modelle, würde ich sagen. Objektorientierung wird genutzt – solange der hohe Wert der Evolvierbarkeit repräsentiert durch das Prinzip “lose Kopplung, hohe Kohäsion” nicht kompromittiert wird.
5 Kommentare:
Ja, das klingt doch wirklich gut und einleuchtend.
1) Wo Abhängigkeiten existierten dürfen die beteiligten nicht kompliziert sein.
2) Von der Domänenklasse ist so gut wie alles afferent abhängig, d.h. sie darf nicht kompliziert sein.
Klingt völlig logisch, aber sehr gut herausgehoben - danke Ralf! :-)
Objektorientierung führt Funktionalität und Daten zusammen. Dass dies zu einer Kopplung von Funktionalität und Daten führt, scheint mir ganz offensichtlich zu sein.
Und so ganz willst Du ja nun auch nicht davon weg, denn sie bietet ja Kapselung. Aber wenn sie Entkoppelung gefährde, dann sei die Grenze erreicht. Ein Domainobjektmodell sei hinreichend konkret um entkoppelt zu werden.
Ich verstehe das nicht. Mir ist das zu kompliziert. Danke, aber ich kaufe keine EBCs.
@Carsten: Wenn du dir ein wenig mehr Zeit nimmst, meinen Beitrag zu lesen, dann siehst du vielleicht, dass ich nichts gegen die Kopplung von Daten und Funktionalität per se habe.
Diese Kopplung ist nur kein Selbstzweck und immer in höchstem Maße anzustreben.
Ich habe lediglich ein Kriterium aus meiner Sicht genannt, was hilft, die Waage zu halten zwischen der wünschenswerten Zusammenlegung von Daten und Funktionen und der Trennung für Evolvierbarkeit auf der anderen Seiten. Lose Kopplung, hohe Kohäsion - so lautet eines der ältesten Prinzipien der SWentwicklung.
Wenn dir das immer noch zu kompliziert ist, dann ist es ok. Lass gut sein. Und EBC musst du nicht kaufen. Gar nichts musst du kaufen. Kannst einfach weiter machen wie immer.
Wie schon vorher erwähnt, besteht immer eine gewisse Schwierigkeit die Balance zwischen Kapselung, Entkopplung und hoher Kohäsion zu halten. Für mich bieten EBC als auch DDD Vorteile:
* EBC mit der klaren Entkopplung
* DDD mit der Ubiqutuous Language und klaren einfachen Patterns
* Beides mit einer Form von Kapselung
Für mich stellt sich nicht die Frage was ich anwende, sondern auf welchem "Level" ich was anwende. So könnte man sich doch eine EBC vorstellen die intern ein Domain-Model benutzt. Was denkt ihr?
@Johannes: Ein Flow-based Model benutzt natürlich ein Domänenmodell. Aber ein anämisches. Aus den hier dargestellten Gründen.
Darüber hinaus kann ein Flow-based Model aber auch ein Domänenmodell sein! DDD lässt es offen, was eine Entität ist. Die rich domain models von Jimmy Nielsson & Co sind nur eine mögliche Interpretation. Mit Flow-based Models kann man aber auch anders interpretieren.
Kommentar veröffentlichen