Sobes.tech
Senior

Ինչպես լուծել միջուկում պերիֆերական բաղադրիչների կախվածությունների օգտագործման խնդիրը?

sobes.tech AI

Պատասխան AI-ից

Հարցը լուծելու համար, թե ինչպես օգտագործել արտաքին կոմպոնենտների (ապարատների, ինտերֆեյսների) կախվածությունները միջուկում (դոմենային տրամաբանություն)՝ օրինակ՝ մաքուր ճարտարապետություն կամ DDD, կարելի է կիրառել կապակցվածության հակադրության սկզբունքը (Dependency Inversion Principle - DIP):

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
// Միջուկի use case-ը կախված է աբստրակցիայից 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(`MongoDB-ից օգտվողի ստացում id ${id}-ով...`);
    // Դատարկացման մոդել
    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);

// Այժմ կարող ենք օգտագործել use case-ը
async function run() {
  const user = await getUserUseCase.execute('123');
  if (user) {
    console.log(`գտնվել է օգտվող՝ ${user.name}`);
  } else {
    console.log('Օգտվողը չի գտնվել');
  }
}

run();

Այս մոտեցման առավելությունները՝

  • Միջուկի անկախություն: Միջուկը չի գիտակցում արտաքին կոմպոնենտների կոնկրետ իրականացումները։
  • Թեստավորման հեշտություն: Միջուկը հեշտ է թեստավորել՝ օգտագործելով մոկ-իրականացումներ։
  • Գործունակություն: Լավ է փոխարինել մեկ իրականացում (օրինակ՝ տվյալների բազա) մյուսով՝ առանց միջուկի կոդը փոխելու։
  • Աջակցություն: Պարզեցնում է կոդի սպասարկումը և փոփոխությունը։