Senior
Comment résoudre le problème de l'utilisation des dépendances des composants périphériques dans le noyau?
sobes.tech IA
Réponse de l'IA
Résoudre le problème de l'utilisation des dépendances des composants périphériques (adaptateurs, interfaces) dans le cœur (logique de domaine) dans des architectures telles que l'Architecture Propre ou DDD, peut être réalisé en utilisant le principe de l'inversion des dépendances (Dependency Inversion Principle - DIP).
L'essence du DIP :
- Les modules de niveau supérieur ne doivent pas dépendre des modules de niveau inférieur. Les deux doivent dépendre d'abstractions.
- Les abstractions ne doivent pas dépendre des détails. Les détails doivent dépendre des abstractions.
Application pratique :
- Le cœur déclare des interfaces : Le cœur définit des abstractions (interfaces ou classes abstraites) qui décrivent les fonctions nécessaires des composants périphériques.
- Les composants périphériques implémentent des interfaces : Les modules externes (adaptateurs pour bases de données, clients HTTP, systèmes de fichiers, etc.) implémentent ces interfaces.
- Injection de dépendances (Dependency Injection - DI) : Les composants périphériques sont injectés dans le cœur depuis l'extérieur, généralement à un niveau supérieur de l'application (par exemple, dans la racine de composition ou en utilisant un conteneur DI). Le cœur ne travaille qu'avec des abstractions.
Exemple d'utilisation du DIP en Node.js :
Supposons que nous ayons un cœur qui a besoin d'accéder aux données.
// src/domain/interfaces/UserRepository.ts
// Le cœur déclare l'interface
export interface UserRepository {
getUserById(id: string): Promise<User | null>;
}
// src/domain/entities/User.ts
// Entité du cœur
export class User {
constructor(public id: string, public name: string) {}
}
// src/domain/usecases/GetUserUseCase.ts
// Cas d'utilisation du cœur dépend de l'abstraction 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
// Composant périphérique qui implémente l'interface du cœur
import { UserRepository } from '../../domain/interfaces/UserRepository';
import { User } from '../../domain/entities/User';
export class MongoDbUserRepository implements UserRepository {
// Ici se trouve la logique d'interaction avec MongoDB
async getUserById(id: string): Promise<User | null> {
console.log(`Fetching user with id ${id} from MongoDB...`);
// Simulation de récupération de données
if (id === '123') {
return new User('123', 'John Doe');
}
return null;
}
}
// src/app/CompositionRoot.ts
// La racine de composition assemble les dépendances
import { GetUserUseCase } from '../domain/usecases/GetUserUseCase';
import { MongoDbUserRepository } from '../infrastructure/database/MongoDbUserRepository';
// Injection de dépendance : Le cœur reçoit l'implémentation MongoDbUserRepository via l'interface
const userRepository = new MongoDbUserRepository();
const getUserUseCase = new GetUserUseCase(userRepository);
// Maintenant, nous pouvons utiliser le cas d'utilisation
async function run() {
const user = await getUserUseCase.execute('123');
if (user) {
console.log(`Found user: ${user.name}`);
} else {
console.log('User not found');
}
}
run();
Avantages de cette approche :
- Indépendance du cœur : Le cœur ne connaît pas les implémentations spécifiques des composants périphériques.
- Testabilité : Le cœur est facile à tester en remplaçant les implémentations mock des interfaces.
- Flexibilité : Il est facile de remplacer une implémentation (par exemple, la base de données) par une autre, sans changer le code du cœur.
- Maintenance : La gestion et la modification du code sont facilitées.