Inhalt

Die Clean Architecture ist eine von Robert C. Martin entwickelte Softwarearchitektur, welche die Unabhängigkeit der Businesslogik von Frameworks, Datenbanken, UI sowie andere externe Schnittstellen des Systems in den Vordergrund stellt.

Wir besprechen die einzelnen Bausteine der Clean Architecture, wie z.B. Entities, Use Cases, Presenter und deren streng geregelten Abhängigkeiten untereinander.

Dieser Post ist der vierte und letzte Teil einer Reihe von Blogposts, der das Buch „Clean Architecture“ von Robert C. Martin zusammenfasst und in den Kontext der App-Entwicklung einordnet.

Idee der Clean Architecture

Ringförmiger Aufbau der Clean Architecture

Aufbau der Clean Architecture, Bild von Robert C. Martin

Endlich wird es konkret. Nachdem wir uns in den vorherigen Posts mit den S.O.L.I.D.-Prinzipien, Softwarekomponenten und Softwarearchitektur & Boundaries beschäftigt haben, kommt hier endlich alles in einer Architektur zusammen.

Das Problem von Massive View Controllern – ein Anti-Pattern bei dem hunderte Zeilen lange UIViewController bzw. Activities entstehen – wird mit diesem Architekturansatz nicht auftreten. Die Clean Architecture setzt in hohem Maße auf Separation of Concerns. Jede Aufgabe, die in einer App ausgeführt werden muss, wird in einer eigenen Klasse realisiert. Wie in der Abbildung zu sehen, sind die einzelnen Aufgaben in Layer (die einzelnen Ringe) gekapselt zwischen denen strenge Abhängigkeitsregeln herrschen. Dies erleichtert die Testbarkeit, Austauschbarkeit und Erweiterbarkeit der Software.

Bausteine der Clean Architecture

Nehmen wir zunächst die einzelnen Bausteine der Architektur – von innen nach außen – auseinander:

  1. Entities

    Entities (gelber Ring) kapseln Businesslogik, die unternehmensweit Anwendung findet. Es handelt sich also um Klassen, welche die grundlegenden Daten und Funktionen eines Geschäfts beinhaltet.

    Stellen wir uns einen Online-Blumenhandel vor. Dieser bietet seinen Kunden eine App, in der sie Blumen bestellen können. Darüber hinaus gibt es ein Rechnungssystem für die interne Finanzverwaltung und ein Kassensystem für den stationären Handel.

    Alle diese Anwendungen haben verschiedene Aufgaben, jedoch gibt es auch Schnittmengen: Beispielsweise arbeiten alle Anwendungen mit einer virtuellen Repräsentation einer Blume und müssen deren Preis bestimmen. Die Blumen-Klasse ist daher ein Entity – ein grundlegendes Businessobjekt, das in allen Anwendungen des Unternehmens wiederverwendet werden kann. Entities sind Plain Old Java/Swift Objects, ohne Abhängigkeiten zu Frameworks.

    Entities beinhalten geschäftsweite Businesslogik.

    Die Entities können in verschiedenen Apps wiederverwendet werden.

  2. Use Cases

    Use Cases (roter Ring) beinhalten App-spezifische Businesslogik. Wir werden später noch sehen, dass Use Cases (zusammen mit Entities) die Anwendungsfälle der App in reinen, testbaren Code gießen ohne direkte Quellcode-Abhängigkeiten zu Frameworks, UI oder Datenbanken zu besitzen.

  3. Interface Adapter

    Objekte in diesem Layer (grüner Ring) bilden eine Schnittstelle zwischen der Businesslogik (Use Cases & Entities) und der Außenwelt (GUI, Services, Bluetooth-Geräte o.ä.). Sie transformieren Daten zwischen dem Format, das am besten für den Use Cases geeignet ist und dem Format, mit der die externe Schnittstelle arbeitet. Wenn ein Use Case beispielsweise ein Datenobjekt in einer Datenbank speichern möchte, transformiert ein Interface Adapter das Objekt in ein SQL-Query für die Datenbank.

    Ein weitere geläufiger Interface Adapter in der Clean Architecture ist der Presenter. Die Aufgabe des Presenters ist die Aufbereitung von Geschäftsdaten für die View. Beispielsweise transformiert der Presenter ein Date-Objekt in einen String, welcher ohne weiteres Zutun der View in einem Label angezeigt werden kann.

  4. Frameworks & Driver

    Der äußerste Layer (blauer Ring) beinhaltet das (UI-)Framework und Tools wie die Datenbank oder z.B. Sensor-APIs. Der Code in diesem Layer dient in erster Linie der Konfiguration dieser Tools und der Anbindung an die inneren Layer.

