Sobes.tech
Senior

Hogyan oldja meg a perifériás komponensek függőségeinek használatával kapcsolatos problémát a kernelben?

sobes.tech MI

Válasz az MI-től

A perifériás komponensek (adapterek, interfészek) függőségeinek problémáját a magban (domain logikában) olyan architektúrákban, mint a Tiszta Architektúra vagy a DDD, a Függőségi Fordított Elv (Dependency Inversion Principle - DIP) segítségével lehet megoldani.

A DIP lényege:

  1. A felső szintű moduloknak nem szabad függniük az alsó szintű moduloktól. Mindkettőnek az absztrakciókra kell támaszkodnia.
  2. Az absztrakciók nem függhetnek a részletektől. A részleteknek az absztrakcióktól kell függeniük.

Gyakorlati alkalmazás:

  • A mag deklarálja az interfészeket: A mag határozza meg azokat az absztrakciókat (interfészek vagy absztrakt osztályok), amelyek leírják a perifériás komponensektől szükséges funkciókat.
  • A perifériás komponensek megvalósítják az interfészeket: Külső modulok (adatbázis adapterek, HTTP-kliens, fájlrendszerek stb.) ezeket az interfészeket valósítják meg.
  • Függőségek injektálása (Dependency Injection - DI): A perifériás komponensek kívülről kerülnek be a magba, általában az alkalmazás magasabb szintjén (pl. kompozíciós gyökér vagy DI-konténer használatával). A mag csak az absztrakciókkal dolgozik.

Példa a DIP használatára Node.js-ben:

Tegyük fel, hogy van egy mag, amely hozzáférést igényel az adatokhoz.

// src/domain/interfaces/UserRepository.ts
// A mag deklarálja az interfészt
export interface UserRepository {
  getUserById(id: string): Promise<User | null>;
}
// src/domain/entities/User.ts
// A mag entitása
export class User {
  constructor(public id: string, public name: string) {}
}
// src/domain/usecases/GetUserUseCase.ts
// A mag use case-je az absztrakciótól függ, azaz a UserRepository-tól
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
// Egy perifériás komponens, amely megvalósítja a mag interfészét
import { UserRepository } from '../../domain/interfaces/UserRepository';
import { User } from '../../domain/entities/User';

export class MongoDbUserRepository implements UserRepository {
  // Itt lesz a MongoDB-vel való kommunikáció logikája

  async getUserById(id: string): Promise<User | null> {
    console.log(`Felhasználó lekérése id ${id}-vel a MongoDB-ből...`);
    // Adatok lekérésének szimulációja
    if (id === '123') {
      return new User('123', 'John Doe');
    }
    return null;
  }
}
// src/app/CompositionRoot.ts
// A kompozíciós gyökér összegyűjti az összes függőséget
import { GetUserUseCase } from '../domain/usecases/GetUserUseCase';
import { MongoDbUserRepository } from '../infrastructure/database/MongoDbUserRepository';

// Függőség injektálás: A mag megkapja a MongoDbUserRepository implementációját az interfészen keresztül
const userRepository = new MongoDbUserRepository();
const getUserUseCase = new GetUserUseCase(userRepository);

// Most már használhatjuk a use case-t
async function run() {
  const user = await getUserUseCase.execute('123');
  if (user) {
    console.log(`Talált felhasználó: ${user.name}`);
  } else {
    console.log('Felhasználó nem található');
  }
}

run();

Az ilyen megközelítés előnyei:

  • Függetlenség a magtól: A mag nem tud a perifériás komponensek konkrét megvalósításairól.
  • Tesztelhetőség: A mag könnyen tesztelhető, mock-implementációk helyettesítésével.
  • Rugalmasság: Könnyen cserélhető egy megvalósítás (pl. adatbázis) másikra, anélkül, hogy a mag kódját módosítanánk.
  • Támogatás: Egyszerűbb a kód karbantartása és módosítása.