Cordis en plugin-architectuur in TypeScript zonder de hoofdpijn
Als je ooit een uitbreidbare applicatie hebt geschreven in Node.js of TypeScript, ben je waarschijnlijk tegen dezelfde valkuilen aangelopen. Een gebruiker of systeem verbindt een plugin. De plugin hangt vijf event listeners, start een paar timers, registreert zijn routes en injecteert een service. En dan wordt de plugin uitgeschakeld of bijgewerkt op het moment zelf.
Wat gebeurt er dan? Juist, geheugenlekken. Listeners blijven hangen, timers tikken op de achtergrond, context-referenties voorkomen dat de garbage collector geheugen opschoont. In Node.js verandert het beheren van de levenscyclus van afhankelijkheden vaak in handmatig werk.
Enkele tijd geleden werden de ontwikkelaars van het Koishi chatbot-framework met precies dit probleem geconfronteerd. Ze moesten een kern creëren waar honderden plugins van derden konden starten, isoleren, elkaar konden vervangen en ontladen zonder het proces te herstarten. Zo werd het Cordis-framework geboren.
Wat is Cordis eigenlijk
De makers noemen hun project een "meta-framework van spatio-temporele composeerbaarheid". Het klinkt slim en pretentieus, maar de essentie is eigenlijk aards.
Cordis combineert een dependency injection container (IoC), een event bus en een hiërarchische contextboom. Elke plugin of service leeft binnen zijn eigen context. Als die context wordt vernietigd, ruimt Cordis automatisch absoluut alle bronnen ervoor op: verwijdert event handlers, doodt timers en verwijdert gecreëerde services.
Er is geen magie hier, maar er is duidelijke discipline: als een plugin de methoden [object Object], [object Object], of [object Object] gebruikt, zorgt het framework zelf voor het opruimen van side effects.
Hoe het contextmodel werkt
Het centrale concept in de library is [object Object]. Het is niet zomaar een plat object met instellingen, maar een vertakkende boom.
Wanneer je [object Object] aanroept, maakt het framework een child context (fork). De child context erft de services van de parent maar slaat zijn eigen referenties op naar geregistreerde bronnen.
Als je Plugin A uitschakelt, klapt de child context in elkaar. De [object Object] handler wordt verwijderd van de gedeelde event bus, terwijl de Database Service en Plugin B rustig blijven werken.
Services en typing in TypeScript
Services in Cordis worden gedeclareerd door overerving van de base class [object Object]. Dit maakt ze direct toegankelijk via context properties terwijl strikte typing behouden blijft:
De [object Object] constructie lost het probleem van module laadvolgorde op. Als de database service asynchroon initialiseert of later verbindt, zal de afhankelijke plugin wachten op zijn gereedheid en zichzelf activeren.
Fijnafstelling van zichtbaarheidsbereik
In echte programma's zouden modules vaak niet op alles moeten reageren. Bijvoorbeeld, één handler is alleen nodig voor berichten van een specifiek kanaal of verzoeken met een bepaalde header.
Cordis introduceert het concept van filters via de [object Object] aanroep en context properties. Je kunt de zichtbaarheid van een service beperken tot een specifieke tak van de boom of een predikaat instellen dat ongewenste events uitfiltert:
Voor welke taken is deze aanpak geschikt
De library is gecreëerd voor een specifieke klasse van applicaties. Je zou het niet moeten slepen naar een reguliere CRUD API op Fastify of Express, waar het een extra abstractielaag zou creëren.
Maar Cordis past perfect in de volgende scenario's:
- Modulaire CLI-hulpprogramma's en generators. Wanneer gebruikers npm-pakketten kunnen leveren die commando's of build-pipelines uitbreiden.
- Desktop applicaties op Electron/Tauri. Voor het organiseren van een addon-systeem en thema's die op het moment zelf kunnen worden ingeschakeld en uitgeschakeld zonder het venster te herladen.
- Bots en integratie-hubs. Als een service communiceert met een dozijn verschillende platforms (Telegram, Discord, Slack), en elke adapter een geïsoleerd leven moet leiden.
- Automatiseringstools. Waar processen door gebruikers dynamisch worden geconfigureerd via een webinterface of YAML-bestanden.
Valkuilen en nadelen
Er zijn geen perfecte tools, en Cordis heeft genoeg specifieke nuances:
- Steile leercurve. De documentatie is geschreven in droge taal met een overvloed aan specifieke termen. Om door de concepten van scope merging en side effects te komen, moet je de broncode zorgvuldig lezen.
- Onbekende API. Services binden aan het context object via TypeScript's type merging module kan in het begin verwarrend zijn voor degenen die gewend zijn aan klassieke NestJS of InversifyJS met hun decorators.
- Verbonden aan een mentaal model. Als de architectuur van je project geen frequente dynamische code-ontlading inhoudt, worden de voordelen van de ingebouwde effect manager tenietgedaan door codecomplexiteit.
Is het de moeite waard om te proberen
Cordis is een interessant engineering project gericht op component levenscyclusbeheer. Het lost elegant het probleem van resource leaks op bij het creëren van uitbreidbare systemen.
Als je een systeem ontwerpt met een ontwikkeld ecosysteem van pluggable plugins en een betrouwbaar mechanisme wilt voor het tracken van side effects out of the box, fork de repository [object Object] en bestudeer de voorbeelden in de tests. Het is een geweldig voorbeeld van hoe je een microkernel architectuur kunt bouwen in pure TypeScript.
Gerelateerde projecten