DDD & Co., Teil 1: Was an CRUD falsch ist

the next big thing  –  24 Kommentare

Die Anwendung TodoMVC ist das "Hallo Welt" des Web: Was ursprünglich als reiner Vergleich von verschiedenen UI-Frameworks gedacht war, ist inzwischen zu einem eigenen Ökosystem gereift. Wenn man die Anwendung serverseitig umsetzen will, warum dann nicht mit Domain-Driven Design (DDD)?

Auf den ersten Blick scheint TodoMVC harmlos zu sein: Es handelt sich um eine einfache Aufgabenliste, auf der man neue Einträge hinzufügen und bestehende editieren kann. Erledigte Einträge lassen sich abhaken, und schließlich kann man Einträge entfernen, um wieder aufzuräumen.

Das Beispiel klingt so trivial, dass man kaum geneigt ist, darüber nachzudenken, die Anwendung nicht mit CRUD umzusetzen: Eine Liste, auf der man Einträge hinzufügen, ändern und löschen kann – wie sollte man das sonst lösen?

Bei näherem Hinsehen ergeben sich aber eine Reihe von Fragen, die zumindest Zweifel daran aufwerfen, dass die Anwendung wirklich so trivial ist, wie sie zunächst scheint. Eine solche Frage lautet, ob es einen Unterschied gibt zwischen dem Entfernen eines Eintrags, der bereits erledigt ist, und dem Entfernen eines Eintrags, der noch nicht erledigt ist.

TodoMVC ist eine einfache Todo-Liste.

Zunächst einmal ist festzuhalten, dass beides unterschiedliche Aktionen sind. Sie mögen in der UI mit den gleichen Steuerelementen dargestellt werden, und das Ergebnis mag das gleiche sein, doch rein vom Zustand der Anwendung unterscheiden sich die beiden Vorgänge grundlegend. Das wird spätestens dann deutlich, wenn man die Regel einführt, dass ausschließlich jene Einträge entfernt werden dürfen, die bereits erledigt sind.

Auch aus Benutzersicht unterscheiden sich die beiden Vorgänge gravierend. Um das zu erkennen, muss man die Frage nach der Intention stellen: Warum entfernen Benutzer einen Eintrag im einen und warum im anderen Fall?

Es wäre beispielsweise denkbar, dass Benutzer noch nicht erledigte Einträge entfernen, weil sie hinfällig sind – sie werden also verworfen. Tatsächlich bereits erledigte Einträge werden hingegen entfernt, um für Ordnung auf der Liste zu sorgen. Trotzdem sind diese Einträge unter Umständen weiterhin interessant, beispielsweise um im Nachhinein herauszufinden, was man innerhalb eines gewissen Zeitraums alles erledigt hat.

Es gibt noch weitere solche Unterscheidungen: TodoMVC erlaubt es Benutzern beispielsweise, den Text eines Eintrags zu ändern. In CRUD wäre das ein UPDATE, ebenso wie das Abhaken eines erledigten Eintrags. Doch beide Vorgänge unterscheiden sich in der Intention gravierend: Im einen Fall geht es um das Korrigieren oder Anpassen eines Eintrags, im anderen um das Als-erledigt-markieren.

Dass diese Unterscheidungen existieren, fällt auch dann auf, wenn man sich TodoMVC mit Stift und Papier vorstellt. Niemand würde umgangssprachlich "UPDATE den dritten Eintrag!" sagen, wenn man ebenso gut "Hake den dritten Eintrag ab!" sagen kann, was viel natürlicher und zugleich klarer ist. Der Kern hierbei: Die erste Formulierung ist rein technisch, die zweite ist fachlich und berücksichtigt die Intention.

Das Wort "abhaken" hat eine fachliche Bedeutung, mit der man automatisch eine greifbare Aktion aus der Realität assoziiert. Das Wort "UPDATE" hingegen beschreibt lediglich den rein technischen Vorgang der Datenmanipulation auf Tabellenebene.

Das zeigt eine der größten Hürden in der tagtäglichen Softwareentwicklung: Die fachliche und die technische Sprache weichen voneinander ab – und in der Regel ist die technische Sprache viel weniger ausdrucksstark als die fachliche. Das erschwert die Kommunikation zwischen Fachexperten und Entwicklern enorm, da in beide Richtung ein ständiges Mapping erforderlich ist.

