Auf einen Blick
- Die Logische Sicht beschreibt die Aufteilung des Codes in Module und die Abhaengigkeiten zwischen den Modulen; Kernanwendungsfaelle und Modellierung stammen aus dem letzten Semester.
- Modularisierung erfolgt in zwei Dimensionen: PRIMAER nach fachlicher Zustaendigkeit (Komponenten), SEKUNDAER nach Art der Aufgabe (Schichten: Infrastruktur, Applikation, Domaene).
- Die drei Schichten haben klare Zustaendigkeiten: Domaene = fachliches Modell der realen Welt (Kern), Applikation = Automatisierung/Ablaufsteuerung, Infrastruktur = technologieabhaengiger Code.
- Abhaengigkeiten folgen dem Dependency Inversion Principle (DIP): die Domaene hat die hoechste Abstraktionsebene und haengt von nichts ab; Details haengen von Abstraktionen ab.
- Das Hexagonale Modell (Ports und Adapter, Alistair Cockburn) ist eine andere Darstellung des Schichtenmodells und teilt Code zusaetzlich nach Programmflussrichtung in Inbound und Outbound.
- Durchgaengiges Beispiel ist NGHR (HR-/Gehaltsabrechnungssystem) mit den Komponenten Gehaltsabrechnung, Mitarbeiter, Berechtigungen und Allgemeines.
Kernkonzepte
1. Einordnung der Logischen Sicht in das Sichtenmodell
Die Software-Architektur wird ueber mehrere Sichten (Sichten) beschrieben. Geschaeftssicht: Geschaeftsprozesse, Geschaeftsobjekte, Akteure, Organisation, fachliche Modelle und Regeln. Kontextsicht: Akteure, andere Systeme, Datenfluesse. Logische Sicht: Kernanwendungsfaelle, Modellierung, Aufteilung in Module, Modulabhaengigkeiten. Entwicklungssicht: Werkzeuge, Entwicklungsrichtlinien, Verwaltung des Codes, Konfiguration, Bau der Anwendung. Datensicht: technisches Datenmodell, Datenaustausch/Integration. Verteilungs- und Laufzeitsicht: Verteilung auf Infrastruktur, Hintergrundverarbeitung, Performanz und Skalierung, Betrieb. Inhalt DIESES Kapitels ist speziell die Aufteilung des Codes in Module und die Abhaengigkeiten zwischen den Modulen. Nebenbemerkung: Validierung heisst baue ich das richtige?, Verifikation heisst baue ich es richtig?
2. Modularisierung in zwei Dimensionen
Fuer betriebswirtschaftliche Anwendungen wird in zwei Dimensionen modularisiert. Primaer nach fachlicher Zustaendigkeit, das ergibt Komponenten: Zustaendigkeit fuer eng zusammenhaengende Geschaeftsobjekte in einer Komponente; querschnittliche Komponenten nach Bedarf (Berechtigungen, Administration, allgemein wiederverwendbarer Code). Sekundaer nach Art der Aufgabe, das ergibt Schichten: Infrastruktur (Anbindung an die Infrastruktur, dieser und nur dieser Code ist technologieabhaengig), Applikation (Automatisierung und Ablaufsteuerung), Domaene (fachliches Verhalten, fachliches Wissen und fachliche Regeln).
3. Komponenten als primaere Dimension - Begruendung
Die Aufteilung in Komponenten und Schichten ist das Single Responsibility Principle (SRP) auf Modul-Ebene: Komponenten tragen fachliche Verantwortlichkeit, Schichten die Verantwortlichkeit fuer die Art der Aufgabe. Komponenten sind primaer, weil der primaere Zweck einer Software das Erfuellen fachlicher Anforderungen ist: die meisten Aenderungen sind auf eine oder wenige Komponenten beschraenkt, die meisten Ablaeufe ebenfalls, aber Aenderungen und Ablaeufe erstrecken sich oft ueber mehr als eine Schicht. Folgerung: die Schichten gehoeren enger zusammen als die Komponenten untereinander; wenn Komponenten die primaere Dimension sind, reduziert sich die Anzahl der Abhaengigkeiten zwischen den Modulen. Die generische Aufteilung in Infrastruktur, Applikation und Domaene gilt fuer eine typische Unternehmensanwendung, ist aber nicht auf jede Softwareart anwendbar (z.B. Compiler).
4. Abbildung im Quellcode als Pakethierarchie (NGHR)
Komponenten und Schichten werden auf eine Pakethierarchie abgebildet. Beispiel NGHR: Gehaltsabrechnung = de.dhbw.nghr.payroll, Mitarbeiter = de.dhbw.nghr.employee, Berechtigungen = de.dhbw.nghr.auth, Allgemeines = de.dhbw.nghr.common. Jede Komponente enthaelt die drei Schichten als Unterpakete: *.infra (Infrastructure), *.application (Application), *.domain (Domain).
5. Kapselung und Schutz der Geschaeftsobjekte
Komponenten geben ihre innere Struktur nicht preis (Kapselung, analog zu Klassen); relevante Prinzipien sind das Gesetz von Demeter und das Entwurfsmuster Facade. Geschaeftsobjekte gehoeren genau einer Komponente; nur diese darf sie erzeugen, aendern und loeschen. Komponenten geben ihre Geschaeftsobjekte nicht heraus, sonst koennte eine fremde Komponente diese aendern und die besitzende Komponente wuerde ihrer Verantwortung fuer das Geschaeftsobjekt nicht gerecht. Fuer den Datenaustausch werden benoetigte Daten in andere Klassen verpackt: Immutable Object (nach Erzeugung nicht mehr aenderbar - beste Loesung, da nichts schiefgehen kann) oder Data Transfer Object (aenderbares Objekt zum Transport von Daten).
6. Regeln fuer Abhaengigkeiten zwischen Komponenten
Abhaengigkeiten minimieren durch maximale Kohaesion und minimale Kopplung: alles mit vielen Abhaengigkeiten untereinander (maximale Kohaesion) in ein Modul packen reduziert die Abhaengigkeiten zwischen den Modulen (minimale Kopplung). Keine zyklischen Abhaengigkeiten (Gruende: Analysierbarkeit, Testbarkeit, Aenderbarkeit). Codeabhaengigkeiten folgen den fachlichen Abhaengigkeiten - Beispiel: Gehaltsabrechnung ist ohne das Konzept Mitarbeiter nicht denkbar, also haengt Gehaltsabrechnung von Mitarbeiter ab; DIP verwenden, um Codeabhaengigkeiten in die richtige Richtung zu bringen. Jedem Client seine eigene Schnittstelle (ISP). Prinzip der losen Kopplung: Schnittstellen verwenden, ereignisbasierte Kommunikation, asynchrone Kommunikation.
7. Exkurs: Prinzip der losen Kopplung
Schnittstellen verwenden bringt gute Wartbarkeit (OO-Designprinzipien OCP, ISP, DIP) und Testbarkeit (Isolierung des zu testenden Codes). Events statt Methodenaufrufe: Kommunikation ohne direkte Kopplung, weil Erzeuger und Empfaenger von Events sich nicht kennen; sehr gute Wartbarkeit; perfekte Isolierung des zu testenden Codes; Flexibilitaet zur Laufzeit (synchron/asynchron, lokal/remote, Queues). Asynchrone Verarbeitung bevorzugen: Performanz (reduziert Antwortzeit fuer Anwender), Skalierbarkeit (Verarbeitung in Queues vermeidet Ueberlast, Verarbeitung in anderer Laufzeit), Robustheit (der synchrone Teil funktioniert auch bei Fehlschlag des asynchronen Teils).
8. Schnittstelle zwischen Komponenten - richtig und falsch (NGHR)
Die Komponente Mitarbeiter stellt der Komponente Gehaltsabrechnung Mitarbeiterdaten zur Verfuegung. Falsch: die Schnittstelle ist nicht clientspezifisch (z.B. eine generische EmployeeUseCases-Schnittstelle) und gibt ein Geschaeftsobjekt als Rueckgabewert zurueck. Richtig: die Schnittstelle ist clientspezifisch (ISP) und liefert ein Immutable oder ein DTO als Rueckgabewert - kein Geschaeftsobjekt.
9. Komponenten zur Entwicklungs- und zur Laufzeit
Getrennter Build-Prozess pro Komponente: jede Komponente kann getrennt kompiliert werden, das reduziert die Dauer fuer den Build. Komponenten getrennt deployen: jede in eigener jar-Datei (Java) / Assembly (.NET); das heisst nicht, dass sie fuer sich lauffaehig ist; wenn sich eine Komponente aendert, sollte man nur diese austauschen muessen (je nach Technologie auch im laufenden Betrieb). Komponente fuer sich ausfuehrbar: Monolith (alle Komponenten in einer Laufzeitumgebung) versus Verteilt/Micro-Service (jede Komponente in einer oder mehreren eigenen Laufzeiten). Weiteres Argument fuer Komponenten als primaere Dimension: es wuerde keinen Sinn machen, Infrastruktur, Applikation und Domaene in eigene Laufzeitumgebungen zu packen. NGHR-Beispiel: aus den Paketen entstehen jar-Ziele (NGHR-Payroll.jar usw.), die entweder zu einem Monolith oder zu einzelnen Micro-Services (Employee-, Payroll-, Authorization-Service, jeweils plus NGHR-Common.jar) gebaut werden.
10. Vorgehen und Aufgaben der Architekten
Komponenten identifizieren: Name, Aufgabe und zugehoerige Geschaeftsobjekte der Komponente. Abhaengigkeiten zwischen den Komponenten festlegen: nach fachlichen Gesichtspunkten, keine Zyklen (bidirektionale Abhaengigkeiten eingeschlossen). Abbildung in den Code: auf Paketstruktur, auf Build- und Deployment-Prozess, auf die Ausfuehrungs- und Verteilungssicht. Dokumentieren und vereinbaren. Ueberpruefen durch statische Codechecks und Code Review.
11. Die drei Schichten und ihre Zustaendigkeiten
Infrastrukturschicht: Code, der von verwendeten Technologien abhaengt. Applikationsschicht: Automatisierung und Steuerung von Ablaeufen. Domaenenschicht: das Modell des fachlichen Problems der realen Welt. Entscheidendes Kriterium fuer die Applikationsschicht: dieser Code existiert nicht in der realen Welt, sondern nur, weil wir ein IT-System haben.
12. Domaenenschicht als Kern der Anwendung
Die Domaenenschicht ist der Code, der das fachliche Problem der realen Welt modelliert. Alles was hier ist, gibt es in der realen Welt auch ohne ein IT-System; ausgedrueckt wird das Modell durch fachliches Wissen, Regeln und Verhalten. Die Domaene ist der Kern der Anwendung: mit ihr faengt man an (alles andere ist sekundaer), sie stiftet den Grossteil des eigentlichen Nutzens, sie aendert sich oft (weil sich die Welt und damit die Anforderungen aendern), und sie richtig hinzubekommen ist schwierig (hohe Komplexitaet, IT-Experten und Fachexperten muessen zusammenfinden, in jedem Projekt anders - man faengt immer von vorne an). Beispiel NGHR Mitarbeiter einstellen: Domaenenaufgabe = Methode zum Erzeugen einer neuen Instanz des Geschaeftsobjektes Mitarbeiter im Speicher (Input: Mitarbeiterdaten, Output: neues Geschaeftsobjekt mit neuer Personalnummer), Validierungslogik bereitstellen (nicht anstossen!), Geschaeftsobjekt vollstaendig erzeugen mit Lebenszyklusstatus Vertrag geschlossen und Einhaltung aller Invarianten, Ereignis ueber Anlage publizieren.
13. Applikationsschicht - duenn und koordinierend
Die Applikationsschicht enthaelt Code zur Automatisierung und Steuerung von Ablaeufen; diese Logik existiert nicht ausserhalb der Anwendung, sondern nur weil wir eine Anwendung bauen, die Ablaeufe automatisiert; sie ist nicht technologiespezifisch. Aufgaben: Anwendungsfaelle (die Methoden, die einen Use Case repraesentieren), Validierung anstossen und organisieren (aber NICHT die fachlichen Validierungsregeln), Koordination innerhalb und zwischen Komponenten, Ausnahmebehandlung, querschnittliche Aspekte (Berechtigungspruefung, Sperrmechanismus, Transaktionssteuerung, Protokollierung). Ausgestaltung duenn: keine komplexe Logik (Bedingungen, Schleifen), Methoden enthalten meist eine lineare Abfolge von Befehlen. NGHR-Beispiel: Berechtigungspruefung veranlassen, Validierung anstossen und an die Domaene delegieren, Personalnummer von Infrastruktur erzeugen lassen, Mitarbeiteranlage an die Domaene delegieren, Speichern in der Infrastruktur ausloesen, Nachrichten an I&B und Controlling ausloesen, Transaktionssteuerung.
14. Infrastrukturschicht - der technologieabhaengige Code
Enthaelt den technologieabhaengigen Code: Datenformate (XML, JSON, CSV, PDF), Persistenzmechanismen (SQL, JDBC, JPA), Netzwerkprotokolle (HTTP, Web Sockets, FTP), Messaging (E-Mail, SMS, X/Twitter). Anbindung an externe Systeme und Ressourcen ueber Adapter (Entwurfsmuster Adapter: passt eine Schnittstelle an eine von den Clients erwartete Schnittstelle an). Interne Mechanismen: Authentifizierung, Transaktionen, Sperren, Protokollierung (der Mechanismus lebt in der Infrastruktur, die Steuerung in der Applikation), Ressourcenverwaltung (Speicher, Prozesse, Threads). Bibliotheken und Frameworks: der Grossteil der Infrastruktur wird nicht vom Anwendungsentwickler geschrieben (Webserver, JEE/Spring, HTTP-Client, XML/JSON-Konverter); oft ist ein Adapter zu den verwendeten Frameworks der einzige Code, den Anwendungsentwickler schreiben. NGHR-Beispiel: HTTP-Endpunkt (Methode an URL binden), SSO-Authentifizierung, HTTP-Request entgegennehmen und Payload in Objekte konvertieren, SQL INSERT, HTTP-Response, Anwendungsausnahmen in HTTP-Status-Codes uebersetzen.
15. Abhaengigkeiten der Schichten und das DIP
Dependency Inversion Principle (DIP): Module auf hoeheren Ebenen sollen nicht von Modulen auf niedrigeren Ebenen abhaengen; Abstraktionen sollen nicht von Details abhaengen, Details sollen von Abstraktionen abhaengen. Abstraktionsebenen: die Domaenenschicht hat die hoechste Abstraktionsebene (das fachliche Problem ist unabhaengig von der Existenz einer Anwendung oder Infrastruktur; sie haengt nach DIP von keiner anderen Schicht ab), die Applikationsschicht die mittlere (Steuerung ist unabhaengig von der Art der Infrastruktur; sie dient den Beduerfnissen der Domaene), die Infrastrukturschicht die niedrigste (dient den technischen Beduerfnissen von Anwendungs- und Domaenenschicht). Wichtig: die richtige Variante ordnet die Domaene ganz oben an (Infra unten, App Mitte, Domain oben), aber die intuitiven Begruendungen (Requests treffen zuerst die Infrastruktur / Applikation koordiniert / Domaene hat das Sagen) waren allesamt keine korrekte Begruendung - nur das DIP begruendet die erlaubten Abhaengigkeiten korrekt. Ebenen duerfen uebersprungen werden.
16. Motivation DIP ueber Aenderungsszenarien (NGHR)
Die Wartbarkeit motiviert das DIP; Aenderungen auf Detailebene duerfen nicht auf hoehere Ebenen durchschlagen. Szenario 1
- Gehaltsabrechnung statt PDF als JSON bereitstellen: keine Aenderung in Domaene und Applikation, Aenderung nur in der Infrastruktur (dort findet die Konvertierung statt). Szenario 2
- Aenderungen des Mitarbeiterlebenszyklus zusaetzlich ans Einkaufssystem weiterleiten: keine Aenderung in der Domaene, Aenderung in Applikation (neuer Schritt in der Ablaufsteuerung) und Infrastruktur (neue Schnittstelle). Szenario 3
- Mitarbeiter muessen immer einer Organisation in der Unternehmenshierarchie zugeordnet werden: Aenderung in allen dreien
- Domaene (neue Regel und neue Beziehung zwischen Geschaeftsobjekten), Applikation (neuer Anwendungsfall Mitarbeiter einer Organisation zuordnen), Infrastruktur (neuer HTTP-Endpunkt).
17. Programmfluss und Codeabhaengigkeit (NGHR Mitarbeiter einstellen)
Typischer Programmfluss: Infrastruktur
- Applikation
- Domaene
- Applikation
- Infrastruktur. Schritte: 1. HTTP-Request zur Einstellung; 2. HTTP-Endpunkt (HREmployeeHTTPController.hireEmployee) delegiert an den Use Case; 3. Use Case (HREmployeeUseCase.hireEmployee) ruft Methode zum Validieren und Einstellen auf; 4. neues, vollstaendig initialisiertes Mitarbeiterobjekt (Employee.hireEmployee) wird zurueckgegeben; 5. Use Case ruft Mitarbeiter speichern im Repository auf
- anders als die Codeabhaengigkeit (DIP!): das Applikations-Interface EmployeeRepository wird vom Infrastruktur-EmployeeJpaRepository implementiert; 6. Repository erzeugt neuen Eintrag in der Datenbank. Der Programmfluss geht also von aussen nach innen und wieder nach aussen, waehrend die Codeabhaengigkeit beim Persistenzaufruf durch DIP umgedreht ist.
18. Hexagonales Modell (Ports und Adapter)
Eine andere Sicht auf das Schichtmodell (Alistair Cockburn, auch Ports-und-Adapter-Modell). Motivation: der Programmfluss geht immer von aussen nach innen und dann wieder nach aussen, und die Domaene ist der Kern. Das legt eine Darstellung als Schalen oder Hexagons nahe (aussen Infrastruktur, dann Applikation, innen Domaene). Diese Darstellung betont die Domaene als geschuetzten Kern; es gibt KEINE Aenderung der Zustaendigkeiten und der Abhaengigkeitsregeln - egal ob man in Schichten, Schalen oder Hexagons veranschaulicht.
19. Inbound und Outbound
Der Code wird zusaetzlich nach Richtung des Programmflusses getrennt. Inbound: Code, der von extern aufgerufen wird (getrieben). Outbound: Code, der nach extern aufruft (treibend). Begruendung der Trennung: SRP (Code ist entweder Client/Outbound oder Service/Inbound
- z.B. HTTP-Service versus HTTP-Client); Reduktion von Abhaengigkeiten (Code mit Inbound- und Outbound-Rolle haette mehr Abhaengigkeiten pro Codeeinheit); Vermeidung zyklischer Abhaengigkeiten (rufen sich zwei Anwendungen ueber HTTP gegenseitig auf, gibt es einen Zyklus auf Protokollebene
- die Trennung verhindert, dass dieser Zyklus auf das Innere der Anwendung durchschlaegt); einfaches Prinzip, um Ordnung und Verstaendlichkeit zu schaffen.
20. Adapter und Ports - Namensgebung
Ports sind die Objekte des Applikationshexagons; Adapter sind die Objekte des Infrastrukturhexagons (sie adaptieren eine Schnittstelle auf eine andere - Entwurfsmuster Adapter). Applikationshexagon hat Inbound Ports (Use Case) und Outbound Ports (Benachrichtigungen versenden, Persistenz aufrufen). Infrastrukturhexagon hat Inbound Adapter (HTTP-Endpunkt) und Outbound Adapter (HTTP-Client, Datenbankzugriff). Das ist ein generisches Gestaltungsprinzip fuer Applikation und Infrastruktur.
21. Referenzarchitektur der Logischen Sicht
Referenzarchitektur einer Unternehmensanwendung: mehrere Komponenten (z.B. Komponente A = rootpackage.a, Komponente B = rootpackage.b), jede mit den drei Schichten Infrastructure (*.infra, mit Inbound Adapter und Outbound Adapter), Application (*.application, mit Inbound Port und Outbound Port) und Domain (*.domain, mit den Domaenenobjekten). Die Codeabhaengigkeiten laufen dabei gemaess DIP nach innen zur Domaene.
22. Benutzerschnittstelle
Enthaelt die Praesentationslogik: grafische Elemente oder Kommandozeile, Navigation, Benutzereingaben und Reaktion darauf. Die Struktur der Benutzerschnittstelle beeinflusst die Struktur der Applikationsschicht (ISP: die Schnittstellen gehoeren dem Client, hier der Benutzerschnittstelle - Beispiel: Anlage eines Mitarbeiters in einem oder in drei Schritten?). Regeln: enthaelt idealerweise nichts, was in Applikation oder Domaene gehoert (User Experience erfordert mitunter Duplikation von Applikations- oder Domaenenlogik im UI); haengt von der Infrastruktur nur fuer UI-spezifischen Code ab, sonst von keiner Infrastruktur; fuer automatisierte Tests in Applikation und Domaene ist die Benutzerschnittstelle nicht noetig. Betrachte die Benutzerschnittstelle als eigene Anwendung mit eigener logischer Architektur. Strukturierungsmoeglichkeiten: komponenten-/geschaeftsobjektbasiert, prozessschrittorientiert, komponentenbasiert mit Navigation, rollenbasiert.
Merksätze
Klausur-Stichworte (aus der Zusammenfassung)
Theißen hat gesagt: alle prüfbaren Themen tauchen als Stichwort in den Zusammenfassungs-Folien auf. Genau diese Stichworte stehen hier.
- Referenzarchitektur einer Unternehmensanwendung: Komponente A/B mit Infrastructure (*.infra, Inbound/Outbound Adapter), Application (*.application, Inbound/Outbound Port), Domain (*.domain), Codeabhaengigkeit nach innen
- Unverzichtbar: Aufteilung in Komponenten (sonst gar keine Struktur im Code)
- Unverzichtbar: Trennung von technischem Code und fachlichem Code (Technologieabhaengiger Code nach Technologien, fachlicher Code nach fachlichen Gesichtspunkten - beides zusammen geht nicht)
- Unverzichtbar: Lose Kopplung (mindestens durch Schnittstellen; Ereignisse und asynchrone Mechanismen bei Bedarf)
- Unverzichtbar: Managen der Abhaengigkeiten (Definieren, Dokumentieren, Umsetzen; Qualitaetssicherung - der Compiler macht das in der Regel nicht)
- Optional: Aufteilung in Domaene, Applikation und Infrastruktur
- Optional: Verwendung des Hexagonalen Modells - Ports und Adapter
- Komponenten sind die primaere Struktur: nach Fachlichkeit geschnitten (SRP), Abhaengigkeiten folgen den fachlichen Abhaengigkeiten, Kapselung, keine Exposition von Geschaeftsobjekten, optional getrennt kompilieren/deployen und verteilbar auf mehrere Laufzeiten
- Schichten sind die sekundaere Struktur: Domaene = Modell der realen Welt und Kern; Applikation = nicht technologieabhaengig und ohne Anwendung nicht existent; Infrastruktur = technologieabhaengig; Abhaengigkeiten nach DIP - die Domaene haengt von nichts ab
- Abhaengigkeiten: Komplexitaet beherrscht man nur, wenn man die Abhaengigkeiten beherrscht; Abhaengigkeitsregeln zwischen Komponenten und Schichten/Hexagons sind wesentlich; lose Kopplung durch Schnittstellen, Ereignisse, asynchrone Verarbeitung; DIP als Prinzip und Instrument fuer die Richtung der Abhaengigkeiten
- Hexagonales Modell: andere Darstellung des Schichtenmodells; Organisation von Applikations- und Infrastrukturhexagon nach Programmfluss - Inbound/Outbound Ports im Applikationshexagon, Inbound/Outbound Adapter im Infrastrukturhexagon
- Benutzerschnittstelle: enthaelt nur die Praesentationslogik (grafische Elemente, Anzeigen, Eingaben, Navigation); als wichtige externe Anwendung betrachten; nach den Beduerfnissen der Anwender schneiden, nicht nach den Komponenten im Backend
Übungsfragen
F1 Nach welchen zwei Dimensionen wird eine betriebswirtschaftliche Anwendung modularisiert, und welche ist die primaere?
Primaer nach fachlicher Zustaendigkeit
- das ergibt die Komponenten (eng zusammenhaengende Geschaeftsobjekte, plus querschnittliche Komponenten wie Berechtigungen). Sekundaer nach Art der Aufgabe
- das ergibt die Schichten Infrastruktur, Applikation und Domaene. Komponenten sind die primaere Dimension, weil der primaere Zweck einer Software das Erfuellen fachlicher Anforderungen ist und die meisten Aenderungen und Ablaeufe auf wenige Komponenten beschraenkt bleiben.
F2 Warum sind die Komponenten und nicht die Schichten die primaere Dimension der Modularisierung?
Weil die meisten Aenderungen und die meisten Ablaeufe auf eine oder wenige Komponenten beschraenkt sind, sich aber oft ueber mehr als eine Schicht erstrecken. Die Schichten gehoeren daher enger zusammen als die Komponenten untereinander. Wenn Komponenten die primaere Dimension sind, reduziert sich die Anzahl der Abhaengigkeiten zwischen den Modulen. Ein weiteres Argument: es wuerde keinen Sinn ergeben, Infrastruktur, Applikation und Domaene in eigene Laufzeitumgebungen zu packen.
F3 Was unterscheidet die Applikationsschicht von der Domaenenschicht?
Die Domaenenschicht modelliert das fachliche Problem der realen Welt - alles darin gibt es auch ohne ein IT-System. Die Applikationsschicht enthaelt Code zur Automatisierung und Steuerung von Ablaeufen, der nicht in der realen Welt existiert, sondern nur weil wir ein IT-System bauen. Das entscheidende Kriterium fuer die Applikationsschicht lautet: existiert nicht in der realen Welt, sondern nur, weil wir ein IT-System haben. Beide sind nicht technologiespezifisch.
F4 Warum soll die Applikationsschicht duenn gehalten werden, und welche Aufgaben hat sie?
Sie soll duenn sein, weil sie keine komplexe Logik (Bedingungen, Schleifen) enthalten soll; ihre Methoden bestehen meist aus einer linearen Abfolge von Befehlen. Ihre Aufgaben sind: Anwendungsfaelle repraesentieren, Validierung anstossen und organisieren (aber nicht die fachlichen Validierungsregeln selbst - die liegen in der Domaene), Koordination innerhalb und zwischen Komponenten, Ausnahmebehandlung sowie querschnittliche Aspekte wie Berechtigungspruefung, Sperren, Transaktionssteuerung und Protokollierung.
F5 Was besagt das Dependency Inversion Principle (DIP) und welche Schicht steht dabei auf der hoechsten Abstraktionsebene?
Das DIP besagt: Module auf hoeheren Ebenen sollen nicht von Modulen auf niedrigeren Ebenen abhaengen; Abstraktionen sollen nicht von Details abhaengen, sondern Details von Abstraktionen. Die Domaenenschicht steht auf der hoechsten Abstraktionsebene, weil das fachliche Problem unabhaengig von der Existenz einer Anwendung oder Infrastruktur ist; nach DIP haengt die Domaene von keiner anderen Schicht ab. Die Applikation liegt in der Mitte, die Infrastruktur auf der niedrigsten Ebene.
F6 Warum werden Geschaeftsobjekte nicht an andere Komponenten herausgegeben, und was gibt man stattdessen zurueck?
Ein Geschaeftsobjekt gehoert genau einer Komponente, die allein es erzeugen, aendern und loeschen darf. Wuerde man es herausgeben, koennte eine fremde Komponente es aendern, und die besitzende Komponente koennte ihrer Verantwortung fuer das Objekt nicht mehr gerecht werden. Stattdessen werden die benoetigten Daten in ein Immutable Object (nach Erzeugung nicht mehr aenderbar, die beste Loesung) oder ein Data Transfer Object (DTO) verpackt.
F7 Was bedeuten Inbound und Outbound im Hexagonalen Modell, und warum trennt man den Code danach?
Inbound ist Code, der von extern aufgerufen wird (getrieben, z.B. ein HTTP-Endpunkt/Service); Outbound ist Code, der nach extern aufruft (treibend, z.B. ein HTTP-Client oder Datenbankzugriff). Man trennt danach wegen des SRP (Code ist entweder Client oder Service), zur Reduktion von Abhaengigkeiten pro Codeeinheit, zur Vermeidung zyklischer Abhaengigkeiten (ein Zyklus auf Protokollebene schlaegt dann nicht auf das Innere der Anwendung durch) und als einfaches Ordnungsprinzip fuer Verstaendlichkeit.
F8 Was sind Ports und was sind Adapter im Hexagonalen Modell?
Ports sind die Objekte des Applikationshexagons
- es gibt Inbound Ports (Use Cases) und Outbound Ports (Benachrichtigungen versenden, Persistenz aufrufen). Adapter sind die Objekte des Infrastrukturhexagons
- Inbound Adapter (HTTP-Endpunkt) und Outbound Adapter (HTTP-Client, Datenbankzugriff). Adapter adaptieren gemaess dem Entwurfsmuster Adapter eine Schnittstelle auf eine andere. Das Hexagonale Modell heisst deshalb auch Ports-und-Adapter-Modell (Alistair Cockburn).
F9 Wie unterscheiden sich Programmfluss und Codeabhaengigkeit beim Anwendungsfall Mitarbeiter einstellen?
Der Programmfluss laeuft von aussen nach innen und wieder nach aussen: Infrastruktur
- Applikation
- Domaene
- Applikation
- Infrastruktur. Die Codeabhaengigkeit stimmt nur teilweise damit ueberein: Beim Persistenzaufruf ruft der Use Case zwar Mitarbeiter speichern im Repository auf (Programmfluss nach aussen), aber die Codeabhaengigkeit ist per DIP umgedreht
- das Repository-Interface liegt in der Applikation und wird vom EmployeeJpaRepository in der Infrastruktur implementiert.
F10 Welche Punkte nennt die Zusammenfassung als unverzichtbar und welche als optional?
Unverzichtbar sind: die Aufteilung in Komponenten (ohne sie hat man gar keine Struktur im Code), die Trennung von technischem und fachlichem Code, die lose Kopplung (mindestens durch Schnittstellen) und das Managen der Abhaengigkeiten (Definieren, Dokumentieren, Umsetzen, Qualitaetssicherung). Optional sind die Aufteilung in Domaene/Applikation/Infrastruktur und die Verwendung des Hexagonalen Modells (Ports und Adapter).
F11 Welche Regeln gelten fuer die Benutzerschnittstelle, und warum betrachtet man sie als eigene Anwendung?
Die Benutzerschnittstelle enthaelt nur Praesentationslogik (grafische Elemente, Navigation, Eingaben und Reaktionen). Sie soll idealerweise nichts enthalten, was in Applikation oder Domaene gehoert (auch wenn User Experience mitunter Duplikation erzwingt), und sie haengt nur fuer UI-spezifischen Code von der Infrastruktur ab. Man betrachtet sie als eigene, externe Anwendung mit eigener logischer Architektur und schneidet sie nach den Beduerfnissen der Anwender, nicht nach den Backend-Komponenten; fuer Tests in Applikation und Domaene ist sie nicht noetig.
F12 Was besagt das Prinzip der losen Kopplung, und welche drei Mittel setzt man dafuer ein?
Lose Kopplung reduziert die direkten Abhaengigkeiten zwischen Komponenten und verbessert Wartbarkeit und Testbarkeit. Die drei Mittel sind: Schnittstellen verwenden (OCP, ISP, DIP), ereignisbasierte Kommunikation (Erzeuger und Empfaenger von Events kennen sich nicht) und asynchrone Kommunikation. Asynchrone Verarbeitung bringt zusaetzlich Performanz (kuerzere Antwortzeit), Skalierbarkeit (Queues vermeiden Ueberlast) und Robustheit (der synchrone Teil funktioniert auch bei Fehlschlag des asynchronen Teils).
F13 Warum duerfen keine zyklischen Abhaengigkeiten zwischen Komponenten bestehen?
Zyklische Abhaengigkeiten verschlechtern die Analysierbarkeit, die Testbarkeit und die Aenderbarkeit des Systems - analog zu Klassen. Codeabhaengigkeiten sollen den fachlichen Abhaengigkeiten folgen (z.B. haengt Gehaltsabrechnung von Mitarbeiter ab, weil Gehaltsabrechnung ohne das Konzept Mitarbeiter nicht denkbar ist), und mit dem DIP bringt man die Codeabhaengigkeiten in die richtige Richtung, ohne Zyklen (bidirektionale Abhaengigkeiten eingeschlossen) zu erzeugen.
F14 Was zeigen die NGHR-Aenderungsszenarien ueber die Wirkung des DIP?
Sie zeigen, dass Aenderungen auf Detailebene nicht auf hoehere Ebenen durchschlagen duerfen. Beispiel: Wird die Gehaltsabrechnung statt als PDF als JSON bereitgestellt, aendert sich nur die Infrastruktur, nicht Domaene oder Applikation. Ein neuer Empfaenger im Ablauf (Einkaufssystem) betrifft Applikation und Infrastruktur, aber nicht die Domaene. Erst eine neue fachliche Regel (Mitarbeiter muss einer Organisation zugeordnet sein) aendert alle drei Schichten. So bestimmen die Abstraktionsebenen die erlaubten Codeabhaengigkeiten.
F15 Wodurch wird laut Kapitel die Kapselung von Komponenten erreicht, und welche Prinzipien und Muster werden dabei genannt?
Eine Komponente gibt ihre innere Struktur nicht preis - analog zur Kapselung bei Klassen. Genannt werden das Gesetz von Demeter und das Entwurfsmuster Facade. Ausserdem gilt das Interface Segregation Principle (ISP): jedem Client seine eigene, clientspezifische Schnittstelle. Falsch waere eine generische, nicht clientspezifische Schnittstelle, die ein Geschaeftsobjekt zurueckgibt; richtig ist eine clientspezifische Schnittstelle, die ein Immutable oder DTO liefert.