Link Search Menu Expand Document

2 Clean Architecture

Inhalt

Was ist Clean Architecture?

Allgemeine Beschreibung der Clean Architecture in eigenen Worten

Hintergrund

Seit Anbeginn der Menschheit wurden Technologien eingesetzt, um Ziele effektiv und effizient zu erreichen. Waffen zum Jagen von Tieren wurden aus Stein und Holz gewonnen und diese immer weiter verbessert. Doch die Entwicklungsfortschritte in den letzten Jahrhundert sind zeitlich gesehen enorm und verdichten sich immer weiter. Nicht zuletzt durch die Synchronisation der Uhren gegen Ende des 19. Jahrhunderts, die Geschwindigkeitsrevolution der Gesellschaft durch neue Transportmittel wie der Eisenbahn sowie die Erfindung des Internets 1969 kommt immer mehr ein GefĂŒhl der Beschleunigung der modernen Geschichte auf.

Besonders in der Informatik sind die Entwicklungen immens und als Entwickler:in ist es teils sehr schwierig, mit den neuen Technologien mitzuhalten und vom Zug des Fortschritts nicht abgehĂ€ngt zu werden. Dies mag zum Teil daran liegen, dass die Kosten zum Erstellen von Prototypen, aber auch zum Ausliefern eines finalen Produkts extrem gering sind, wenn man die Informatik mit anderen Disziplinen vergleicht. Zwar mĂŒssen Entwickler:innen und Serverinfrastruktur bezahlt werden — doch im Vergleich zum Bau eines Hauses, bei dem Hunderttausende Euro allein fĂŒr Baukosten aufkommen können, scheinen die Kosten fĂŒr eine Anwendung in der Informatik vergleichsweise gering.

Um beim Hausbau zu bleiben: als Informatiker:innen genießen wir einen weiteren, bedeutenden Vorteil. Wir können kostengĂŒnstig Prototypen entwickeln, die eventuell sogar zu einem ausgereiften Produkt weiterentwickelt werden. Als Architekt sind sicherlich Prototypen auch hilfreich, doch diese werden im Modellformat Ă  la DIN-A4-Blatt angefertigt und nicht anhand eines “echten” Hauses mit Rohstoffen, die auch beim richtigen Hausbau verwendet werden.

Privilegien und Herausforderungen im IT-Sektor

Ein weiteres Privileg der Informatiker:innen ist die Anpassbarkeit unserer Software. Beim Hausbau muss bereits beim Entwurf einer Wohnung entschieden werden, an welchen Stellen Steckdosen in der Wand angebracht werden sollen, damit die Stromleitungen entsprechend geplant werden können. Im Nachhinein wĂ€re es nur noch mit (grĂ¶ĂŸerem) Aufwand möglich, die Position der Steckdosen anzupassen.

In der Software-Branche hingegen können wir — bei geeigneter Architektur — unser Produkt in der Art gestalten, dass es auch noch nachtrĂ€glich einfach anpassbar und erweiterbar ist. In Hinblick auf den vorher angefĂŒhrten, rasenden Technologiewandel ist dies enorm wichtig, da Technologien stĂ€ndig aussterben und neue entstehen. In den seltensten FĂ€llen basiert heutzutage ein Programm nur noch auf einer Technologie. Stattdessen treffen wir eine Entscheidung und wĂ€hlen aus einer Myriade von Optionen die fĂŒr uns am besten geeignetste heraus — zumindest die geeignetste nach unserem Empfinden zu einem bestimmten Zeitpunkt. Ein Jahr spĂ€ter hĂ€tte diese Wahl ganz anders ausfallen können. Im schlimmsten Fall ist bis dahin eine Technologie vom Aussterben bedroht, auf die wir vor einem Jahr gesetzt haben. Wenn unsere Architektur nicht gut genug durchdacht ist, könnte dies das Ende unserer gesamten Lösung bedeuten, da der Aufwand zu groß ist, die bisherige Codebasis zu migrieren.

Was ist nun Clean Architecture?

Clean Architecture bezeichnet ein Architektur-Prinzip, das eine nachhaltige Architektur von Programmcode ermöglichen soll. Nachhaltig in diesem Sinne bedeutet, dass nachtrĂ€glich Entscheidungen von Technologiewahlen revidiert werden können, ohne eine Unmenge von Änderungen im Code nach sich zu ziehen. Dazu wird der Code in Schichten (Layers) aufgeteilt, die jeweils fĂŒr sich stehen und eine ganz bestimmte Aufgabe ĂŒbernehmen (“Separation of Concerns”).

Insbesondere beim Zwiebelmodell (Onion Architecture) wird bildlich klar, was Clean Architecture auszeichnet. Im Kern ist der zentrale, langlebige Code angesiedelt, der sich möglichst wenig Ă€ndert und unabhĂ€ngig von den Ă€ußeren Schichten kompiliert werden kann. Dies wird nur dadurch erzielt, dass der DomĂ€nen-Code in der innersten Schicht keine AbhĂ€ngigkeit zu einer Ă€ußeren Schicht hat. Stattdessen werden im Kern hĂ€ufig Interfaces definiert, die dann von Ă€ußeren Schichten implementiert werden. Äußere Schichten dĂŒrfen also AbhĂ€ngigkeiten zu inneren Schichten haben, jedoch dĂŒrfen innere Schichten nicht von Ă€ußeren abhĂ€ngen. Dies beschreibt die Dependency Rule: AbhĂ€ngigkeitspfeile zeigen immer von außen nach innen.

