Sobes.tech
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:

  1. Moduly vyšší úrovně by neměly záviset na modulech nižší úrovně. Oba by měly záviset na abstrakcích.
  2. 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.