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

Как сделать фичу согласно принципам чистой архитектуры где приложение отправляет запрос в сеть и делает какие-то определенные действия

3.0 Senior🔥 132 комментариев
#Архитектура и паттерны#Сетевое взаимодействие

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

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

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

Реализация фичи в 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. Ключевые шаги для любой новой фичи

  1. Определить Entities в Domain Layer (что представляет собой данные).
  2. Создать Repository Interface в Domain (какие данные нужны).
  3. Разработать Use Cases (какие действия с данными выполняются).
  4. Реализовать Repository в Data Layer с конкретными источниками (API, DB).
  5. Создать ViewModel и State в Presentation Layer для управления UI.
  6. Связать слои через Dependency Injection (используя Hilt или другой DI-фреймворк).

Этот подход делает код устойчивым к изменениям во внешних библиотеках и фреймворках, поскольку ядро приложения (Domain) остается полностью независимым. Фича становится не просто набором классов, а структурированным модулем с четкими обязанностями.

Как сделать фичу согласно принципам чистой архитектуры где приложение отправляет запрос в сеть и делает какие-то определенные действия | PrepBro