Senior
Jak vyřešit problém používání závislostí periferních komponent v jádře?
sobes.tech AI
Odpověď od AI
Řešení problému používání závislostí periferních komponent (adapterů, rozhraní) v jádře (doménové logice) v architekturách, například v Čisté architektuře nebo DDD, lze dosáhnout pomocí principu Inverze závislostí (Dependency Inversion Principle - DIP).
Podstata DIP:
- Moduly vyšší úrovně by neměly záviset na modulech nižší úrovně. Oba by měly záviset na abstrakcích.
- Abstrakce by neměly záviset na detailech. Detaily by měly záviset na abstrakcích.
Praktická aplikace:
- Jádro deklaruje rozhraní: Jádro definuje abstrakce (rozhraní nebo abstraktní třídy), které popisují potřebné funkce periferních komponent.
- Periferní komponenty implementují rozhraní: Externí moduly (adaptery pro databáze, HTTP klienty, souborové systémy atd.) implementují tato rozhraní.
- Vkládání závislostí (Dependency Injection - DI): Periferní komponenty jsou vkládány do jádra zvenčí, obvykle na vyšší úrovni aplikace (například v kompozičním kořeni nebo s použitím DI kontejneru). Jádro pracuje pouze s abstrakcemi.
Příklad použití DIP v Node.js:
Předpokládejme, že máme jádro, kterému je potřeba přístup k datům.
// src/domain/interfaces/UserRepository.ts
// Jádro deklaruje rozhraní
export interface UserRepository {
getUserById(id: string): Promise<User | null>;
}
// src/domain/entities/User.ts
// Entita jádra
export class User {
constructor(public id: string, public jméno: string) {}
}
// src/domain/usecases/GetUserUseCase.ts
// Use case jádra závisí na abstrakci 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
// Periferní komponent, který implementuje rozhraní jádra
import { UserRepository } from '../../domain/interfaces/UserRepository';
import { User } from '../../domain/entities/User';
export class MongoDbUserRepository implements UserRepository {
// Zde bude logika komunikace s MongoDB
async getUserById(id: string): Promise<User | null> {
console.log(`Načítání uživatele s id ${id} z MongoDB...`);
// Simulace získání dat
if (id === '123') {
return new User('123', 'John Doe');
}
return null;
}
}
// src/app/CompositionRoot.ts
// Kompoziční kořen shromažďuje závislosti
import { GetUserUseCase } from '../domain/usecases/GetUserUseCase';
import { MongoDbUserRepository } from '../infrastructure/database/MongoDbUserRepository';
// Vkládání závislosti: Jádro získává implementaci MongoDbUserRepository přes rozhraní
const userRepository = new MongoDbUserRepository();
const getUserUseCase = new GetUserUseCase(userRepository);
// Nyní můžeme použít případ použití
async function run() {
const user = await getUserUseCase.execute('123');
if (user) {
console.log(`Nalezen uživatel: ${user.name}`);
} else {
console.log('Uživatel nenalezen');
}
}
run();
Výhody tohoto přístupu:
- Nezávislost jádra: Jádro nezná konkrétní implementace periferních komponent.
- Testovatelnost: Jádro je snadno testovatelné pomocí mock implementací rozhraní.
- Flexibilita: Lze snadno nahradit jednu implementaci (například databázi) jinou, aniž by se měnil kód jádra.
- Podpora: Usnadňuje údržbu a změny kódu.