Saubere Architektur in JavaScript: Hexagonal in der Praxis meistern

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

Ihr Code ist eine Festung: Warum hexagonale Architektur Ihr JavaScript-Projekt rettet

\n\n

Haben Sie schon einmal die Situation erlebt, dass ein Datenbankwechsel oder der Austausch einer externen API zu einem Albtraum des Umschreibens der Hälfte des Projekts wurde? Oder haben Sie vielleicht Unit-Tests geschrieben, die eher wie Integrationstests aussahen, weil die Geschäftslogik eng mit Express oder MongoDB verbunden war? Wenn ja – willkommen in der Welt der Hexagonalen Architektur (Ports and Adapters).

\n\n

Dies ist nicht nur ein modisches Pattern aus den Büchern von Alistair Cockburn. Es ist eine praktische Methode, um Ihren JavaScript-Code robust, testbar und unabhängig von Frameworks zu machen. In diesem Artikel werden wir keine langweiligen Vorträge halten. Wir nehmen eine reale Aufgabe (ein Auftragsverwaltungssystem) und bauen es Schritt für Schritt nach den Prinzipien der hexagonalen Architektur in JavaScript auf. Sie werden sehen, wie die Geschäftslogik makellos sauber bleibt und der gesamte „Lärm“ (Datenbanken, HTTP-Anfragen, Warteschlangen) außen vor bleibt.

\n\n

Bereit? Los geht's!

\n\n

Was ist hexagonale Architektur (und was haben Ports und Adapter damit zu tun)?

\n\n

Stellen Sie sich eine Schleuse (Gateway) eines Raumschiffs vor. Im Inneren befindet sich das Kontrollpult (Ihre Geschäftslogik). Draußen gibt es unendlich viele verschiedene Planeten (Datenbanken, Web-Frameworks, externe Dienste). Die Schleuse ist der Port (die Schnittstelle). Und der Raumanzug, der das Atmen auf einem bestimmten Planeten ermöglicht, ist der Adapter.

\n\n

Die Grundidee: Der Anwendungskern (Core) sollte nicht wissen, wer und wie darauf zugreift. Er deklariert nur die „Ports“ (Schnittstellen). Die konkreten Implementierungen (Adapter) werden von außen angeschlossen. Dies bietet drei Super-Fähigkeiten:

\n\n
    \n
  • Isolierung der Geschäftslogik – Sie wechseln die Datenbank, ohne den Code für die Rabattberechnung zu ändern.
  • \n
  • Testbarkeit – Sie testen die Logik, ohne Docker oder eine echte Datenbank hochfahren zu müssen.
  • \n
  • Framework-Unabhängigkeit – Sie können von Express zu Fastify wechseln, indem Sie einfach einen neuen Adapter schreiben.
  • \n
\n\n

Kommen wir zum Code.

\n\n

Projekteinrichtung und Ordnerstruktur

\n\n

Zunächst erstellen wir eine einfache Struktur. Wir schreiben in reinem TypeScript (oder JavaScript mit JSDoc – je nach Vorliebe). Unser Ziel ist ein Auftragsverwaltungssystem.

\n\n
src/\n├── core/\n│   ├── domain/          # Entitäten und Value Objects\n│   ├── ports/           # Schnittstellen (Ports)\n│   └── services/        # Geschäftslogik (Use Cases)\n├── adapters/\n│   ├── primary/         # Adapter, die unseren Kern aufrufen (HTTP, CLI)\n│   └── secondary/       # Adapter, die der Kern aufruft (DB, externe APIs)\n└── app.js               # Einstiegspunkt (alles zusammenbauen)\n
\n\n

Schritt 1. Der Kern: Domänenentitäten und Ports

\n\n

Beginnen wir mit dem Wichtigsten – dem, was von nichts abhängt. Erstellen wir die Entität Order (Auftrag).

\n\n

core/domain/Order.js

\n\n
export class Order {\n  constructor({ id, userId, items, status = 'pending' }) {\n    this.id = id;\n    this.userId = userId;\n    this.items = items; // Array { productId, quantity, price }\n    this.status = status;\n    this.total = this.calculateTotal();\n  }\n\n  calculateTotal() {\n    return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);\n  }\n\n  approve() {\n    if (this.status !== 'pending') {\n      throw new Error('Only pending orders can be approved');\n    }\n    this.status = 'approved';\n  }\n}\n
\n\n

Jetzt definieren wir die Ports – die Schnittstellen, die unser Kern von der Außenwelt erwartet.

\n\n

core/ports/OrderRepository.js

\n\n
// Dies ist ein Port (Schnittstelle). Wir implementieren ihn hier nicht.\nexport class OrderRepository {\n  async save(order) { throw new Error('Not implemented'); }\n  async findById(id) { throw new Error('Not implemented'); }\n}\n
\n\n

core/ports/EmailService.js

\n\n
export class EmailService {\n  async sendOrderConfirmation(order) { throw new Err                

Blogs

Buchempfehlungen