Мобильная разработка
Clean Architecture для мобильных
Два разработчика работают над функцией «добавить в корзину». Первый кладёт логику прямо в ViewModel, делает запрос к Retrofit, обновляет UI. Через шесть месяцев продуктовые требования меняются трижды, появляется оффлайн-режим, нужны тесты. Второй разработчик написал Use Case и Repository с первого дня - каждое изменение занимает час. Clean Architecture - это инвестиция, которая окупается на третьем месяце.
- **Google рекомендует** Clean Architecture в официальной документации Android (Guide to App Architecture)
- **Uber, Twitter, Netflix** используют вариации Clean Architecture для своих мобильных приложений
- **TCA (The Composable Architecture)** - реализация похожих принципов в мире iOS/SwiftUI от создателей Point-Free
Слои Clean Architecture
Небольшой проект начинается просто: Activity делает сетевой запрос, парсит JSON и показывает результат. Через год - 50 тысяч строк кода, Activity знает об API, базе данных, бизнес-правилах и навигации одновременно. Тест написать невозможно. **Clean Architecture** Роберта Мартина решает эту проблему: разбивает код на слои с чёткими границами.
На практике в мобильной разработке часто используют три слоя: **Presentation** (ViewModel + UI), **Domain** (Use Cases + Entities + Repository interfaces), **Data** (Repository реализации + API + локальная БД). Domain - это ядро, которое не зависит ни от Android, ни от внешних библиотек.
**Почему domain - чистый Kotlin?** Когда domain не зависит от Android SDK, его можно тестировать обычными JUnit-тестами без эмулятора. Тесты запускаются за секунды на CI, а не минуты. Это главная практическая выгода от разделения слоёв.
Какой слой Clean Architecture не должен содержать import-ы из Android SDK?
Dependency Rule
**Dependency Rule** - главный закон Clean Architecture: **зависимости кода должны указывать только внутрь**. Внешние слои знают о внутренних. Внутренние слои не знают о внешних. Domain не знает, что данные приходят из Retrofit или Room - он работает только с абстракциями.
Dependency Rule реализуется через **Dependency Inversion Principle**: высокоуровневые модули (Domain) не зависят от низкоуровневых (Data). Оба зависят от абстракций (интерфейсов). Интерфейс `UserRepository` живёт в Domain, реализация - в Data.
**Mapper паттерн:** Data слой возвращает DTO (Data Transfer Object), Domain ожидает Entity. Mapper конвертирует между ними. Mapper живёт в Data, чтобы Domain не знал о форматах API. Никогда не передавайте DTO из Data прямо в Presentation.
Где должен находиться интерфейс Repository в Clean Architecture?
Use Cases / Interactors
**Use Case** (он же Interactor) - класс, реализующий одну конкретную операцию приложения: «войти в систему», «оформить заказ», «получить список продуктов». Use Case оркестрирует репозитории и правила бизнес-логики. ViewModel вызывает Use Case, не зная деталей реализации.
Использование `operator fun invoke()` позволяет вызывать Use Case как функцию: `loginUseCase(email, password)` вместо `loginUseCase.execute(email, password)`. Это популярный Kotlin-идиом для Use Cases.
**Один Use Case - одна ответственность.** Если Use Case делает слишком много - разбейте его. `PlaceOrderUseCase` не должен также обновлять баланс, отправлять email и обновлять UI. Для каждого действия - отдельный Use Case.
Что должен делать Use Case при успешном входе помимо основной операции?
Repository pattern
**Repository** абстрагирует источник данных. Use Case говорит «дай мне пользователя», Repository решает: взять из кеша, из локальной БД или сделать сетевой запрос. Детали хранения и получения данных скрыты за интерфейсом.
Паттерн **Single Source of Truth (SSOT)**: Room - единственный источник правды. Сетевые данные всегда сохраняются в Room, UI наблюдает за Room через Flow. При обновлении сети данные текут: API -> Room -> Flow -> UI. Это устраняет рассинхронизацию между кешем и сервером.
**Fake Repository для тестов:** вместо мока создайте `FakeUserRepository : UserRepository` с in-memory данными. Это позволяет тестировать Use Cases и ViewModels детерминированно, без сетевых запросов и зависимостей от базы данных.
В паттерне Single Source of Truth (SSOT) что является источником правды для UI?
Clean Architecture для мобильных
- Три слоя: Presentation (ViewModel + UI), Domain (Use Cases + Entities + интерфейсы), Data (реализации + API + БД)
- Dependency Rule: зависимости только внутрь. Domain - чистый Kotlin, тестируется без Android
- Use Case = одна бизнес-операция. operator invoke() для Kotlin-идиоматичного вызова. Тест без Android SDK
- Repository скрывает источник данных. SSOT: API -> Room -> Flow -> UI. FakeRepository для тестов
Связанные темы
Clean Architecture применяется совместно с другими паттернами и инструментами:
- KMP: Kotlin Multiplatform — Domain слой Clean Architecture идеально переносится в KMP commonMain
- Android Jetpack и ViewModel — ViewModel - часть Presentation слоя Clean Architecture
- Тестирование мобильных приложений — Чистый Domain слой делает тестирование Use Cases быстрым и изолированным
Вопросы для размышления
- Когда Clean Architecture избыточна? При каком размере проекта накладные расходы оправданы?
- Как Repository паттерн помогает реализовать оффлайн-первый (offline-first) подход в мобильных приложениях?
- Чем Use Case отличается от метода в сервисном классе? Когда стоит создать отдельный класс Use Case?