DDD verursacht Komplexität

vg

Beim Software-Engineering geht es darum, Systeme zu entwickeln, die durch die Zusammenarbeit ihrer verschiedenen Komponenten klar definierte Ziele erreichen. Diese Zusammenarbeit impliziert Wechselwirkungen, um Grenzen zu überschreiten, die das System unterteilen. Ein gutes Design dieser Grenzen macht Interaktionen effizient und stellt sicher, dass sie die angegebenen Geschäftsanforderungen erfüllen. Dies wollen wir hier im Beitrag DDD verursacht Komplexität untersuchen.

„Ein System ist ein miteinander verbundenes Set von Elementen, das so kohärent organisiert ist, dass es etwas erreicht“

— Donella Meadows, Denken in Systemen

Grenzen

Grenzen sind schwer zu definieren und erfordern einen durchdachten Prozess, um zu entscheiden, wo eine gesetzt werden soll. In einem System können wir zwei Arten von Grenzen definieren: logische und physikalische, die völlig unterschiedlich sind und nicht unbedingt eins zu eins abgebildet werden können.

Wenn wir tief in ein ganzes System eintauchen, können wir deutlich sehen, dass es in Komponenten zerlegt ist, die ebenfalls Systeme sind, die in Komponenten zerlegt sind, die auch in Komponenten zerlegt sind. Es handelt sich um eine Teilmenge von Systemen, die in miteinander verbundene Komponenten unterteilt sind, die miteinander interagieren, um eine bestimmte Geschäftsanforderung zu erfüllen.

Komponenten sind ein Zeichen von Modularität, aber Wechselwirkungen zwischen ihnen sind ein Zeichen für wesentliche oder zufällige Komplexität. Ein gutes Systemdesign muss die richtige Balance finden. Die Modularität soll die Zusammenarbeit von Komponenten ermöglichen und gleichzeitig sicherstellen, dass zukünftige Geschäftsanforderungen umgesetzt werden können. Nicht jedes geteilte System kann als modular bezeichnet werden, denn eine zufällige Zerlegung führt zu einem hochkomplexen System. Wir wollen Modularität erreichen, aber versehentliche Komplexität vermeiden.

Microservices

In verteilten Systemen wie Microservices ist jeder Microservice ein unabhängiges System, das in mehrere Namespaces (oder Pakete) unterteilt ist. Ein Namespace ist selbst ein System, das Klassen enthält. Eine Klasse ist auch ein System, das in Methoden unterteilt ist. Eine Methode ist ein System, das in Anweisungen zerlegt ist. Jede Stufe wird als ein System betrachtet, das in Module unterteilt ist. Gleiches gilt für einen modularen Monolithen.

Auf jeder Ebene ist ein Modul eine Sammlung ausführbarer Programmanweisungen, die alle folgenden Kriterien erfüllen:

  • Die Anweisungen implementieren eigenständige Funktionen
  • Die Funktionalität kann von jedem anderen Modul aus aufgerufen werden
  • Die Implementierung hat das Potenzial, unabhängig erstellt zu werden

Verursacht Modularität wirklich Komplexität?

Ein System gilt als komplex, wenn:

  • Das Ergebnis einer Aktion ist unvorhersehbar (wir wissen nicht, was passieren kann, wenn wir eine Codezeile ändern)
  • Nicht verwandte Teile des Systems werden unterbrochen, wenn eine Änderung übernommen wird
  • Nicht zu wissen, welche Module von einer neuen Geschäftsanforderung betroffen sind

Komplexität erkennt man an der Beziehung zwischen Ursache und Wirkung.

Was Komplexität verursacht, ist nicht die Modularität. Tatsächlich ist Komplexität eine Funktion sowohl des Systemdesigns als auch unserer kognitiven Fähigkeiten. Das Ziel der Modularität ist es, die kognitive Belastung zu reduzieren, die für die Weiterentwicklung des Systems in der Zukunft erforderlich ist und von den richtigen Designentscheidungen abhängt.

Domain-Driven Design

Domain-Driven Design (DDD) ist eine Modellierungstechnik, die die Modularität verbessert, da DDD auf dem über den Problemraum gesammelten Wissen basiert, um den Lösungsraum mit klar definierten Grenzen, klaren Modulen und Interaktionsströmen zu erreichen. DDD zerlegt zunächst die Geschäftsdomäne in Subdomänen: Kern-Subdomäne, unterstützende Subdomäne und generische Subdomäne, die versucht, die kognitive Belastung zu reduzieren. Anschließend werden sie auf der Grundlage konsistenter Geschäftsregeln in einem begrenzten Kontext gruppiert, der effektiv eine logische Grenze zieht. Der Schutz der Konsistenz von Geschäftsregeln innerhalb eines begrenzten Kontexts mit DDD wird durch die Identifizierung von Interaktionsströmen erreicht, die schließlich zu einer Kontextzuordnung führen.

