>_ DevTrendsde

Sprache

Start

Sprachen

Bereiche

Frontend Backend Mobile DevOps AI / ML GameDev Blockchain Embedded Sicherheit
TypeScript

Cordis und Plugin-Architektur in TypeScript ohne Kopfschmerzen

Wenn du schon einmal eine erweiterbare Anwendung in Node.js oder TypeScript geschrieben hast, bist du wahrscheinlich auf dieselben Fallstricke gestoßen. Ein Benutzer oder System verbindet ein Plugin. Das Plugin hängt fünf Event-Listener an, startet ein paar Timer, registriert seine Routen und injiziert einen Service. Und dann wird das Plugin deaktiviert oder im laufenden Betrieb aktualisiert.

Was passiert als nächstes? Richtig, Speicherlecks. Listener bleiben hängen, Timer laufen im Hintergrund weiter, Kontextreferenzen verhindern, dass der Garbage Collector Speicher freigibt. In Node.js wird das Verwalten des Lebenszyklus von Abhängigkeiten oft zur manuellen Fleißarbeit.

Vor einiger Zeit standen die Entwickler des Koishi-Chatbot-Frameworks vor genau diesem Problem. Sie mussten einen Kern schaffen, in dem Hunderte von Drittanbieter-Plugins starten, sich isolieren, sich gegenseitig ersetzen und entladen können, ohne den Prozess neu zu starten. So wurde das Cordis-Framework geboren.

Was ist Cordis überhaupt

Die Erfinder nennen ihr Projekt ein „Meta-Framework der raumzeitlichen Komponierbarkeit". Das klingt schlau und anmaßend, aber die Essenz ist eigentlich bodenständig.

Cordis kombiniert einen Dependency-Injection-Container (IoC), einen Event-Bus und einen hierarchischen Kontextbaum. Jedes Plugin oder jeder Service lebt in seinem eigenen Kontext. Wenn dieser Kontext zerstört wird, bereinigt Cordis automatisch absolut alle Ressourcen dafür: entfernt Event-Handler, beendet Timer und löscht erstellte Services.

Hier gibt es keine Magie, aber es gibt klare Disziplin: wenn ein Plugin die Methoden [object Object], [object Object] oder [object Object] verwendet, kümmert sich das Framework selbst um die Bereinigung der Nebeneffekte.

Wie das Kontextmodell funktioniert

Das zentrale Konzept in der Bibliothek ist [object Object]. Es ist nicht nur ein flaches Objekt mit Einstellungen, sondern ein verzweigter Baum.

Wenn du [object Object] aufrufst, erzeugt das Framework einen Kindkontext (Fork). Der Kindkontext erbt die Services des Elternteils, speichert aber seine eigenen Referenzen auf registrierte Ressourcen.

Wenn du Plugin A deaktivierst, kollabiert sein Kindkontext. Der [object Object]-Handler wird vom gemeinsamen Event-Bus entfernt, während der Database Service und Plugin B ruhig weiterarbeiten.

Services und Typisierung in TypeScript

Services in Cordis werden durch Vererbung der Basisklasse [object Object] deklariert. Das macht sie direkt über Kontexteigenschaften zugänglich und erhält gleichzeitig strenge Typisierung:

Das [object Object]-Konstrukt löst das Problem der Modulladereihenfolge. Wenn der Database Service asynchron initialisiert oder sich später verbindet, wartet das abhängige Plugin auf seine Bereitschaft und aktiviert sich selbst.

Feinabstimmung der Sichtbarkeitsbereiche

In echten Programmen sollten Module oft nicht auf alles reagieren. Zum Beispiel wird ein Handler nur für Nachrichten aus einem bestimmten Kanal oder Anfragen mit einem bestimmten Header benötigt.

Cordis führt das Konzept der Filter durch den [object Object]-Aufruf und Kontexteigenschaften ein. Du kannst die Sichtbarkeit eines Services auf einen bestimmten Zweig des Baums beschränken oder ein Prädikat setzen, das unerwünschte Events herausfiltert:

Für welche Aufgaben eignet sich dieser Ansatz

Die Bibliothek wurde für eine bestimmte Klasse von Anwendungen erstellt. Du solltest sie nicht in eine normale CRUD-API auf Fastify oder Express schleppen, wo sie eine zusätzliche Abstraktionsschicht schaffen würde.

Aber Cordis passt perfekt in die folgenden Szenarien:

  1. Modulare CLI-Tools und Generatoren. Wenn Benutzer npm-Pakete liefern können, die Befehle oder Build-Pipelines erweitern.
  2. Desktop-Anwendungen auf Electron/Tauri. Für die Organisation eines Addon-Systems und Themes, die im laufenden Betrieb aktiviert und deaktiviert werden können, ohne das Fenster neu zu laden.
  3. Bots und Integrations-Hubs. Wenn ein Service mit einem Dutzend verschiedener Plattformen kommuniziert (Telegram, Discord, Slack) und jeder Adapter ein isoliertes Leben führen muss.
  4. Automatisierungstools. Wo Prozesse von Benutzern dynamisch über eine Weboberfläche oder YAML-Dateien konfiguriert werden.

Fallstricke und Nachteile

Es gibt keine perfekten Tools und Cordis hat reichlich spezifische Feinheiten:

  • Steile Lernkurve. Die Dokumentation ist in trockener Sprache verfasst mit einer Fülle spezifischer Begriffe. Um die Konzepte von Scope-Merging und Nebeneffekten zu durchdringen, musst du den Quellcode sorgfältig lesen.
  • Ungewohnte API. Services an das Kontextobjekt durch TypeScripts Typ-Merging-Modul zu binden, kann zunächst verwirrend sein für diejenigen, die klassisches NestJS oder InversifyJS mit ihren Decorators gewohnt sind.
  • An ein mentales Modell gebunden. Wenn die Architektur deines Projekts keine häufige dynamische Code-Entladung beinhaltet, werden die Vorteile des eingebauten Effect-Managers durch die Codekomplexität zunichte gemacht.

Lohnt es sich, es zu probieren

Cordis ist ein interessantes Ingenieursprojekt, das auf die Verwaltung von Komponentenlebenszyklen fokussiert ist. Es löst elegant das Problem von Ressourcenlecks bei der Erstellung erweiterbarer Systeme.

Wenn du ein System mit einem entwickelten Ökosystem einsteckbarer Plugins entwirfst und einen zuverlässigen Mechanismus zur Nachverfolgung von Nebeneffekten out-of-the-box möchtest, fork das Repository [object Object] und studiere die Beispiele in den Tests. Es ist ein großartiges Beispiel dafür, wie man eine Microkernel-Architektur in purem TypeScript aufbauen kann.

Ähnliche Projekte