Где будет лежать репозиторий в чистой архитектуре?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Место репозитория в чистой архитектуре (Clean Architecture)
В чистой архитектуре, предложенной Робертом Мартином (Uncle Bob), репозиторий занимает ключевое положение на стыке бизнес-логики и механизмов доступа к данным. Он является частью слоя интерфейсов адаптеров (Interface Adapters), но его интерфейс (контракт) определяется и принадлежит слою бизнес-политик (Entities, Use Cases). Это один из центральных паттернов, обеспечивающих соблюдение правила зависимостей (Dependency Rule): зависимости направлены внутрь, к центру архитектуры.
Детальная роль и расположение
- Интерфейс репозитория (контракт) принадлежит доменному слою.
* Этот интерфейс объявляется во **внутреннем круге** архитектуры — обычно в модуле `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). Такое разделение — краеугольный камень для создания гибкого, тестируемого и поддерживаемого приложения, устойчивого к изменениям во внешних сервисах и фреймворках.