Как в DDD управляют зависимостями?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Управление зависимостями в Domain-Driven Design (DDD)
В Domain-Driven Design управление зависимостями — это критически важная практика, которая напрямую влияет на чистоту доменного слоя и его способность выражать бизнес-логику без технических деталей. Основная цель — изолировать домен от инфраструктурных компонентов (базы данных, внешних сервисов, фреймворков) и даже от других частей приложения.
Ключевые принципы и паттерны
1. Инверсия зависимостей через Dependency Inversion Principle (DIP)
Вместо того чтобы доменный код зависел от инфраструктуры, мы делаем инфраструктуру зависимой от абстракций, определённых в домене. Это реализуется через интерфейсы (портs) в доменном слое.
// Доменный слой: определяет интерфейс (порт)
protocol OrderRepository {
func save(_ order: Order) async throws
func findById(_ id: OrderId) async throws -> Order?
}
// Доменная сущность использует только абстракцию
class OrderProcessingService {
private let repository: OrderRepository
init(repository: OrderRepository) {
self.repository = repository
}
func process(order: Order) async throws {
// Бизнес-логика
try await repository.save(order)
}
}
2. Чёткое разделение слоёв (Layered Architecture)
DDD традиционно предполагает разделение на:
- Доменный слой (сущности, агрегаты, доменные сервисы, репозитории интерфейсы)
- Слой приложения (App Services, координирующие доменные операции)
- Инфраструктурный слой (реализации репозиторий, внешние API, persistence)
- Слой представления (UI, API контроллеры)
Зависимости направлены внутрь: внешние слои зависят от внутренних, но не наоборот.
3. Использование Dependency Injection (DI)
Конкретные реализации внедряются в доменные сервисы или App Services через инъекцию зависимостей, обычно на уровне композиции корня приложения.
// Инфраструктурный слой предоставляет реализацию
class SQLOrderRepository: OrderRepository {
private let database: DatabaseConnection
func save(_ order: Order) async throws {
// SQL-специфичная реализация
}
}
// Композиция корня (например, в SwiftUI или отдельном модуле)
let repository = SQLOrderRepository(database: myDB)
let service = OrderProcessingService(repository: repository)
4. Порт и Адаптер (Hexagonal/Ports and Adapters Architecture)
Это более современный подход, расширяющий layered architecture:
- Порт — интерфейс в домене, определяющий контракт (например,
OrderRepository). - Адаптер — реализация порта в инфраструктурном слое (например,
SQLOrderRepositoryилиGraphQLOrderAdapter).
Такой подход позволяет домену быть полностью независимым от способов взаимодействия с внешним миром.
Практические механизмы управления в iOS/Swift
Для модульности и тестирования:
- Протоколы (интерфейсы) как основные абстракции.
- Фреймворки/модули для физического разделения слоёв (можно использовать Swift Package Manager).
- Тестирование: легко создавать mock-реализации для доменных интерфейсов.
// Mock для тестирования
class MockOrderRepository: OrderRepository {
var savedOrders: [Order] = []
func save(_ order: Order) async throws {
savedOrders.append(order)
}
}
let mockRepo = MockOrderRepository()
let service = OrderProcessingService(repository: mockRepo)
// Тестируем бизнес-логику без инфраструктуры
Конкретные примеры зависимостей в DDD
Что НЕ должно попадать в доменный слой:
- Конкретные классы баз данных (CoreData, Realm, SQL).
- Сетевые фреймворки (Alamofire, URLSession напрямую).
- Фреймворки UI (SwiftUI, UIKit).
- Системы аналитики или логирования.
- Любые технические детали реализации.
Что определяет доменный слой:
- Интерфейсы репозиториев для персистентности.
- Интерфейсы для внешних сервисов (например,
PaymentGateway). - Доменные события и их обработчики (через интерфейсы).
Выводы и лучшие практики
Управление зависимостями в DDD сводится к строгой защите доменного модели от внешних влияний. Это достигается через:
- Абстракции, определённые доменом.
- Инверсию направления зависимостей.
- Чёткие границы слоёв с правилами взаимодействия.
- Инъекцию конкретных реализаций только на уровне композиции.
Такое подход не только сохраняет чистоту бизнес-логики, но и делает систему более тестируемой, модульной и адаптируемой к изменениям инфраструктуры без переписывания доменного кода. В iOS разработке это особенно важно для долгосрочной поддержки проекта и обеспечения его эволюции без нарушения работы ядра приложения.