Senior
Come risolvere il problema dell'uso delle dipendenze dei componenti periferici nel kernel?
sobes.tech AI
Risposta dell'AI
Risolvere il problema dell'uso delle dipendenze dei componenti periferici (adattatori, interfacce) nel nucleo (logica di dominio) in architetture come l'Architettura Pulita o DDD, può essere fatto utilizzando il principio di Inversione delle Dipendenze (Dependency Inversion Principle - DIP).
L'essenza del DIP:
- I moduli di livello superiore non devono dipendere dai moduli di livello inferiore. Entrambi devono dipendere da astrazioni.
- Le astrazioni non devono dipendere dai dettagli. I dettagli devono dipendere dalle astrazioni.
Applicazione pratica:
- Il nucleo dichiara interfacce: Il nucleo definisce astrazioni (interfacce o classi astratte) che descrivono le funzioni necessarie dei componenti periferici.
- I componenti periferici implementano le interfacce: I moduli esterni (adattatori per database, client HTTP, sistemi di file, ecc.) implementano queste interfacce.
- Iniezione delle dipendenze (Dependency Injection - DI): I componenti periferici vengono iniettati nel nucleo dall'esterno, di solito a un livello superiore dell'applicazione (ad esempio, nella radice di composizione o usando un contenitore DI). Il nucleo lavora solo con astrazioni.
Esempio di utilizzo di DIP in Node.js:
Supponiamo di avere un nucleo che necessita di accesso ai dati.
// src/domain/interfaces/UserRepository.ts
// Il nucleo dichiara l'interfaccia
export interface UserRepository {
getUserById(id: string): Promise<User | null>;
}
// src/domain/entities/User.ts
// Entità del nucleo
export class User {
constructor(public id: string, public name: string) {}
}
// src/domain/usecases/GetUserUseCase.ts
// Caso d'uso del nucleo dipende dall'astrazione UserRepository
import { UserRepository } from '../interfaces/UserRepository';
import { User } from '../entities/User';
export class GetUserUseCase {
constructor(private userRepository: UserRepository) {}
async execute(id: string): Promise<User | null> {
return this.userRepository.getUserById(id);
}
}
// src/infrastructure/database/MongoDbUserRepository.ts
// Componente periferico che implementa l'interfaccia del nucleo
import { UserRepository } from '../../domain/interfaces/UserRepository';
import { User } from '../../domain/entities/User';
export class MongoDbUserRepository implements UserRepository {
// Qui ci sarà la logica di interazione con MongoDB
async getUserById(id: string): Promise<User | null> {
console.log(`Fetching user with id ${id} from MongoDB...`);
// Simulazione di recupero dati
if (id === '123') {
return new User('123', 'John Doe');
}
return null;
}
}
// src/app/CompositionRoot.ts
// La radice di composizione raccoglie le dipendenze
import { GetUserUseCase } from '../domain/usecases/GetUserUseCase';
import { MongoDbUserRepository } from '../infrastructure/database/MongoDbUserRepository';
// Iniezione di dipendenza: Il nucleo riceve l'implementazione MongoDbUserRepository tramite l'interfaccia
const userRepository = new MongoDbUserRepository();
const getUserUseCase = new GetUserUseCase(userRepository);
// Ora possiamo usare il caso d'uso
async function run() {
const user = await getUserUseCase.execute('123');
if (user) {
console.log(`Found user: ${user.name}`);
} else {
console.log('User not found');
}
}
run();
Vantaggi di questo approccio:
- Indipendenza del nucleo: Il nucleo non conosce le implementazioni specifiche dei componenti periferici.
- Testabilità: Il nucleo è facile da testare, sostituendo le implementazioni mock delle interfacce.
- Flessibilità: È facile sostituire un'implementazione (ad esempio, il database) con un'altra, senza modificare il codice del nucleo.
- Manutenzione: La gestione e la modifica del codice sono semplificate.