Назад к вопросам
Senior
116
questionbank
Как решить проблему использования зависимостей периферийных компонентов в ядре?
Sobes Copilot
Получайте ответы в реальном времени
Ответ от нейросети
sobes.tech AI
Решить проблему использования зависимостей периферийных компонентов (адаптеров, интерфейсов) в ядре (доменной логике) в архитектуре, например, Чистой Архитектуры или DDD, можно с помощью принципа инверсии зависимостей (Dependency Inversion Principle - DIP).
Суть DIP:
- Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций.
- Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Применение на практике:
- Ядро объявляет интерфейсы: Ядро определяет абстракции (интерфейсы или абстрактные классы), которые описывают необходимые ему функции от периферийных компонентов.
- Периферийные компоненты реализуют интерфейсы: Внешние модули (адаптеры для баз данных, 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();
Преимущества такого подхода:
- Независимость ядра: Ядро не знает о конкретных реализациях периферийных компонентов.
- Тестируемость: Ядро легко тестировать, подставляя мок-реализации интерфейсов.
- Гибкость: Можно легко заменить одну реализацию (например, базу данных) на другую, не меняя код ядра.
- Поддержка: Упрощается сопровождение и изменение кода.