Zum Inhalt springen

Presentation – wo UI und UX leben

Auf der anderen Seite des View-Contracts beginnt Presentation.

Eine View erhält von ihrer Facade zum Beispiel:

customer
canOrder
canDelete
loading
deleteCustomer()
placeOrder()

Presentation entscheidet, wie diese Informationen sichtbar und bedienbar werden: wie sie angeordnet sind, wie Ladezustände aussehen, welcher Button deaktiviert erscheint, wann sich ein Dialog öffnet, wie ein Formular funktioniert und wie sich Tastatur- oder Touch-Interaktion verhält.

Die fachliche Bedeutung der Werte wurde bereits vorher bestimmt. Bis hierhin war die Architektur für den Benutzer unsichtbar. In Presentation wird sie zur Oberfläche.

„Presentation muss dumm sein“ ist als Leitsatz zu pauschal. Eine gute UI kann technisch anspruchsvoll sein.

Presentation darf komplexe Verantwortung für Forms, Wizards, modale Dialoge, Tabellen, Drag & Drop, Fokusmanagement, Keyboard-Navigation, Accessibility, Touch-Interaktion, responsive Layouts, Animationen und UI-Komposition besitzen. Ein anspruchsvolles Formular kann mehrere hundert Zeilen UI-Code benötigen, ohne dabei Businesslogik zu enthalten.

Ein Autocomplete-Feld etwa muss Eingaben verarbeiten, Vorschläge darstellen, Pfeiltasten und Escape behandeln, den Fokus zwischen Eingabefeld und Liste verwalten und für Screenreader verständlich bleiben. Keine dieser Aufgaben entscheidet darüber, ob der ausgewählte Kunde bestellen darf – trotzdem ist die Komponente alles andere als trivial.

Die Grenze verläuft nicht entlang der Zeilenzahl, sondern entlang der Art der Komplexität: Presentation darf UI-Komplexität besitzen. Fachliche Komplexität gehört ihr nicht.

+State hat bereits geliefert, Application hat die Werte über die View-Facade zugänglich gemacht:

canOrder;
canDelete;
selectedCustomer;
visibleCustomers;

Presentation darf diese Werte darstellen:

canOrder = false
Order-Button wird disabled dargestellt

Sie soll nicht erneut rekonstruieren, was bereits entschieden wurde:

<button
[disabled]="
facade.customer().status !== 'ACTIVE' ||
facade.customer().creditHold !== 'NONE'
"
>
Order
</button>

Sobald ein Template selbst prüft, welcher Status oder welches Flag eine Aktion erlaubt, existiert die Business Rule ein zweites Mal – nur schwerer auffindbar, weil sie jetzt im Markup statt in +State steht.

Presentation darf eine fachliche Entscheidung darstellen. Sie sollte sie nicht rekonstruieren.

Presentation rendert bereits abgeleitete fachliche Zustände wie canOrder, canDelete und selectedCustomer, ohne diese Entscheidungen selbst neu zu berechnen.

Die Grenze aus dem Application-Artikel setzt sich hier fort. Eine View konsumiert genau einen view-spezifischen Contract:

CustomerListFacade
CustomerListView

Sie greift nicht direkt auf mehrere interne Domain-Bereiche zu:

CustomerListView
├── CustomerState
├── PermissionState
├── OrderState
└── SelectionState

Und im Regelfall konsumiert sie auch nicht mehrere Facades gleichzeitig:

CustomerListView
├── CustomerFacade
├── PermissionFacade
├── OrderFacade
└── SelectionFacade

Sonst orchestriert die Presentation wieder die interne Struktur der Domain – nur eine Ebene höher, mit Facades statt Stores.

Eine View konsumiert genau eine Facade. Diese Facade darf intern auf mehrere Domain-Bereiche zugreifen. Die View muss diese Struktur nicht kennen.

Forms gehören zur Presentation. Dazu gehören Formzustände und -mechaniken wie FormControl, FormGroup, dirty, touched, focused, submitted, Feldsichtbarkeit, Eingabeformatierung, Masking und lokales Validierungsfeedback.

Das gilt unabhängig davon, ob ein Framework dafür Reactive Forms, Template Forms, Hooks oder eine eigene Form-Library verwendet.

Innerhalb eines Formulars lohnt sich allerdings eine weitere Unterscheidung.

Presentation darf beispielsweise feststellen:

  • ein Pflichtfeld ist leer,
  • eine Eingabe entspricht nicht dem erwarteten Format,
  • eine E-Mail-Adresse ist syntaktisch ungültig,
  • zwei lokal eingegebene Passwortwerte stimmen nicht überein.

Eine Regel wie:

Dieser Kunde darf seine E-Mail-Adresse in seinem aktuellen Vertragsstatus nicht verändern.