Dieser Aufbau hat einen großen Vorteil: der Kern ist — abgesehen von der verwendeten Programmiersprache — komplett unabhĂ€ngig von jeglichen Technologien, das heißt zum Beispiel unabhĂ€ngig von einem konkreten UI oder einer konkreten Datenbank. Dies impliziert, dass Ă€ußere Schichten einfach ausgetauscht werden können, ohne die Implementierung der DomĂ€ne bzw. die Business-Logik zu gefĂ€hrden. Die inneren Schichten sind im besten Fall komplett von der Außenwelt abgeschirmt. Äußere Schichten beeinflussen innere Schichten in keinster Weise.

Ein weiterer Vorteil von Clean Architecture ist die UnabhĂ€ngigkeit von “schweren” Frameworks, die oftmals eine bestimmte Arbeitsweise verlangen und uns limitieren. Mit Clean Architecture programmieren wir nicht mehr fĂŒr die Frameworks, sondern können sie gezielt eher in der Funktionsweise von Libraries verwenden. DarĂŒber hinaus ist durch “Separation of Concerns” und die “Dependency Rule” eine einfache Testbarkeit unseres Codes gewĂ€hrleistet: Business-Regeln stehen alleine fĂŒr sich und können also auch eigenstĂ€ndig getestet werden, unabhĂ€ngig von UI, Datenbank oder sonstigen externen Komponenten. Äußere Schichten können sich zwar auf innere beziehen, diese AbhĂ€ngigkeiten können aber einfach “gemockt” werden, sodass auch hier das Testen einfacher fĂ€llt (im Vergleich zu einer Architektur, die den Code nicht in Schichten aufteilt und die “Dependency Rule” nicht einhĂ€lt).

Clean Architecture - TLDR

Clean-Architecture bezeichnet ein Architektur-Prinzip, bei dem Code Ă€hnlich wie bei einer Zwiebel in Schichten aufgeteilt wird. Technologie-unabhĂ€ngiger Code wird im Kern angesiedelt und weist keinerlei AbhĂ€ngigkeiten zu Ă€ußeren Schichten auf (“Dependency Rule”). Durch diese Trennung können Ă€ußere Schichten mit kurzlebigerem (da technologie-abhĂ€ngigerem) Programmcode ausgewechselt werden, ohne die Business Logik zu gefĂ€hrden. Gerade im schnelllebigen IT-Sektor ist dies von großer Bedeutung, um beim Absterben von Technologien mit moderatem Aufwand auf andere umzusteigen zu können. Auf die Zwiebel bezogen: wenn Ă€ußere Schichten faulen, kann der Kern noch wohlauf sein.

Eine schöne Übersicht zu Clean Architecture von Robert C. Martin ist hier zu finden.

Analyse der Dependency Rule

Eine Klasse, die die Dependency Rule einhÀlt und eine Klasse, die die Dependency Rule verletzt; jeweils UML der Klasse und Analyse der AbhÀngigkeiten in beide Richtungen (d.h., von wem hÀngt die Klasse ab und wer hÀngt von der Klasse ab) in Bezug auf die Dependency Rule

In den folgenden UML-Diagrammen werden zur besseren Übersichtlichkeit die fĂŒr die Analyse der Dependency Rule unnötigen Details ausgelassen, beispielsweise Methoden von anderen Klassen. AbhĂ€ngigkeiten werden ausschließlich ausgehend von der zentralen Klasse eingezeichnet, die gerade diskutiert wird.

1. Positiv-Beispiel

Dependency Rule 1. Positiv-Beispiel

Die Klasse Game befindet sich im Application-Layer. GemĂ€ĂŸ der Dependency Rule darf sie also nur AbhĂ€ngigkeiten zu inneren Schichten (Domain) haben. Wie im UML-Diagramm zu sehen, hĂ€ngt die Klasse von anderen Klassen in ihrer eigenen Schicht (z.B. von GamePlayer) als auch von Klassen aus der Domain-Schicht (z.B. von Deck) ab. Es gibt jedoch keinen AbhĂ€ngigkeitspfeil, der von Game in eine Klasse aus der Plugin-CLI-Schicht zeigt. Stattdessen wird Game von der Klasse Dominion in ihrer main()-Methode verwendet.

2. Positiv-Beispiel

Die von den Dozenten vorgegeben Projektstruktur mit vorpopulierten pom.xml-Dateien fĂŒr einzelne Maven-Module schließt ein Verletzen der Dependency Rule aus. Es ist schlichtweg nicht möglich, in einer inneren Schicht eine Klasse einer Ă€ußeren Schicht zu verwenden, da innere Schichten in dieser Projektstruktur nichts von Ă€ußeren wissen und dadurch auch Symbole, d.h. zum Beispiel Klassennamen, nicht aufgelöst werden können. Es kommt dann bereits beim Kompilieren zu Fehlern. Statt eines Negativ-Beispiels soll hier deshalb ein weiteres Positiv-Beispiel vorgestellt werden.

