Ответ
Dependency Inversion Principle (DIP) в Clean Architecture означает, что модули верхнего уровня (доменные сущности, use cases) не должны зависеть от модулей нижнего уровня (репозитории, фреймворки). Вместо этого оба уровня зависят от абстракций (интерфейсов).
Пример:
// Domain layer (не зависит от деталей)
interface UserRepository {
suspend fun getUser(id: String): User
}
class GetUserUseCase(private val repo: UserRepository) {
suspend operator fun invoke(id: String) = repo.getUser(id)
}
// Data layer (реализует интерфейс)
class UserRepositoryImpl @Inject constructor(
private val api: UserApi
) : UserRepository {
override suspend fun getUser(id: String) = api.fetchUser(id)
}
Ключевые моменты:
- UseCase зависит только от интерфейса
UserRepository - Реализация
UserRepositoryImplможет меняться без изменения домена - Направление зависимостей идет к центру (домену)
Ответ 18+ 🔞
Давай разберем эту хрень, которая звучит как заклинание для инициации в какую-то секту. Dependency Inversion Principle, блядь. Слушай, а по-русски-то что? Принцип инверсии зависимостей. Суть в том, чтобы твой главный, самый важный код (логика, бизнес-правила, вот это всё) не зависел от всякой технической ерунды вроде баз данных, апишек и прочего фреймворчного говна.
Представь себе, блядь, архитектора, который проектирует дом так, что если у тебя сломается кофеварка, то вся проводка в стенах накроется медным тазом. Вот это пиздопроебибна архитектура. А DIP говорит: давай-ка мы сделаем так, чтобы твоя голова (домен) не знала, какими руками (репозиториями) она сегодня будет жопу подтирать.
Вот смотри на этот код, тут всё наглядно:
// Domain layer (не зависит от деталей)
interface UserRepository {
suspend fun getUser(id: String): User
}
class GetUserUseCase(private val repo: UserRepository) {
suspend operator fun invoke(id: String) = repo.getUser(id)
}
// Data layer (реализует интерфейс)
class UserRepositoryImpl @Inject constructor(
private val api: UserApi
) : UserRepository {
override suspend fun getUser(id: String) = api.fetchUser(id)
}
Видишь эту магию? UseCase — это твой главный по тарелочкам, он говорит: «Мне нужен пользователь, иди принеси». Но он нихуя не знает, откуда его принесут: из интернета, из базы данных, из файла на диске или, ёпта, из оперативной памяти. Ему похуй! Он знает только контракт — интерфейс UserRepository. А кто и как его реализует — это уже проблемы нижнего, технического слоя.
Ключевые моменты, чтобы не облажаться:
-
UseCase зависит только от интерфейса. Это как если бы ты нанимал уборщицу только по резюме, а не потому что она твоя тётя. UseCase'у всё равно, кто там за интерфейсом скрывается —
UserRepositoryImpl,FakeRepositoryилиRepositoryForTestsWithMockData. Доверия к конкретной реализации — ноль ебать, главное, чтобы контракт соблюдала. -
Реализация может меняться без изменения домена. Сегодня пользователей грузим с REST API, завтра начальство охуело и сказало перейти на GraphQL, а послезавтра и вовсе на gRPC. Ну и хуй с ним! Ты просто пишешь новую реализацию
UserRepositoryImplV2, которая тырит данные откуда надо, а твой драгоценный UseCase, где зашита вся бизнес-логика, даже не чихнет. Он как работал, так и работает. Волнение ебать — ноль. -
Направление зависимостей идет к центру (домену). Это самый важный пиздец. Зависимости не торчат во все стороны, как иголки у ёжика. Они все смотрят внутрь, на самый главный и стабильный кусок — домен. База данных зависит от интерфейса в домене. Сетевая библиотека зависит от интерфейса в домене. А сам домен нихуя ни от кого не зависит, он самодостаточная сука. Красота, да? Это и есть чистая архитектура, а не та хуйня, где всё перемешано в одну кучу, и через полгода ты сам от своего кода охуеешь.
Короче, запомни: высокоуровневые модули (что делать) не должны зависеть от низкоуровневых (как делать). Оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций. Звучит как мантра, но если вникнуть — реально спасает от головной боли, когда проект растёт.