← Назад к вопросам

Где будет лежать репозиторий в чистой архитектуре?

2.0 Middle🔥 132 комментариев
#Архитектура и паттерны

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Место репозитория в чистой архитектуре (Clean Architecture)

В чистой архитектуре, предложенной Робертом Мартином (Uncle Bob), репозиторий занимает ключевое положение на стыке бизнес-логики и механизмов доступа к данным. Он является частью слоя интерфейсов адаптеров (Interface Adapters), но его интерфейс (контракт) определяется и принадлежит слою бизнес-политик (Entities, Use Cases). Это один из центральных паттернов, обеспечивающих соблюдение правила зависимостей (Dependency Rule): зависимости направлены внутрь, к центру архитектуры.

Детальная роль и расположение

  1. Интерфейс репозитория (контракт) принадлежит доменному слою.
    *   Этот интерфейс объявляется во **внутреннем круге** архитектуры — обычно в модуле `domain` или `entities`/`usecases`.
    *   Он абстрагирует способы получения и сохранения данных, описывая их в терминах предметной области (например, `User`, `Order`), а не технических деталей (например, SQL-запросы или API-вызовы).
    *   Это гарантирует, что бизнес-правила (Use Cases) не зависят от внешних фреймворков и библиотек.

```kotlin
// domain/repository/UserRepository.kt
interface UserRepository {
    suspend fun getUserById(id: String): User
    suspend fun saveUser(user: User): Boolean
    suspend fun getUsersByCriteria(criteria: SearchCriteria): List<User>
}
```

2. Реализация репозитория принадлежит слою инфраструктуры (внешнему кругу).

    *   Конкретный класс, реализующий интерфейс `UserRepository`, находится во **внешнем слое**, чаще всего в модуле `data`.
    *   Эта реализация отвечает за взаимодействие с конкретными источниками данных: базами данных (Room, Realm), сетью (Retrofit, Ktor), файловой системой или даже памятью.
    *   Она преобразует объекты предметной области (`User`) в объекты, понятные источнику данных (например, `UserEntity` для Room), и наоборот.

```kotlin
// data/repository/UserRepositoryImpl.kt
class UserRepositoryImpl @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource,
    private val userMapper: UserMapper
) : UserRepository {

    override suspend fun getUserById(id: String): User {
        // 1. Проверяем локальную БД (Room)
        val localUser = localDataSource.getUserById(id)
        if (localUser != null) {
            return userMapper.mapFromEntity(localUser)
        }
        // 2. Если нет локально, идём в сеть
        val remoteUser = remoteDataSource.fetchUser(id)
        // 3. Сохраняем полученные данные локально
        localDataSource.saveUser(userMapper.mapToEntity(remoteUser))
        // 4. Возвращаем доменный объект
        return remoteUser
    }

    override suspend fun saveUser(user: User): Boolean {
        // Сохраняем и локально, и удалённо (логика может быть сложнее)
        val localSuccess = localDataSource.saveUser(userMapper.mapToEntity(user))
        val remoteSuccess = remoteDataSource.updateUser(user)
        return localSuccess && remoteSuccess
    }
}
```

3. Использование репозитория в Use Case (Interactor).

    *   Сценарии использования (Use Cases/Interactors), находящиеся в слое бизнес-логики, зависят **только от интерфейса** репозитория. Они ничего не знают о том, откуда именно приходят данные: из сети, кэша или базы данных.

```kotlin
// domain/usecase/GetUserProfileUseCase.kt
class GetUserProfileUseCase @Inject constructor(
    private val userRepository: UserRepository // Зависимость от АБСТРАКЦИИ
) {
    suspend operator fun invoke(userId: String): UserProfile {
        // Бизнес-логика: например, получить пользователя и скомпоновать его профиль
        val user = userRepository.getUserById(userId)
        // ... возможная дополнительная логика (валидация, обогащение данных)
        return UserProfile(user = user, /* ... */)
    }
}
```

Ключевые принципы, которые это обеспечивает

  • Инверсия зависимостей (DIP): Высокоуровневые модули (Use Cases) не зависят от низкоуровневых (источники данных). Оба зависят от абстракции (интерф Iса репозитория).
  • Тестируемость: Бизнес-логику можно легко протестировать с помощью моков или фейков репозитория, без необходимости поднимать реальную БД или сетевой стек.
  • Слабое зацепление и заменаемость: Источник данных можно полностью изменить (например, перейти с REST на GraphQL или с SQLite на Realm), переписав только реализацию репозитория в слое data. Код домена и use case останется нетронутым.
  • Единая точка управления данными: Репозиторий часто реализует стратегии кэширования (как в примере выше getUserById), предоставляя Use Case'ам прозрачный доступ к актуальным данным, скрывая сложность их получения.

Итог: Репозиторий в чистой архитектуре — это не просто папка repository в проекте. Это мост с двусторонним движением: его интерфейс (контракт) живёт в ядре (domain), а реализация — на периферии (data). Такое разделение — краеугольный камень для создания гибкого, тестируемого и поддерживаемого приложения, устойчивого к изменениям во внешних сервисах и фреймворках.