gehört dagegen nicht ins Formular. Diese Entscheidung muss bereits aus der Domain kommen, beispielsweise als canEditEmail im View-Contract.

Auch eine technische Validator-Funktion muss nicht zwangsläufig direkt in der Component-Datei stehen. Entscheidend für die Layergrenze ist ihre Verantwortung, nicht der physische Ort jeder Hilfsfunktion.

Das Formular gehört zur Presentation. Die fachliche Wahrheit, die über dieses Formular verändert wird, nicht.

Dialoge gehören ebenfalls zur Presentation:

User klickt Delete
Confirmation Dialog öffnen
User bestätigt
facade.deleteCustomer()

Ob überhaupt gelöscht werden darf, wurde bereits vorher beantwortet – in +State, sichtbar über canDelete.

Presentation entscheidet, ob dafür ein Dialog verwendet wird, wie er aussieht, welche Buttons er besitzt, welcher Fokus gesetzt wird, wie Escape behandelt wird und was nach Cancel passiert. Sie entscheidet nicht erneut, ob das Löschen fachlich erlaubt ist.

Dasselbe gilt für einen laufenden Löschvorgang. Der View-Contract kann beispielsweise loading bereitstellen. Presentation entscheidet daraus, wie sich die Oberfläche währenddessen verhält: ob der Dialog offenbleibt, einen Spinner zeigt, Buttons deaktiviert oder sich nach erfolgreichem Abschluss schließt.

Der Zustand kommt an. Die UI entscheidet, wie er sich anfühlt.

Neben Forms ist reiner UI-Zustand ein weiterer großer Bereich der Presentation:

isDialogOpen;
activeAccordion;
hoveredRow;
focusedField;
selectedTab;
menuExpanded;
tooltipVisible;

Solange diese Werte lediglich beschreiben, wie sich die Oberfläche gerade darstellt oder verhält, gehören sie zur Presentation.

Dieselbe Leitfrage wie schon in +State hilft auch hier:

Hat dieser Zustand unabhängig von seiner aktuellen Darstellung fachliche Bedeutung?

Wenn nein, ist Presentation sehr wahrscheinlich seine natürliche Heimat.

Das ist keine absolute Regel. Ein selectedTab kann fachlich relevant werden, wenn er tatsächlich eine Domain-Auswahl repräsentiert. Der Name einer Variable entscheidet wenig über ihren Layer. Entscheidend ist ihre Bedeutung.

Die Idee, UI-Komponenten auf Darstellung zu fokussieren, ist älter als heutige Signal- oder Store-Architekturen.

John Papas Angular Style Guide empfahl bereits unter Defer Logic to Services, Logik aus Components zu delegieren, unter anderem um sie „slim, trim, and focused“ zu halten. Unter Keep Components Focused wird zusätzlich empfohlen, eine Component auf ihre View zu konzentrieren.

Auch der aktuelle offizielle Angular Style Guide formuliert mit „Keep components and directives focused on presentation“ weiterhin denselben Grundgedanken: Code in Components und Directives sollte grundsätzlich mit der dargestellten UI zusammenhängen. Code, der unabhängig von dieser UI sinnvoll bleibt, soll in eigene Funktionen oder Klassen ausgelagert werden.

Das bedeutet nicht, dass der Angular Style Guide das Layering dieser Serie definiert. Auch eine ausgelagerte Validator-Funktion kann weiterhin Teil der Presentation-Verantwortung sein.

Die Quellen zeigen lediglich, dass fokussierte UI-Komponenten eine etablierte Idee sind.

Dasselbe Grundproblem existiert bei React-, Vue- und Svelte-Components. Sobald eine UI-Komponente gleichzeitig Datenbeschaffung, Business Rules, fachliche Aggregation, State Management und UI-Interaktion übernimmt, verliert sie ihren Fokus – unabhängig vom Framework, das sie rendert. Der Denkzettel Komponenten sind keine Use-Cases beschreibt dieses Muster genauer.

Saubere Layergrenzen ermöglichen unterschiedliche Presentation-Technologien auf derselben fachlichen Basis:

gemeinsame Domain
Application
View-Contract
/ \
/ \
Material View Ionic View

Welche Presentation aktiv ist, kann außerhalb des Features auf App- beziehungsweise Composition-Level entschieden werden:

App Composition
├── Desktop → Material Presentation
└── Mobile → Ionic Presentation

Die Domain muss nicht wissen, warum eine bestimmte Presentation gerade aktiv ist.

In einem Langzeitprojekt wurden Angular Material und Ionic über Jahre parallel auf derselben fachlichen Basis betrieben. Das funktionierte, weil beide Presentations dieselben fachlichen Entscheidungen konsumierten, Business Rules nicht selbst neu implementierten und UI-spezifische Unterschiede lokal hielten.

