Sobes.tech
Senior

როგორ გადავწყვიტოთ ბირთვში პერიფერიული კომპონენტების დამოკიდებულებების გამოყენების პრობლემა?

sobes.tech AI

პასუხი AI-სგან

მოსაგვარად დამოკიდებულების პრინციპის (Dependency Inversion Principle - DIP) გამოყენებით შესაძლებელია პრობლემის გადაჭრა გარე კომპონენტების (ადაპტერების, ინტერფეისების) გამოყენებაში ბირთვში (დომენური ლოგიკაში) არქიტექტურაში, მაგალითად, წმინდა არქიტექტურაში ან DDD-ში.

DIP-ის არსი:

  1. ზედა დონეზე მოდულები არ უნდა იყოს დამოკიდებული ქვედა დონეზე მოდულებზე. ორივე უნდა იყოს დამოკიდებული აბსტრაქციებზე.
  2. აბსტრაქციები არ უნდა იყოს დამოკიდებული დეტალებზე. დეტალები უნდა იყოს დამოკიდებული აბსტრაქციებზე.

პრაქტიკაში გამოყენება:

  • ბირთვი აცხადებს ინტერფეისებს: ბირთვი განსაზღვრავს აბსტრაქციებს (ინტერфეისებს ან აბსტრაქტულ კლასებს), რომლებიც აღწერენ მას სჭირდება გარე კომპონენტებისგან.
  • გარე კომპონენტები ახორციელებენ ინტერფეისებს: გარე მოდულები (ადაპტერები ბაზების მონაცემებისთვის, HTTP-კლიანტები, ფაილური სისტემები და ა.შ.) ახორციელებენ ამ ინტერფეისებს.
  • დამოკიდებულების ინექცია (Dependency Injection - DI): გარე კომპონენტები ინექციონირებენ ბირთვში გარედან, ჩვეულებრივ, უფრო მაღალ დონეზე აპლიკაციის (მაგალითად, კომპოზიციური კორნიდან ან DI-კონტეინერიდან). ბირთვი მუშაობს მხოლოდ აბსტრაქციებზე.

Node.js-ში DIP-ის გამოყენების მაგალითი:

დავუშვათ, გვაქვს ბირთვი, რომელსაც სჭირდება მონაცემებზე წვდომა.

// src/domain/interfaces/UserRepository.ts
// ბირთვი აცხადებს ინტერფეისს
export interface UserRepository {
  getUserById(id: string): Promise<User | null>;
}
// src/domain/entities/User.ts
// ბირთვის ერთეული
export class User {
  constructor(public id: string, public name: string) {}
}
// src/domain/usecases/GetUserUseCase.ts
// ბირთვი დამოკიდებულია აბსტრაქციაზე 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
// გარე კომპონენტი, რომელიც ახორციელებს ბირთვის ინტერფეისს
import { UserRepository } from '../../domain/interfaces/UserRepository';
import { User } from '../../domain/entities/User';

export class MongoDbUserRepository implements UserRepository {
  // აქ იქნება ურთიერთქმედების ლოგიკა MongoDB-სთან

  async getUserById(id: string): Promise<User | null> {
    console.log(`მომხმარებლის მიღება id ${id}-ით MongoDB-დან...`);
    // მონაცემების მიღების იმიტაცია
    if (id === '123') {
      return new User('123', 'John Doe');
    }
    return null;
  }
}
// src/app/CompositionRoot.ts
// კომპოზიციური კორნი აგროვებს დამოკიდებულებებს
import { GetUserUseCase } from '../domain/usecases/GetUserUseCase';
import { MongoDbUserRepository } from '../infrastructure/database/MongoDbUserRepository';

// დამოკიდებულების ინექცია: ბირთვი იღებს MongoDbUserRepository-ის რეალიზაციას ინტერფეისის მეშვეობით
const userRepository = new MongoDbUserRepository();
const getUserUseCase = new GetUserUseCase(userRepository);

// ახლა შეგვიძლია გამოვიყენოთ იუზკეისი
async function run() {
  const user = await getUserUseCase.execute('123');
  if (user) {
    console.log(`მოძებნილი მომხმარებელი: ${user.name}`);
  } else {
    console.log('მომხმარებელი ვერ მოიძებნა');
  }
}

run();

ასეთი მიდგომის უპირატესობები:

  • ბირთვის დამოუკიდებლობა: ბირთვი არ იცის გარე კომპონენტების კონკრეტული რეალიზაციები.
  • ტესტირებადობა: ბირთვი ადვილად ტესტირებადია, მასზე mock-რეალიზაციებს აყენებ.
  • სხვაობა: მარტივად შეიძლება ერთი რეალიზაციის (მაგალითად, ბაზის) შეცვლა სხვა რეალიზაციით, ბირთვის კოდის შეცვლის გარეშე.
  • მხარდაჭერა: ადვილია კოდის შენარჩუნება და ცვლილება.