Wie kann man domänengesteuerte Designentscheidungen treffen, welche die Komplexität reduzieren?

Zwei wichtige Faktoren, die sich auf die Komplexität auswirken: die Integrationsstärke und die Distanz zwischen den Komponenten auf jeder Ebene eines Systems. Der Begriff „Komponente“ wird hier verwendet, um einen Bounded Context, ein Aggregat, ein Domänenereignis, eine Klasse oder eine Methode darzustellen.

Zwei Komponenten, die miteinander interagieren, teilen Wissen, nicht nur Daten. Der Austausch von Wissen führt zu einer Kopplung, die je nach Integrationsstärke zwischen den Komponenten in 4 Typen unterschieden werden kann:

  • Intrusive Kopplung: Eine Komponente hängt von den Implementierungsdetails der anderen Komponente ab, sodass Änderungen an den Implementierungsdetails dazu führen können, dass die Interaktion aufgrund möglicher kaskadierender Fehler gestoppt wird.
  • Funktionale Kopplung: Eine Komponente implementiert eng verwandte Geschäftsfunktionen aus einer anderen Komponente, sodass Änderungen der Geschäftsanforderungen beide Komponenten betreffen.
  • Modellkopplung: Beide Komponenten teilen sich ein Modell der Geschäftsdomäne
  • Vertragskopplung: Eine Komponente hat einen Vertrag, um ein Modell aus einer anderen Komponente zu integrieren, es kommt also nicht auf das Implementierungsmodell an

Die Kosten für die Änderung sind proportional zum Abstand zwischen den Komponenten. Wenn diese Distanz zunimmt, werden auch die Kosten des Wandels steigen.

Eine geringe Integrationsfestigkeit und ein großer Abstand zwischen den Komponenten führen zu einer losen Kopplung, die Folgendes beinhaltet:

  • Wenig geteiltes Wissen
  • Hohe Kosten für kaskadierende Änderungen
  • Geringe Wahrscheinlichkeit von kaskadierenden Änderungen

Eine hohe Integrationsstärke und ein geringer Abstand zwischen den Komponenten führen zu einer hohen Kohäsion, die Folgendes umfasst:

  • Hohes geteiltes Wissen
  • Niedrige Kosten für kaskadierende Änderungen
  • Häufige kaskadierende Änderungen

Eine geringe Integrationsstärke und ein geringer Abstand zwischen den Komponenten führen zu lokaler Komplexität, die Folgendes umfasst:

  • Wenig geteiltes Wissen
  • Niedrige Kosten für kaskadierende Änderungen
  • Zusammengelegte, nicht miteinander verknüpfte Module

Eine hohe Integrationsstärke und ein großer Abstand zwischen den Komponenten führen zu einer globalen Komplexität, die Folgendes verursacht:

  • Hohes geteiltes Wissen
  • Hohe Kosten für kaskadierende Änderungen
  • Häufige kaskadierende Änderungen
DDD verursacht Komplexität

DDD sorgt für den Aufenthalt im Grünen.

Insgesamt werden die Ziele der Modularität innerhalb der grünen Blöcke und durch die Vermeidung der Ursachen für lokale und globale Komplexität erreicht.

Um domänengetriebene Designentscheidungen zu treffen, die die Modularität verbessern, ohne in lokale und globale Komplexität zu verfallen, müssen Integrationsstärke und Distanz in einem ungünstigen Verhältnis zueinander stehen.

Wie wenden Sie diese Regel in Ihrer täglichen Arbeit an?

Hier ist wie immer ein kleiner Ratschlag, wenn Sie Designentscheidungen treffen, versuchen Sie Ihr Bestes:

  • Reduzieren Sie die Integrationsstärke so weit wie möglich.
  • Gleichgewicht zwischen Integrationskraft und Distanz; Wenn beide hoch sind, reduzieren Sie die Entfernung; Wenn beide niedrig sind, erhöhen Sie den Abstand.

Einen weitere Beitrag finden Sie hier: Diesen Monat neu bei den STM32-Prozessoren

com

Newsletter Anmeldung

Bleiben Sie informiert! Wir informieren Sie über alle neuen Beiträge (max. 1 Mail pro Woche – versprochen)