Senior
Cum se rezolvă problema utilizării dependențelor componentelor periferice în nucleu?
sobes.tech AI
Răspuns de la AI
Rezolvarea problemei utilizării dependențelor componentelor periferice (adaptori, interfețe) în nucleu (logica de domeniu) în arhitecturi precum Arhitectura Curată sau DDD, poate fi realizată folosind principiul de Inversarea Dependențelor (Dependency Inversion Principle - DIP).
Esenta DIP:
- Modulele de nivel superior nu trebuie să depindă de modulele de nivel inferior. Ambele trebuie să depindă de abstracții.
- Abstracțiile nu trebuie să depindă de detalii. Detaliile trebuie să depindă de abstracții.
Aplicarea practică:
- Nucleul declară interfețe: Nucleul definește abstracții (interfețe sau clase abstracte) care descriu funcțiile necesare componentelor periferice.
- Componentele periferice implementează interfețe: Modulele externe (adaptori pentru baze de date, clienți HTTP, sisteme de fișiere etc.) implementează aceste interfețe.
- Injecția dependențelor (Dependency Injection - DI): Componentele periferice sunt injectate în nucleu din exterior, de obicei la un nivel superior al aplicației (de exemplu, în rădăcina de compoziție sau folosind un container DI). Nucleul lucrează doar cu abstracții.
Exemplu de utilizare DIP în Node.js:
Presupunem că avem un nucleu care are nevoie de acces la date.
// src/domain/interfaces/UserRepository.ts
// Nucleul declară interfața
export interface UserRepository {
getUserById(id: string): Promise<User | null>;
}
// src/domain/entities/User.ts
// Entitatea nucleului
export class User {
constructor(public id: string, public name: string) {}
}
// src/domain/usecases/GetUserUseCase.ts
// Cazul de utilizare al nucleului depinde de abstracția 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
// Componenta periferică care implementează interfața nucleului
import { UserRepository } from '../../domain/interfaces/UserRepository';
import { User } from '../../domain/entities/User';
export class MongoDbUserRepository implements UserRepository {
// Aici va fi logica de interacțiune cu MongoDB
async getUserById(id: string): Promise<User | null> {
console.log(`Fetching user with id ${id} from MongoDB...`);
// Simulare de obținere date
if (id === '123') {
return new User('123', 'John Doe');
}
return null;
}
}
// src/app/CompositionRoot.ts
// Rădăcina de compoziție adună dependențele
import { GetUserUseCase } from '../domain/usecases/GetUserUseCase';
import { MongoDbUserRepository } from '../infrastructure/database/MongoDbUserRepository';
// Injecție de dependență: Nucleul primește implementarea MongoDbUserRepository prin interfață
const userRepository = new MongoDbUserRepository();
const getUserUseCase = new GetUserUseCase(userRepository);
// Acum putem folosi cazul de utilizare
async function run() {
const user = await getUserUseCase.execute('123');
if (user) {
console.log(`Found user: ${user.name}`);
} else {
console.log('User not found');
}
}
run();
Avantajele acestei abordări:
- Independența nucleului: Nucleul nu cunoaște implementările specifice ale componentelor periferice.
- Testabilitate: Nucleul este ușor de testat, înlocuind implementările mock ale interfețelor.
- Flexibilitate: Se poate înlocui cu ușurință o implementare (de exemplu, baza de date) cu alta, fără a modifica codul nucleului.
- Întreținere: Se facilitează întreținerea și modificarea codului.