Abhängigkeiten

Quellcode-Abhängigkeiten müssen immer nach innen zeigen.

Anmerkung: „innen“ und „außen“ beziehen sich immer auf die Abbildung am Anfang des Artikels.

Das ist die goldene Regel der Clean Architecture. Beispielsweise darf ein Entity höchsten Abhängigkeiten zu anderen Entities aufweisen, jedoch nicht zu einem Use Case oder Objekten, die noch weiter außen angesiedelt sind. Es sollten auch keine Objekte aus den äußeren Layern als Funktionsargumente in einen inneren Layer übergeben werden.

Auch wenn es keine Quellcodeabhängigkeiten von innen nach außen geben darf, gibt es doch häufig den Fall, dass der Kontrollfluss eines Programms von innen nach außen zeigt.

Beispiel

Nehmen wir ein Beispiel aus der Blumen-App: Durch einen Button-Klick möchte der Nutzer eine Blume aus seinem Warenkorb entfernen.

Der Kontrollfluss geht auch von innen nach außen.

Typischer Kontrollfluss einer App

Der Ablauf in der Clean Architecture sieht wie folgt aus:

  1. Der Nutzer löst im UI (blau = äußerster Layer) einen Button-Klick aus.
  2. Der Controller (grün = Interface Adapter) fungiert als Listener/Observer/Delegate für das UI. Er ist für Übersetzung des UI-Events in ein Request-Model für den Use Case zuständig. Das UI-Event beinhaltet beispielsweise lediglich den Index der zu löschenden Zeile in einer Liste. Da der Use Case jedoch losgelöst vom UI agiert, kann er mit dieser Information wenig anfangen. Der Controller transformiert daher den Zeilen-Index in eine Blumen-Id, z.B. durch einen Lookup.
  3. Der Use Case (rot = Business Rules) übernimmt die eigentliche Businesslogik des Löschens. Wie genau er das macht ignorieren wir an dieser Stelle. Er wird jedoch anschließend eine neue, um eine Blume reduzierte, Repräsentation des Warenkorbs an den Presenter übergeben.
  4. Der Presenter (grün = Interface Adapter) erzeugt aus dem Warenkorb-Objekt ein ViewModel. Ein ViewModel ist ein einfaches Datenobjekt, das den State einer View durch einfache Datentypen (hauptsächlich Strings und Booleans) repräsentiert.
  5. Das UI aktualisiert sich mit den Daten aus dem ViewModel.

Wir sehen, dass der Kontrollfluss zu Beginn zunächst von außen nach innen läuft, nämlich vom UI zum Use Case. Da der Use Case aber auch eine Aktualisierung des UIs auslöst, gibt es anschließend auch einen Flow von innen zurück nach außen.

Wie wird in diesem Fall die Verletzung der goldenen Abhängigkeitsregel verhindert?

Beachte die Richtung der Pfeile: Durch Dependency Inversion wird die Abhängigkeitsregel eingehalten.

Wir erinnern uns: Durch Dependency Inversion können der Kontrollfluss und die Richtung der Quellcodeabhängigkeiten in entgegengesetzte Richtungen zeigen. Wer nicht weiß was Dependency Inversion ist, dem empfehle ich das Nachlesen in Teil 1 dieser Blogreihe.

Bessere Testbarkeit dank des Humble-Object-Pattern

