Cosa non abbiamo ancora costruito (e perché)
TinyDI arriva con esattamente due lifetime (Singleton, Transient), nessun child container, nessuna API di risoluzione asincrona e nessun sistema di plugin. Nessuna di queste è una lacuna accidentale — ognuna è un taglio di scope deliberato, reso esplicito nei commenti del codice sorgente della libreria core e approfondito nella FAQ e nel ROADMAP.md del progetto. Questo post racconta il ragionamento reale, non un vago "forse più avanti".
La regola generale: il minimalismo vince quando è in conflitto con la predisposizione a estensioni future
Ovunque restare minimali oggi e restare aperti a una specifica estensione futura tirassero in direzioni diverse, ha vinto il minimalismo — è stata la priorità esplicita per tutto il lavoro sulla libreria core. È una priorità facile da dichiarare e più difficile da rispettare: "sarebbe utile" è vero per quasi ogni funzionalità che si potrebbe aggiungere a un container DI, ed è esattamente per questo che non può essere il criterio.
Ciò che rende difendibili i tagli qui sotto, e non solo un "non abbiamo fatto in tempo", è che ciascuna delle quattro estensioni è stata verificata rispetto al design attuale ed è risultata comunque raggiungibile senza un breaking change. Un taglio di scope irreversibile è una scommessa molto più rischiosa di uno che si può ancora disfare più avanti.
Lifetime Scoped
export enum ServiceLifetime {
Singleton,
Transient,
// Scoped is a natural future extension (per-request/per-operation
// instances), but it is not implemented here — see the project ROADMAP.
}
Un lifetime per-request o per-operazione è l'estensione più richiesta nei container basati su reflection, ed è quella su cui gli utenti chiedono di più (vedi la FAQ). Non è entrata in questa versione perché cambia la forma stessa della risoluzione: il caching del singleton in resolveFactory dovrebbe diventare consapevole dello scope invece di essere un semplice booleano per registrazione. Per lo più additiva, ma non gratuita.
Child container
Questa è quasi gratis, ed è gratis proprio grazie alla decisione sulla firma della factory descritta in il post su reflect-metadata. Dato che una Factory<T> riceve già il container in modo esplicito invece di catturare stato a livello di modulo, nulla internamente presuppone che ci sia sempre e solo un'istanza di container in gioco. Una relazione parent/child — un child che ricade sulle registrazioni del parent quando non ha le proprie — è additiva rispetto a quanto esiste oggi, non un redesign.
Risoluzione asincrona
Factory<T> è sincrona per scelta di design: (container: Container) => T, non => Promise<T>. Questa è l'unica delle quattro estensioni che non è davvero gratuita. Oggi il caching del singleton memorizza T | undefined più un flag booleano; supportare correttamente factory asincrone significa anche mettere in cache la Promise<T> in corso, in modo che due chiamate concorrenti a resolve() per lo stesso singleton non ancora costruito attendano la stessa promise invece di correre a costruire due istanze. È un vero cambiamento interno a resolveFactory, non solo un metodo aggiunto.
La soluzione alternativa oggi
Puoi risolvere come servizio stesso un valore che restituisce una promise — Token<Promise<T>> — e fare l'await al punto di chiamata. Funziona, ma non offre il comportamento "await una volta, cache una volta" che una vera API asincrona garantirebbe.
Plugin
Un sistema di plugin è la più "gratuita" delle quattro estensioni: un plugin sarebbe semplicemente una funzione che chiama registerInstance/registerFactory su un container che gli viene passato, usando solo API pubbliche già esistenti. Completamente additiva, nessun cambiamento interno richiesto — semplicemente non è stata ancora costruita perché nulla nello scope attuale ne ha bisogno.
Perché scriverlo, in fondo
Una roadmap che elenca solo nomi di funzionalità ("Scoped", "Child Container", "Async", "Plugin") si legge come una wish list. Dichiarare per ciascuna se è additiva o richiede un redesign è un segnale più onesto per chiunque debba decidere se dipendere da TinyDI oggi: tre di queste quattro estensioni possono arrivare come aggiunte in stile 2.x senza rompere codice esistente; una (l'async) è una domanda aperta che richiederebbe un vero lavoro di design prima di essere rilasciata.
Questa distinzione conta più della lista di funzionalità in sé, e vale oltre questa singola libreria: qualunque progetto che tiene una lista di "non ancora" dovrebbe poter dire, per ciascuna voce, se è additiva o richiede un redesign. Se non sai dirlo, non sai davvero quanto ti stia costando quel taglio di scope.