Iniezione delle dipendenze e contenitore DI

2

Ho implementato clean architecture per la mia app e ho alcune domande.

In genere, il puro DI viene discusso per un modello di Localizzazione del servizio, perché è molto esplicito e più verificabile.

Tuttavia, mi piace l'idea di avere un oggetto che contenga tutti i miei servizi o tutti i miei repository e posso semplicemente iniettare quel singolo oggetto invece di iniettare i vari servizi / repository per facilitare lo sviluppo.

Ma non sono sicuro se questo segue ancora i principi SOLIDI.

Ad esempio, ecco un DI tipico:

// Database

type Database interface {
    Insert(...)
    Delete(...)
}

// User object

type User struct {}

// User Service

type UserService interface {
    Create(user User)
}

func NewUserService(userRepo UserRepository) UserService {
    return &userService{userRepo: userRepo}
}

type userService struct {
    userRepo UserRepository
}

func (s *userService) Create(user User) {
    s.userRepo.Create(user)
}

// User Repository

type UserRepository interface {
    Create(user User)
}

func NewUserRepository(db Database) UserRepository {
    return &userRepository{db: db}
}

type userRepository struct {
    db Database
}

func (r *userRepository) Create(user User) {
    r.db.Insert(user)
}

main() {
    // Create db
    db := database.New(...)

    // Create repo by injecting db
    userRepo := NewUserRepository(db)

    // Create service by injecting repo
    userService := NewUserService(userRepo)
}

Come vedi nell'esempio sopra, sta seguendo un approccio DI puro.

Il problema che ho con questo è che gli argomenti possono arrivare a oltre 30 cose che vengono iniettate in un'applicazione più grande.

Quindi mi sono imbattuto in un modello diverso che fondamentalmente inietta un singolo ServiceFactory o RepositoryFactory , e quelli conterranno i puntatori a tutte le altre classi di servizi / repository più granulari.

Quindi il nuovo codice può essere cambiato in qualcosa di simile:

// User Service

type UserService interface {
    Create(user User)
}

func NewUserService(repos RepositoryFactory) UserService {
    return &userService{repos: repos}
}

type userService struct {
    repos RepositoryFactory
}

func (s *userService) Create(user User) {
    s.repos.UserRepository.Create(user)
}

// Repository factory

type RepositoryFactory struct {
    UserRepository UserRepository
}

func NewRepositoryFactory(db Database) RepositoryFactory {
    return RepositoryFactory{
        UserRepository: NewUserRepository(db),
    }
}

main() {
    // Create db
    db := database.New(...)

    // Create repo factory
    repos := NewRepositoryFactory(db)

    // Create services by injecting repo factory
    userService := NewUserService(repos)
    fooService := NewFooService(repos)
    barService := NewBarService(repos)
    bazService := NewBazService(repos)
}

Questo approccio conferma ancora i principi SOLID? Rende lo sviluppo molto più veloce e utilizza ancora il DI, quindi non vedo perché non possa ancora essere testato perché potrei semplicemente prendere in giro la classe factory del repository nello stesso modo in cui avrei preso in giro le singole classi del repository ...

Consiglieresti questo approccio, perché / perché no? Grazie.

    
posta Lansana 07.03.2018 - 18:58
fonte

2 risposte

4

Se si inserisce una "ServiceFactory", che contiene tutte le dipendenze per tutte le parti del codice, in tutte le parti del codice, si utilizza l'integrazione delle dipendenze, ma non l'inversione di dipendenza.

L'inversione delle dipendenze segue le regole di "tell, do not ask". Inverte il normale "Ho una dipendenza, quindi andrò a cercarlo" in "hai una dipendenza, eccola qui". Quindi se fornisci un oggetto di grandi dimensioni che contiene tutto per un tipo, funzione o altro, lo stai forzando di nuovo in "Ho una dipendenza, quindi andrò a cercarlo in questo grande oggetto".

E poiché D di SOLID riguarda l'inversione di dipendenza, allora sì la stai violando.

MA avere dozzine di parametri per molte funzioni porterà rapidamente a un codice difficile da leggere. SOLID è un buon insieme di principi, ma la leggibilità è il re. Quindi, se semplifica il tuo codice per utilizzare un oggetto di raccolta di questo tipo, dovresti prendere in considerazione l'utilizzo di esso e SOLID essere dannato.

Detto questo, una delle chiavi per usare IoC e avere un contenitore è che tu passi la responsabilità per l'impianto idraulico delle dipendenze al contenitore. Elimina quindi la necessità di scrivere un sacco di codice illeggibile e rispetta il principio "racconta, non chiedere" assicurando che ogni funzione e tipo ottenga solo ciò di cui ha bisogno. Quindi varrebbe la pena guardare i contenitori IoC per vedere se uno di questi si adatta meglio.

    
risposta data 07.03.2018 - 19:31
fonte
3

Anche se puoi ancora prendere in giro il tuo RepositoryFactory per i test, rende difficile avere più di una singola dipendenza per tipo nel codice reale.

Ad esempio, supponiamo di avere una base di codice di grandi dimensioni con servizi may, ognuno ha le sue dipendenze come parte di RepositoryFactory .

Ma considera anche che ho due% diUserServices uno usa un DB come nel tuo codice, ma l'altro usa un repository che si connette a un servizio di riposo.

Ora ho bisogno di fare due% diRepositoryFactories.

Al momento hai hardcoded il tipo UserRepository che viene restituito, (rompendo il Principio di inversione delle dipendenze ) così, in realtà, avrò bisogno di creare un'interfaccia IRepositoryFactory , una IUserRepository interfaccia e due concrete RepositoryFactorys ,

Uno dei quali probabilmente non implementa tutti gli altri tipi di repository. rompere il LSP

Anche IRepositoryFactory con la sua ampia raccolta di vari repository che non hanno alcuna relazione tra loro interromperà il principio di segregazione dell'interfaccia

La tua RepositoryFactory potrebbe funzionare su piccola scala, ma più le tue esigenze diventano complesse più inizierà a sembrare un contenitore DI.

Ora puoi considerare che "qualunque cosa tu faccia è ok finché funziona per te!". Ma la gente ha inventato i principi per riassumere l'esperienza vincente. Potresti non vederne il bisogno e forse inventerai un modo nuovo e migliore andando fuori pista.

Ma non è qualcosa da raccomandare fino a quando non è stato dimostrato da un uso difficile.

    
risposta data 07.03.2018 - 20:40
fonte