DDD in TypeScript: Saubere Architektur für komplexe Projekte

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Warum gewöhnliches CRUD Ihr Projekt tötet und wie DDD das beheben kann

\n\n

Jeder Entwickler ist mindestens einmal auf die Situation gestoßen, in der ein zunächst einfaches Projekt zu einem „Sumpf“ aus Services, Controllern und über den gesamten Code verteilter Geschäftslogik wird. Sie beginnen, UserService zu schreiben, dann OrderService, und nach einem halben Jahr befindet sich im OrderService die Logik zum Senden von E-Mails, zur Berechnung von Rabatten und zur Überprüfung des Benutzerguthabens. Kommt Ihnen das bekannt vor?

\n\n

Das Problem ist, dass wir zu früh an Datenbanken, Frameworks und REST-APIs denken. Wir vergessen das Wesentliche – die Domäne (domain). Domain-Driven Design (DDD) bietet einen radikal anderen Ansatz: Zuerst modellieren wir die Geschäftsprozesse in der Sprache der Experten, erst dann den Code.

\n\n

In diesem Artikel werden wir untersuchen, wie man Domain-Driven Design in TypeScript anhand realer Beispiele anwendet. Sie werden sehen, wie man eine saubere Architektur aufbaut, in der Geschäftsregeln getrennt von der Infrastruktur leben und der Code leicht zu testen und zu ändern ist.

\n\n

Grundlagen von DDD: Sprache, Kontexte und Modell

\n\n

Ubiquitäre Sprache (Ubiquitous Language)

\n

Das erste Prinzip von DDD ist eine einheitliche Sprache, die sowohl für Entwickler als auch für Fachexperten verständlich ist. Wenn ein Manager sagt „Der Kunde hat das Abonnement aktiviert“, dann sollte es im Code eine Methode activateSubscription() geben, nicht setStatus(1). TypeScript mit seiner strengen Typisierung ist dafür ideal geeignet – die Namen von Klassen, Methoden und Variablen werden zur Dokumentation.

\n\n

Begrenzter Kontext (Bounded Context)

\n

Ein großes System wird in unabhängige Kontexte unterteilt: „Zahlungen“, „Benutzerverwaltung“, „Lieferung“. Jeder Kontext hat sein eigenes Modell und seine eigene Sprache. TypeScript-Module und strikte Typen helfen, diese Kontexte auf Importebene strikt zu trennen.

\n\n

Aufbau des Domänenmodells in TypeScript

\n\n

Value Object (Wertobjekt)

\n

Dies ist eine unveränderliche Entität, die durch ihre Attribute und nicht durch eine ID definiert wird. Beispiele: Email, Money, Coordinates.

\n\n
// value-objects/Email.ts\nexport class Email {\n  private readonly value: string;\n\n  constructor(email: string) {\n    if (!this.isValid(email)) {\n      throw new Error('Invalid email address');\n    }\n    this.value = email;\n  }\n\n  private isValid(email: string): boolean {\n    return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n  }\n\n  public getValue(): string {\n    return this.value;\n  }\n\n  public equals(other: Email): boolean {\n    return this.value === other.value;\n  }\n}\n
\n\n

Beachten Sie: Email prüft selbst seine Gültigkeit. Kein Service muss wissen, wie eine korrekte E-Mail aussieht. Das ist reines Domain-Driven Design in TypeScript – die Geschäftsregel lebt innerhalb des Objekts.

\n\n

Entity (Entität)

\n

Im Gegensatz zu Value Objects hat eine Entität eine ID und kann sich im Laufe der Zeit ändern. Beispiele: User, Order.

\n\n
// entities/User.ts\nimport { Email } from '../value-objects/Email';\nimport { UserId } from '../value-objects/UserId';\n\nexport class User {\n  private readonly id: UserId;\n  private email: Email;\n  private isActive: boolean;\n\n  constructor(id: UserId, email: Email) {\n    this.id = id;\n    this.email = email;\n    this.isActive = true;\n  }\n\n  public getId(): UserId {\n    return this.id;\n  }\n\n  public changeEmail(newEmail: Email): void {\n    if (!this.isActive) {\n      throw new Error('Cannot change email for inactive user');\n    }\n    this.email = newEmail;\n  }\n\n  public deactivate(): void {\n    this.isActive = false;\n  }\n}\n
\n\n

Aggregate (Aggregat)

\n

Ein Aggregat ist eine Gruppe von Entitäten und Value Objects, die als eine Einheit behandelt werden. Ein Aggregat hat eine Wurzel (Aggregate Root), über die der Zugriff auf die inneren Objekte erfolgt.

\n\n

Beispiel: eine Bestellung (Order)

Blogs

Buchempfehlungen