Как сделать фичу согласно принципам чистой архитектуры где приложение отправляет запрос в сеть и делает какие-то определенные действия
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Реализация фичи в Android по принципам Clean Architecture
Принципы Clean Architecture (или чистой архитектуры) создают четкое разделение ответственности, делая код независимым от фреймворков, легко тестируемым и поддерживаемым. Для фичи, которая отправляет сетевые запросы и выполняет действия, необходимо создать слои: Presentation, Domain и Data. Рассмотрим пример фичи "Получение списка пользователей и их отображение".
1. Структура слоев Clean Architecture
- Domain Layer (Ядро бизнес-логики): Не зависит от Android и внешних библиотек. Здесь находятся Use Cases (Interactors) и Repository Interfaces.
- Data Layer: Реализация репозиториев, источников данных (например, сетевых API и локальной базы данных). Эта layer зависит от Domain (реализует его интерфейсы).
- Presentation Layer: UI-логика (ViewModel, State). Она зависит от Domain Layer для выполнения бизнес-задач.
2. Реализация примера "Получение списка пользователей"
Domain Layer
Создаем сущность (Entity), интерфейс репозитория и Use Case.
// Entity: чистый объект бизнес-логики
data class User(
val id: Int,
val name: String,
val email: String
)
// Интерфейс репозитория в Domain
interface UserRepository {
suspend fun getUsers(): List<User>
}
// Use Case (Interactor). Он содержит чистую бизнес-логику.
class GetUsersUseCase(
private val userRepository: UserRepository
) {
suspend fun execute(): List<User> {
return userRepository.getUsers()
}
}
Data Layer
Реализуем интерфейс репозитория из Domain. Здесь используем конкретные библиотеки (например, Retrofit).
// Модель ответа сети (может отличаться от Entity, нужен маппинг)
data class NetworkUser(
@SerializedName("id") val id: Int,
@SerializedName("full_name") val name: String,
@SerializedName("email_address") val email: String
)
// Реализация репозитория в Data Layer
class UserRepositoryImpl @Inject constructor(
private val apiService: ApiService,
private val userMapper: UserMapper
) : UserRepository {
override suspend fun getUsers(): List<User> {
val networkUsers = apiService.getUsers()
return userMapper.toDomain(networkUsers)
}
}
// Маппер для преобразования Data Model в Domain Entity
class UserMapper {
fun toDomain(networkUsers: List<NetworkUser>): List<User> {
return networkUsers.map { networkUser ->
User(
id = networkUser.id,
name = networkUser.name,
email = networkUser.email
)
}
}
}
Presentation Layer (Android)
В этом слое используем ViewModel для управления состоянием и взаимодействия с Use Case. Применяем подходы MVVM или MVI.
// Состояние UI
data class UsersState(
val users: List<User> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null
)
// ViewModel, зависящая от Use Case из Domain
@HiltViewModel
class UsersViewModel @Inject constructor(
private val getUsersUseCase: GetUsersUseCase
) : ViewModel() {
private val _state = MutableStateFlow(UsersState())
val state: StateFlow<UsersState> = _state.asStateFlow()
fun loadUsers() {
viewModelScope.launch {
_state.update { it.copy(isLoading = true, error = null) }
try {
val users = getUsersUseCase.execute()
_state.update { it.copy(users = users, isLoading = false) }
} catch (e: Exception) {
_state.update { it.copy(error = e.message, isLoading = false) }
}
}
}
}
В Activity или Fragment собираем данные из ViewModel и отображаем:
class UsersActivity : AppCompatActivity() {
private val viewModel: UsersViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_users)
// Наблюдаем за состоянием
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.state.collect { state ->
// Обновляем UI на основе state
if (state.isLoading) showProgress()
else {
hideProgress()
if (state.error != null) showError(state.error)
else displayUsers(state.users)
}
}
}
}
// Запускаем загрузку
viewModel.loadUsers()
}
private fun displayUsers(users: List<User>) {
// Логика отображения списка пользователей
}
}
3. Преимущества такого подхода
- Тестируемость: Domain Use Cases и бизнес-логику можно тестировать в чистых unit-тестах без Android зависимостей. Data Layer можно тестировать с моками сети, Presentation Layer — с помощью ViewModel тестов.
- Замена зависимостей: Если нужно изменить источник данных (например, с REST API на GraphQL), это делается только в Data Layer без затрагивания Domain или Presentation.
- Четкое разделение: Каждый разработчик понимает границы слоев. UI-логика не смешивается с бизнес-логикой или деталями сети.
- Поддержка и масштабирование: Добавление новой фичи или изменение существующей становится локальной задачей в одном слое.
4. Ключевые шаги для любой новой фичи
- Определить Entities в Domain Layer (что представляет собой данные).
- Создать Repository Interface в Domain (какие данные нужны).
- Разработать Use Cases (какие действия с данными выполняются).
- Реализовать Repository в Data Layer с конкретными источниками (API, DB).
- Создать ViewModel и State в Presentation Layer для управления UI.
- Связать слои через Dependency Injection (используя Hilt или другой DI-фреймворк).
Этот подход делает код устойчивым к изменениям во внешних библиотеках и фреймворках, поскольку ядро приложения (Domain) остается полностью независимым. Фича становится не просто набором классов, а структурированным модулем с четкими обязанностями.