Senior
Wie löst man das Problem der Verwendung von Abhängigkeiten peripherer Komponenten im Kernel?
sobes.tech KI
Antwort von AI
Problemlösung für die Verwendung von Abhängigkeiten von Peripheriekomponenten (Adapter, Schnittstellen) im Kern (Domänenlogik) in Architekturen wie der Clean Architecture oder DDD kann durch das Prinzip der Abhängigkeitsumkehr (Dependency Inversion Principle - DIP) erfolgen.
Das Wesentliche von DIP:
- Module höherer Ebene sollten nicht von Modulen niedrigerer Ebene abhängen. Beide sollten von Abstraktionen abhängen.
- Abstraktionen sollten nicht von Details abhängen. Details sollten von Abstraktionen abhängen.
Praktische Anwendung:
- Der Kern deklariert Schnittstellen: Der Kern definiert Abstraktionen (Schnittstellen oder abstrakte Klassen), die die notwendigen Funktionen der Peripheriekomponenten beschreiben.
- Peripheriekomponenten implementieren Schnittstellen: Externe Module (Adapter für Datenbanken, HTTP-Clients, Dateisysteme usw.) implementieren diese Schnittstellen.
- Dependency Injection (DI): Peripheriekomponenten werden von außen in den Kern injiziert, meist auf einer höheren Ebene der Anwendung (z.B. im Kompositionswurzel oder mit einem DI-Container). Der Kern arbeitet nur mit Abstraktionen.
Beispiel für die Verwendung von DIP in Node.js:
Angenommen, wir haben einen Kern, der Zugriff auf Daten benötigt.
// src/domain/interfaces/UserRepository.ts
// Der Kern deklariert die Schnittstelle
export interface UserRepository {
getUserById(id: string): Promise<User | null>;
}
// src/domain/entities/User.ts
// Entität des Kerns
export class User {
constructor(public id: string, public name: string) {}
}
// src/domain/usecases/GetUserUseCase.ts
// Anwendungsfall des Kerns hängt von der Abstraktion UserRepository ab
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
// Peripheriekomponente, die die Schnittstelle des Kerns implementiert
import { UserRepository } from '../../domain/interfaces/UserRepository';
import { User } from '../../domain/entities/User';
export class MongoDbUserRepository implements UserRepository {
// Hier befindet sich die Logik der Interaktion mit MongoDB
async getUserById(id: string): Promise<User | null> {
console.log(`Fetching user with id ${id} from MongoDB...`);
// Simulierte Datenabfrage
if (id === '123') {
return new User('123', 'John Doe');
}
return null;
}
}
// src/app/CompositionRoot.ts
// Die Kompositionswurzel sammelt die Abhängigkeiten
import { GetUserUseCase } from '../domain/usecases/GetUserUseCase';
import { MongoDbUserRepository } from '../infrastructure/database/MongoDbUserRepository';
// Dependency Injection: Das Kernmodul erhält die Implementierung MongoDbUserRepository über die Schnittstelle
const userRepository = new MongoDbUserRepository();
const getUserUseCase = new GetUserUseCase(userRepository);
// Jetzt können wir den Anwendungsfall verwenden
async function run() {
const user = await getUserUseCase.execute('123');
if (user) {
console.log(`Found user: ${user.name}`);
} else {
console.log('User not found');
}
}
run();
Vorteile dieses Ansatzes:
- Unabhängigkeit des Kerns: Der Kern kennt keine spezifischen Implementierungen der Peripheriekomponenten.
- Testbarkeit: Der Kern ist leicht testbar, indem Mock-Implementierungen der Schnittstellen verwendet werden.
- Flexibilität: Es ist einfach, eine Implementierung (z.B. Datenbank) durch eine andere zu ersetzen, ohne den Kerncode zu ändern.
- Wartbarkeit: Die Wartung und Änderung des Codes wird erleichtert.