Material und Ionic durften sich beispielsweise in Navigation, Layout, Controls, Dialogen, Touch-Verhalten und Responsive Design unterscheiden. Sie sollten sich nicht darin unterscheiden, ob derselbe fachliche Vorgang erlaubt ist:

canOrder = false
Material:
Button disabled
Ionic:
Action ausgegraut oder nicht verfügbar

Unterschiedliche UX. Dieselbe fachliche Wahrheit.

Angular Material und Ionic sind dabei nur ein Praxisbeispiel. Das Architekturprinzip hängt nicht an diesen Frameworks.

Eine gemeinsame Domain und ein View-Contract werden von einer Angular-Material- und einer Ionic-Presentation unterschiedlich dargestellt, während beide dieselben fachlichen Entscheidungen verwenden.

Das widerspricht der 1:1-Regel aus dem Application-Artikel nicht, solange beide Technologien alternative Implementierungen derselben logischen View und desselben View-Contracts darstellen.

CustomerDetailFacade
/ \
Material Ionic
View View

Entwickeln Desktop und Mobile dagegen unterschiedliche Anforderungen an ihren Application-Contract, sind daraus unterschiedliche View-Verantwortungen geworden. Dann erhalten sie auch eigene Facades.

Die relevante Grenze bleibt dieselbe: Eine konkrete Presentation konsumiert genau einen klar definierten View-Contract.

Presentation darf und soll:

  • genau einen View-Contract beziehungsweise eine Facade konsumieren
  • Werte und Derived State rendern
  • Benutzerinteraktionen entgegennehmen
  • Benutzeraktionen an die Facade delegieren
  • Forms verwalten
  • modale Dialoge öffnen
  • lokalen UI-Zustand besitzen
  • Fokus, Keyboard- und Touch-Interaktionen verwalten
  • Accessibility umsetzen
  • Animationen und responsive Darstellung steuern
  • UI-Komponenten komponieren
  • UI-spezifische Fehlermeldungen darstellen
  • fachliche Entscheidungen visuell repräsentieren

Presentation sollte nicht:

  • Business Rules implementieren
  • fachliche Entscheidungen rekonstruieren
  • DTOs interpretieren
  • HTTP-Requests ausführen
  • externe SDKs direkt als Domain-Zugang verwenden
  • +State direkt konsumieren
  • mehrere Domain-Stores orchestrieren
  • mehrere Facades fachlich koordinieren
  • fachliche Streams zusammenführen
  • fachlichen Derived State erzeugen
  • technische API-Fehlercodes interpretieren
  • Domain-Zustand manuell synchronisieren

Wer eine Presentation-Datei öffnet, sieht UI- und Interaktionscode. Business Rules müssen nicht zwischen Event-Handlern, Formularzustand und CSS-Klassen gesucht werden – sie stehen in +State.

UI und UX können sich unabhängig von der Fachlichkeit weiterentwickeln. Ein Dialog kann zu einem Drawer werden, eine Tabelle zu Cards, Desktop und Mobile können unterschiedliche Interaktionsmuster verwenden. Die Domain muss dafür nicht verändert werden.

Umgekehrt bleiben canOrder, canDelete und visibleCustomers über mehrere Presentations hinweg konsistent. Jede Oberfläche konsumiert dieselben fachlichen Aussagen und entscheidet lediglich, wie sie diese sichtbar und bedienbar macht.

Eine neue Accessibility-Anforderung, ein geändertes Layout oder ein anderes Interaktionsmuster verändert dadurch nicht automatisch den fachlichen State.

UI-Arbeit bleibt UI-Arbeit.

Eine gute Presentation-Komponente muss nicht fünf Zeilen lang sein.

Komplexes Form Handling, Drag & Drop, Accessibility oder Keyboard-Navigation können völlig legitim hunderte Zeilen Presentation-Code erzeugen.

Entscheidend ist die Art der Verantwortung:

komplexe UI-Interaktion → Presentation
komplexe fachliche Entscheidung → +State

Eine große Component ist deshalb nicht automatisch ein Architekturproblem. Problematisch wird sie, wenn ihre Größe daraus entsteht, dass mehrere Verantwortungsarten ineinanderlaufen.

Technische Außenwelt
Infrastructure
+State
Application
Presentation
Benutzer

Infrastructure schützt und übersetzt die technische Außenwelt. +State gibt dem Zustand fachliche Bedeutung und besitzt die Business Rules. Application schneidet daraus einen Contract für eine konkrete View. Presentation macht diesen Contract sichtbar und bedienbar.

Jede dieser Schichten darf anspruchsvoll sein, solange ihre Komplexität aus ihrer eigenen Verantwortung entsteht.

Der Wert des Layerings liegt in dieser Klarheit: Eine Entscheidung hat einen natürlichen Ort, und die benachbarten Schichten müssen sie nicht noch einmal treffen.