Das führt außerdem dazu, dass die Fachsprache nicht klar definiert ist: Da man sie ohnehin ständig auf technische Begriffe übersetzen und rückübersetzen muss, ist mal von "abhaken" die Rede, mal von "wegstreichen" und mal von "vergessen". Ob die drei Begriffe tatsächlich denselben Vorgang meinen oder ob vielleicht verschiedene Konzepte dahinterstecken, wird kaum hinterfragt.

Stattdessen werden ständig implizite Annahmen getroffen, was letztlich allerdings häufig dazu führt, dass die technische Implementierung nicht die fachlichen Anforderungen erfüllt. Das fällt aber erst auf, wenn es zu spät ist und Änderungen nur noch mit viel zeitlichem und finanziellen Aufwand möglich sind.

Zusätzlich kommt noch ein weiteres Problem hinzu: CRUD verliert die Historie. Ändern Benutzer beispielsweise einen Eintrag im Lauf der Zeit mehrfach, lassen sich die einzelnen Bearbeitungsschritte nicht mehr nachvollziehen. Schlimmer noch: Es lässt sich nicht einmal mehr nachvollziehen, ob der Eintrag überhaupt jemals geändert wurde.

Selbstverständlich lässt sich das in den Griff bekommen, indem man ein Feld für einen Zeitstempel der letzten Aktualisierung einfügt. Doch auch das hilft noch nicht, die einzelnen Bearbeitungsstände nochmals abrufen zu können. Hierzu muss man entweder eine ganze Reihe von Feldern oder gleich eine neue Tabelle anlegen.

Das Problem dabei: Da man nicht weiß, welche Fragen zukünftig über die Daten gestellt werden, kann man die Tabellen nicht auf die entsprechenden Antworten optimieren. Man sammelt daher entweder zu viele oder zu wenige Daten, ganz sicher aber stets die falschen. Werden dann von Fachexperten Fragen wie die folgenden gestellt, lassen sie sich – wenn überhaupt – nur mit Glück beantworten:

  • Wie viel Zeit vergeht zwischen dem Hinzufügen und dem Abhaken eines Eintrags?
  • Wie häufig wird ein Eintrag editiert, bevor er abgehakt wird?
  • Wie viele Einträge werden verworfen, obwohl sie mindestens dreimal editiert wurden?
  • Wie häufig wird innerhalb von 30 Sekunden das Abhaken rückgängig gemacht?

In der Regel wird die Antwort auf jede dieser Fragen lauten, dass man zunächst Code schreiben müsse, der die dafür relevanten Daten erfasst und speichert. Anschließend müsse man lediglich noch ein paar Wochen oder Monate warten, und schon könne man die gewünschte Auswertung liefern. Dass das alles andere als befriedigend ist, liegt auf der Hand.

Nimmt man das alles zusammen, ergibt sich ein erschreckendes Bild für CRUD. Da die Vorgänge UPDATE und DELETE Daten irreversibel zerstören, lassen sich historische Daten nicht ohne zusätzlichen Aufwand auswerten. Außerdem führt die künstliche Beschränkung auf gerade einmal vier Verben dazu, dass die Kommunikation zwischen Fachexperten und Entwicklern schwerfällt und häufig misslingt.

Warum verwenden wir dann ständig CRUD? Die Antwort lautet meistens, weil wir es nicht anders gelernt haben. Das Modell ist dermaßen einfach und grundlegend, dass es jeder Entwickler frühzeitig kennt – und da alle es benutzen, hinterfragt kaum jemand Sinn und Unsinn davon.

Dass CRUD nach dem raschen Einstieg aber einen Haufen Probleme aufwirft, wird dabei gerne vergessen. Wie rasch man auf diese Probleme stößt, zeigt selbst ein so einfaches Beispiel wie TodoMVC.

Die nächste Folge von "DDD & Co." wird das Konzept des Domain-Driven Design (DDD) als eine Alternative zur Modellierung der Fachlichkeit vorstellen, die sich von dem Grundgedanken des relationalen Datenbankschemas löst und stattdessen die eigentlich relevanten fachlichen Aspekte in den Vordergrund rückt.

tl;dr: CRUD ist einfach, aber begrenzt: Die künstliche Einschränkung auf vier Verben und die destruktiven Vorgänge UPDATE und DELETE verursachen zahlreiche Probleme, die sich selbst in kleinen Anwendungen rasch bemerkbar machen.