>_ DevTrendsit

Lingua

Home

Linguaggi

Sezioni

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

Cordis e architettura plugin in TypeScript senza mal di testa

Se hai mai scritto un'applicazione estensibile in Node.js o TypeScript, probabilmente hai incontrato gli stessi inconvenienti. Un utente o un sistema connette un plugin. Il plugin aggancia cinque event listener, avvia un paio di timer, registra le sue rotte e inietta un servizio. E poi il plugin viene disabilitato o aggiornato al volo.

Cosa succede dopo? Esatto, memory leak. Gli listener continuano a rimanere agganciati, i timer ticchettano in background, i riferimenti al contesto impediscono al garbage collector di liberare la memoria. In Node.js, gestire il ciclo di vita delle dipendenze spesso si trasforma in lavoro manuale noioso.

Un po' di tempo fa, gli sviluppatori del framework chatbot Koishi si sono trovati ad affrontare esattamente questo problema. Avevano bisogno di creare un core in cui centinaia di plugin di terze parti potessero avviarsi, isolarsi, sostituirsi a vicenda e scaricarsi senza riavviare il processo. È così che è nato il framework Cordis.

Cos'è esattamente Cordis

I creatori chiamano il loro progetto un "meta-framework di composabilità spazio-temporale". Suona intelligente e pretenzioso, ma l'essenza è in realtà molto pratica.

Cordis combina un contenitore di dependency injection (IoC), un event bus e un albero gerarchico di contesti. Ogni plugin o servizio vive all'interno del proprio contesto. Se quel contesto viene distrutto, Cordis ripulisce automaticamente tutte le risorse associate: rimuove gli event handler, termina i timer ed elimina i servizi creati.

Non c'è magia qui, ma c'è una disciplina chiara: se un plugin utilizza i metodi [object Object], [object Object] o [object Object], il framework si occupa da solo della pulizia degli effetti collaterali.

Come funziona il modello dei contesti

Il concetto centrale della libreria è [object Object]. Non è solo un oggetto piatto con impostazioni, ma un albero ramificato.

Quando chiami [object Object], il framework genera un contesto figlio (fork). Il contesto figlio eredita i servizi del genitore ma memorizza i propri riferimenti alle risorse registrate.

Se disabiliti il Plugin A, il suo contesto figlio collassa. Il gestore [object Object] viene rimosso dall'event bus condiviso, mentre il Database Service e il Plugin B continuano a funzionare serenamente.

Servizi e tipizzazione in TypeScript

I servizi in Cordis vengono dichiarati attraverso l'ereditarietà della classe base [object Object]. Questo li rende accessibili direttamente attraverso le proprietà del contesto mantenendo una tipizzazione rigorosa:

Il costrutto [object Object] risolve il problema dell'ordine di caricamento dei moduli. Se il servizio database si inizializza in modo asincrono o si connette successivamente, il plugin dipendente attenderà la sua prontezza e si attiverà.

Ottimizzazione fine della visibilità degli scope

Nei programmi reali, i moduli spesso non dovrebbero reagire a tutto. Ad esempio, un gestore è necessario solo per i messaggi di un canale specifico o per le richieste con un'intestazione particolare.

Cordis introduce il concetto di filtri attraverso la chiamata [object Object] e le proprietà del contesto. Puoi limitare la visibilità di un servizio a un ramo specifico dell'albero o impostare un predicato che filtri gli eventi indesiderati:

Per quali attività questo approccio è adatto

La libreria è stata creata per una classe specifica di applicazioni. Non dovresti trascinarla in una normale API CRUD su Fastify o Express, dove creerebbe un livello di astrazione extra.

Ma Cordis si adatta perfettamente ai seguenti scenari:

  1. Utility CLI modulari e generatori. Quando gli utenti possono fornire pacchetti npm che estendono comandi o pipeline di build.
  2. Applicazioni desktop su Electron/Tauri. Per organizzare un sistema di addon e temi che possono essere abilitati e disabilitati al volo senza ricaricare la finestra.
  3. Bot e hub di integrazione. Se un servizio comunica con una dozzina di piattaforme diverse (Telegram, Discord, Slack), e ogni adapter ha bisogno di vivere una vita isolata.
  4. Strumenti di automazione. Dove i processi sono configurati dagli utenti dinamicamente attraverso un'interfaccia web o file YAML.

Insidie e svantaggi

Non esistono strumenti perfetti, e Cordis ha parecchie sfumature specifiche:

  • Curva di apprendimento ripida. La documentazione è scritta in un linguaggio secco con un'abbondanza di termini specifici. Per comprendere i concetti di merging degli scope e degli effetti collaterali, dovrai leggere attentamente il codice sorgente.
  • API poco familiare. Associare i servizi all'oggetto contesto attraverso il type merging di TypeScript può confondere inizialmente chi è abituato ai classici NestJS o InversifyJS con i loro decorator.
  • Legato a un modello mentale. Se l'architettura del tuo progetto non prevede frequenti scaricamenti dinamici del codice, i benefici del gestore degli effetti integrato vengono annullati dalla complessità del codice.

Vale la pena provarlo

Cordis è un progetto di ingegneria interessante, focalizzato sulla gestione del ciclo di vita dei componenti. Risolve elegantemente il problema delle perdite di risorse nella creazione di sistemi estensibili.

Se stai progettando un sistema con un ecosistema sviluppato di plugin pluggable e vuoi un meccanismo affidabile per tracciare gli effetti collaterali out of the box, fai un fork del repository [object Object] e studia gli esempi nei test. È un ottimo esempio di come puoi costruire un'architettura microkernel in TypeScript puro.

Progetti correlati