Как в Domain-Driven Design (DDD) управляют зависимостями между слоями?

«Как в Domain-Driven Design (DDD) управляют зависимостями между слоями?» — вопрос из категории Архитектура, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В DDD управление зависимостями строится на двух ключевых принципах: Слоистой архитектуре (Layered Architecture) и Принципе инверсии зависимостей (Dependency Inversion Principle, DIP).

1. Слои и направление зависимостей: Архитектура делится на слои, где зависимости направлены внутрь, к домену.

  • Presentation Layer (UI): Зависит от Application Layer.
  • Application Layer (Use Cases/Сервисы): Зависит от Domain Layer.
  • Domain Layer (Бизнес-логика, Сущности): Не зависит ни от чего внешнего. Содержит абстракции (интерфейсы) для внешних сервисов.
  • Infrastructure Layer (Базы данных, API): Реализует интерфейсы, объявленные в Domain Layer, и зависит от него.

2. Практическая реализация на Swift: Абстракции объявляются в доменном слое, а их конкретные реализации — в инфраструктурном.

// Domain Layer: Объявляем интерфейс (абстракцию)
protocol UserRepository {
    func fetchUser(by id: String) -> User?
}

// Infrastructure Layer: Предоставляем реализацию
class CoreDataUserRepository: UserRepository {
    func fetchUser(by id: String) -> User? {
        // ... реализация с использованием CoreData
    }
}

// Application Layer: Используем абстракцию через Dependency Injection
class FetchUserUseCase {
    private let repository: UserRepository // Зависим от абстракции

    init(repository: UserRepository) {
        self.repository = repository
    }

    func execute(userId: String) -> User? {
        return repository.fetchUser(by: userId)
    }
}

Преимущества: Такой подход обеспечивает тестируемость (можно подменить реализацию на мок), гибкость (легко менять реализации) и чистоту доменной логики.