Dependency Rule 2. Positiv-Beispiel

Die abstrakte Klasse Move aus dem DomĂ€nen-Kern ist ausschließlich von Klassen innerhalb ihrer eigenen Schicht abhĂ€ngig (z.B. von der abstrakten Klasse Player). PlayerMove im Application-Layer erbt von Move und ĂŒberschreibt die mit protected versehenen Methoden. AbhĂ€ngigkeitspfeile zeigen auch in diesem Beispiel stets von außen nach innen, niemals von innen nach außen.

Analyse der Schichten

Jeweils eine Klasse zu zwei unterschiedlichen Schichten der Clean-Architecture: jeweils UML der Klasse (ggf. auch zusammenspielenden Klassen), Beschreibung der Aufgabe, Einordnung in die Clean-Architecture (mit BegrĂŒndung).

Action in der Domain-Schicht

Domain-Schicht Action

Die Klasse Action innerhalb der Domain stellt eine Art “Container” fĂŒr mehrere Anweisungen einer Karte zur VerfĂŒgung. Diese Anweisungen werden von dem Interface Instruction modelliert, wobei dieses im Wesentlichen nur eine execute(...)-Methode umfasst. Beim Spielen einer Karte fĂŒhrt der/die Spieler:in wĂ€hrend der Aktionsphase die Anweisungen “von oben nach unten” (wie auf der Karte aufgeschrieben) aus. Diese Reihenfolge wird durch eine einfache Liste von Instructions innerhalb der Klasse Action modelliert.

Die Klasse Action befindet sich in der DomĂ€nen-Schicht, da sie zur DomĂ€ne des Spiels Dominion zĂ€hlt: der Begriff “Anweisung” (Instruction) wird durchgehend in der Spielanleitung verwendet, außerdem wird von “Aktionskarten” geredet. Dementsprechend liegt es nahe, eine Klasse Action einzufĂŒhren, die die Instructions einer Karte zu einer Aktion bĂŒndelt, die dann ausgefĂŒhrt werden kann. Dass eine Aktion aus Anweisungen besteht ist eine Invariante, die fĂŒr jede Aktion berĂŒcksichtigt werden muss, auch dies begrĂŒndet die Position von Action im Domain-Code. Dass die Anweisungen “von oben nach unten” ausgefĂŒhrt werden, ist durch die Reihenfolge der Anweisungen in der Liste implizit gegeben. Die Klasse ÂŽAction wird vom CardPool` verwendet, um die Karten im Spiel aufzubauen.

CardFormatter in der Plugin-CLI-Schicht

Plugin-CLI-Schicht CardFormatter

Der CardFormatter befindet sich in der Plugin-CLI-Schicht und ist dafĂŒr zustĂ€ndig Karten fĂŒr den/die Benutzer:in ansprechend in der Konsole mithilfe von Unicode-Zeichen darzustellen, zum Beispiel so:

┏━━━━━━ KUPFER ━━━━━━┓
┃                    ┃
┃             💰💰💰 ┃
┃             💰1 💰 ┃
┃             💰💰💰 ┃
┃                    ┃
┃                    ┃
┃                    ┃
┃                    ┃
┃                    ┃
┃                    ┃
┃                    ┃
┗ 0💰 ━━ Geld ━━━━━━━┛

Um den Body der Karte zu “rendern” (im Fall der Geldkarte die “1” umgeben von GeldsĂ€cken), nutzt der CardFormatter eine konkrete Implementierung der abstrakten Klasse CardBodyFormatter, je nachdem von welchem Typ die Karte bei getFormatted(Card card) ist. In diesem Beispiel wird demnach der MoneyCardFormatter zum Einsatz kommen.

Die Plugin-CLI-Schicht wurde als Ort fĂŒr den CardFormatter gewĂ€hlt, da dieser lediglich fĂŒr die Anzeige von Karten, jedoch fĂŒr keinerlei Anwendungslogik zustĂ€ndig ist. Normalerweise greift diese Schicht nur auf den Adapter zu. Im Rahmen dieses Projekts wurde jedoch auf einen Adapter verzichtet, da dieser zur jetzigen Zeit keinen großen Mehrwert liefern und den Code nur unnötig aufblĂ€hen wĂŒrde. Stattdessen ruft der CardFormatter fĂŒr die Ausgabe direkt Methoden der Karte auf, zum Beispiel card.getName() oder card.getMoney().

Trotz des herausfordernden Codes, Karten mit Unicode in einem Grid anzuordnen und auszugeben, ist die emotionale Bindung an diesen Code sehr gering: der CardFormatter könnte jederzeit ausgetauscht werden, beispielsweise mit einem “richtigen” UI fĂŒr eine Web-Applikation. Die Business Logik und der Kern unserer Anwendung wĂ€re dadurch nicht betroffen; die innersten Schichten bekĂ€men von diesen Änderungen in der Tat ĂŒberhaupt nichts mit.