Назад к вопросам
Senior
116
questionbank

Как решить проблему использования зависимостей периферийных компонентов в ядре?

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

Решить проблему использования зависимостей периферийных компонентов (адаптеров, интерфейсов) в ядре (доменной логике) в архитектуре, например, Чистой Архитектуры или DDD, можно с помощью принципа инверсии зависимостей (Dependency Inversion Principle - DIP).

Суть DIP:

  1. Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций.
  2. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Применение на практике:

  • Ядро объявляет интерфейсы: Ядро определяет абстракции (интерфейсы или абстрактные классы), которые описывают необходимые ему функции от периферийных компонентов.
  • Периферийные компоненты реализуют интерфейсы: Внешние модули (адаптеры для баз данных, HTTP-клиенты, файловые системы и т.д.) реализуют эти интерфейсы.
  • Внедрение зависимостей (Dependency Injection - DI): Периферийные компоненты внедряются в ядро извне, обычно на более высоком уровне приложения (например, в композиционном корне или с использованием DI-контейнера). Ядро работает только с абстракциями.

Пример использования DIP в Node.js:

Допустим, у нас есть ядро, которому нужен доступ к данным.

// 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(`Fetching user with id ${id} from 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(`Found user: ${user.name}`);
  } else {
    console.log('User not found');
  }
}

run();

Преимущества такого подхода:

  • Независимость ядра: Ядро не знает о конкретных реализациях периферийных компонентов.
  • Тестируемость: Ядро легко тестировать, подставляя мок-реализации интерфейсов.
  • Гибкость: Можно легко заменить одну реализацию (например, базу данных) на другую, не меняя код ядра.
  • Поддержка: Упрощается сопровождение и изменение кода.