Vai al contenuto
Dependency Injection

Dependency Injection e Inversion of Control senza religione 🔌

Dependency Injection viene spesso raccontata come una liturgia fatta di container, annotation, reflection e altri modi fantasiosi di complicare ciò che poteva essere un costruttore con due parametri. Conviene ripartire dall’idea più importante: prima della DI viene la Inversion of Control.

Inversion of Control: chi controlla il flusso? 🧠

La IoC è il principio per cui il controllo del flusso non resta tutto nelle mani del tuo codice applicativo. Invece di chiamare ogni dipendenza direttamente e decidere ogni passo internamente, deleghi parte del controllo a un framework, a un runtime, a una callback o a un orchestratore.

Esempi quotidiani:

  • un web server invoca il tuo handler
  • un test runner esegue i tuoi test
  • un consumer di eventi riceve messaggi dal broker

Il principio, riassunto brutalmente, è: don’t call us, we’ll call you.

Dependency Injection: rendere esplicite le collaborazioni 🪛

La Dependency Injection è un modo pratico per applicare IoC alle dipendenze di un componente. Invece di crearle dentro il componente, le ricevi dall’esterno.

Vantaggi immediati:

  • dipendenze più visibili
  • test più semplici
  • sostituibilità di infrastruttura e collaboratori
  • meno accoppiamento ai dettagli concreti

Il punto non è l’astrazione in sé. Il punto è evitare che il dominio si costruisca da solo il proprio database, il proprio logger, il proprio client HTTP e magari anche la propria crisi esistenziale.

Constructor injection: il default sano 🏗️

La forma più chiara e noiosa di DI è spesso la migliore: constructor injection.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
type Clock interface {
  Now() time.Time
}

type Repository interface {
  Save(ctx context.Context, order Order) error
}

type Service struct {
  repo  Repository
  clock Clock
}

func NewService(repo Repository, clock Clock) Service {
  return Service{repo: repo, clock: clock}
}

Qui le dipendenze sono esplicite, leggibili e facili da sostituire nei test. Nessuna magia. Nessun santino del container.

Interfacce piccole, non interfacce decorative ✂️

La DI funziona bene quando i contratti sono piccoli e coerenti. Se definisci un’interfaccia da quindici metodi solo per poter dire che hai fatto dependency injection, stai spostando il problema, non risolvendolo.

Buona regola:

  • definisci interfacce dove c’è vera variabilità
  • tienile vicine al chiamante o al dominio che le usa
  • non introdurle per puro folclore architetturale

In Go questo è ancora più importante: interfacce piccole e locali tendono a funzionare meglio di gerarchie “enterprise” trapiantate a forza.

Service Locator: il cugino elegante del globale tossico 🚫

Il Service Locator viene spesso venduto come soluzione elegante. In pratica, molto spesso è uno stato globale con un vestito migliore.

Problemi tipici:

  • le dipendenze reali non sono visibili dalla firma
  • capire cosa usa un componente richiede seguire registrazioni e wiring sparsi
  • i test diventano più fragili e dipendono dal setup globale

Se per capire di cosa ha bisogno un servizio devo leggere mezzo bootstrap applicativo, non sto guardando buon design. Sto facendo spelunking.

DI container: utili, ma non gratis 🧰

Un container può avere senso in applicazioni grandi o molto modulari, soprattutto quando il wiring diventa ripetitivo. Però non è gratuito:

  • nasconde il grafo delle dipendenze
  • sposta errori da compile time a runtime
  • rende più opaco l’avvio dell’applicazione

Se la tua applicazione ha dieci dipendenze esplicite e un main leggibile, probabilmente hai già tutto quello che ti serve. Un container non è automaticamente maturità. A volte è solo decorazione industriale.

Quando la DI è davvero utile 🎯

La DI porta valore soprattutto quando:

  • vuoi isolare il dominio dall’infrastruttura: repository, store, gateway e simili non dovrebbero essere costruiti dentro il dominio stesso
  • hai collaboratori sostituibili: clock di sistema, client HTTP, notificatori, generatori di ID, storage provider
  • vuoi test veloci e mirati: con dipendenze iniettate puoi sostituire l’I/O reale con un double leggero, senza avviare l’intera applicazione
  • alcune dipendenze cambiano più spesso di altre: il meccanismo di notifica, il provider di storage, il sistema di autenticazione sono buoni candidati

Il segnale più affidabile che vale la pena iniettare una dipendenza è semplice: ha senso averne un’implementazione diversa in test rispetto a produzione? Se la risposta è sì, iniettarla. Se no, probabilmente va bene usarla direttamente, e la firma del codice ne guadagna in chiarezza.

Non serve per ogni funzione, struct o package. Un servizio che calcola l’IVA non ha bisogno di un’interfaccia per la moltiplicazione. Se non c’è variabilità reale, una dipendenza concreta è spesso più leggibile e altrettanto corretta.

Segnali che stai esagerando 🚨

Quando la DI smette di aiutare e inizia a costare, si riconosce abbastanza bene:

  • interfacce create prima ancora di avere un secondo implementatore plausibile: una UserRepository con un solo implementatore concreto non ha bisogno di un contratto separato fin dal giorno uno
  • costruttori che assemblano metà sistema solo per restituire un wrapper sottile: se il grafo delle dipendenze cresce più velocemente della logica, qualcosa non torna
  • container ovunque, ma responsabilità poco chiare: il container non è un sostituto per un design ragionevole; è un modo per gestire il wiring, non per evitare di pensarci
  • doppioni, mock e boilerplate che costano più del problema iniziale: se aggiungere una feature richiede di aggiornare tre mock, due interfacce e un file di registration, l’overhead ha superato il beneficio

La DI è utile perché rende esplicite le dipendenze. Se il risultato è invece nasconderle dietro uno strato di magia, il meccanismo si è inceppato.

In sintesi 🧾

La Inversion of Control è il concetto. La Dependency Injection è una tecnica utile per applicarlo alle dipendenze. Il criterio non è la purezza ideologica, ma la riduzione dell’accoppiamento e del costo del cambiamento.

Se vuoi il seguito naturale, abbina questa guida ai principi di software design, a S.O.L.I.D. e alla guida sugli anti-pattern di design.

Ultimo aggiornamento il