Einer der großen Vorteile der Clean Architecture ist die einfache Testbarkeit. Die Separation of Concerns, die durch die Aufteilung von Funktionalität auf die verschiedenen Layer herbeigeführt wird, führt uns auch automatisch zum Humble-Object-Pattern. Das Humble-Object-Pattern trennt schwer zu testendes Verhalten von leicht zu testendem Verhalten. Der schwierige Teil wird auf seine minimale Essenz heruntergebrochen und in ein sogenanntes. Humble-Objekt (deutsch: demütig, bescheiden) verlagert. Das übrige Verhalten ist nun leichter zu testen und verbleibt in einem anderen Objekt.

Beispiel: Das UI ist meist schwierig zu testen, da ein automatisches Testprogramm die korrekte Darstellung eines Screens nur begrenzt überprüfen kann. Daher trennen wir leicht zu testendes Verhalten von schwer zu testendem Verhalten, indem wir die Aufbereitung von Daten für eine View dem Presenter überlassen. Dieser erzeugt hauptsächlich Strings und Booleans, welche die Eigenschaften der View beschreiben und sehr einfach zu testen sind. Die eigentliche View wird auf die Definition der UI-Elemente reduziert und muss lediglich die Attribute des Presenters setzen.

Weitere Humble-Objects

Das Humble-Object-Pattern begegnet uns auch an vielen anderen Stellen in der Clean Architecture:

  • Datenbank Gateway: Ein Use Case operiert nicht selbst direkt mit SQL-Statements auf der Datenbank, sondern delegiert die schwer zu testendende SQL-Queries an ein Datenbank Gateway (ein Humble-Object). Auf diese Weise bleibt der Use Case leicht testbar.
  • Service Listener: Häufig verwenden wir externe Services, konsumieren beispielsweise einen Webservice über HTTP. Anstatt selbst Daten zu externen Services zu schicken und Daten von ihnen zu empfangen verwendet der Use Case einen Service Listener, der sich beispielsweise um die Serialisierung und Deserialisierung von Objekten kümmert. Beim Unit Testing kann der Service Listener leicht durch ein Mock-Objekt ausgetauscht werden, anstatt den gesamten Service (z.B. den Webserver) zu simulieren.

Zusammenfassung

Die Clean Architecture zeigt wie die SOLID-Prinzipien und das Konzept der Boundaries praktisch in einer Architektur realisiert werden können. Die Architektur ist in verschiedene Layer aufgeteilt:

  • Im Innersten sind die sogenannten Entities angeordnet: Einfache Objekte, welche die grundlegende, applikationsübergreifende Businesslogik enthalten.
  • Use Cases enthalten die applikationsspezifische Geschäftslogik.
  • Interface Adapter übersetzen Daten zwischen den Use Cases und externen Schnittstellen wie dem UI oder der Datenbank.
  • Frameworks und Driver bilden den äußerten Layer, der tunlichst keine Geschäftslogik mehr enthält.

Besonders wichtig ist die Richtung der Quellcodeabhängigkeit. Sie zeigt von außen nach innen, sodass die Businesslogik im Inneren nicht durch Implementierungsdetails ‚verunreinigt‘ (und damit unübersichtlicher und schwieriger zu testen) wird. Wenn eine Klasse im inneren einen Effekt in einem der äußeren Layer erzeugen möchte, so wird stets das Konzept der Dependency Inversion angewendet, d.h. die Funktionen der äußeren Layer werden durch Interfaces abstrahiert.

Die fehlenden Kapitel

Dies war der letzte Teil der Buchzusammenfassung „Clean Architecture“ von Robert C. Martin. Natürlich konnten nicht alle Kapitel des Buches berücksichtigt werden. Im Buch werden viele der hier nur grob angerissenen Konzepte noch weiter vertieft (z.B. Boundaries, Details und Case Studies). Wer ihn kennt der weiß, Uncle Bob plaudert viel aus dem Nähkästchen und erläutert auch in diesem Buch viele Beispiele und historische Gegebenheiten recht ausufernd. Ich möchte dennoch jedem Leser empfehlen das Buch selbst auch zu lesen um das hier erlangte Wissen zu vertiefen.

Beitragsbild: unsplash-logoJason Cooper


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.