Урок по треку Архитектура мобильных приложений 6 мин

Clean Architecture и слои мобильного приложения

Слои data, domain и presentation, правило зависимостей, use case, репозиторий и границы модулей.

«Clean Architecture» на собесах спрашивают почти всегда, и почти всегда ждут не цитату из книги, а понимание слоёв, направления зависимостей и того, когда это перебор.

Три слоя

  • Presentation: экраны, view model, UI-модели. Знает о UI-фреймворке.
  • Domain: сущности, бизнес-правила, use case, интерфейсы репозиториев. Чистый язык без фреймворков (корутины или Swift Concurrency допустимы).
  • Data: реализации репозиториев, сеть, база, кеш, DTO и мапперы.

Официальный гайд Android называет domain-слой опциональным: его добавляют, когда появляется логика, которую нужно переиспользовать или изолировать.

Правило зависимостей

Зависимости в коде направлены внутрь, к домену. Домен не знает ни о Retrofit, ни о Core Data, ни о SwiftUI. Data-слой реализует интерфейсы, объявленные в домене:

// domain
interface OrderRepository { suspend fun place(cart: Cart): PlacedOrder }
class PlaceOrderUseCase(private val orders: OrderRepository) {
    suspend operator fun invoke(cart: Cart): PlacedOrder {
        require(cart.items.isNotEmpty()) { "Пустая корзина" }
        return orders.place(cart)
    }
}

// data
class RemoteOrderRepository(private val api: OrderApi) : OrderRepository {
    override suspend fun place(cart: Cart) = api.place(cart.toDto()).toDomain()
}

Это инверсия зависимостей: верхний уровень зависит от абстракции, реализация подставляется через DI. Отсюда тестируемость: use case проверяется с фейковым репозиторием без сети.

Репозиторий

Фасад над источниками данных. Решает, откуда взять данные (кеш, база, сеть), как их объединить и когда обновить. Остальной код не знает, есть ли кеш вообще.

Модели и маппинг

  • DTO повторяет формат API: snake_case, строки вместо дат, опциональные поля.
  • Entity базы повторяет формат хранения.
  • Доменная модель описывает понятия приложения: Date, enum статуса, обязательные поля.
  • UI-модель содержит готовые строки, цвета, форматированные суммы.

Маппинг на границе data изолирует приложение от изменений API. Но четыре модели на каждую сущность нужны не всегда: если форматы совпадают, модели объединяют.

Ошибки на границах

Data переводит технические ошибки (HttpException, URLError, ошибки базы) в доменные: «нет связи», «сессия истекла», «не найдено». Presentation решает, как их показать. Если HttpException доходит до view model, презентационный слой начинает зависеть от сетевой библиотеки.

Use case: когда нужен

Use case-прокладка, которая лишь вызывает repository.get(), добавляет файл и ничего не даёт. Use case оправдан, когда в нём бизнес-правило (лимиты, скидки, валидация) или координация нескольких репозиториев, и это нужно нескольким экранам.

Слои внутри фич

В больших приложениях код группируют по фичам, а слои живут внутри:

feature/cart/data
feature/cart/domain
feature/cart/ui

Изменение фичи касается одной папки или модуля, и фичу легко вынести в отдельный модуль.

Когда это перебор

В маленьком приложении, которое в основном показывает ответы API, полный набор слоёв замедляет разработку. Минимум, который стоит делать всегда: сеть и база за интерфейсами, зависимости через конструктор, тестируемые view model, структура по фичам. Остальное добавляется по мере роста.

Частые ошибки

  • Домен импортирует UIKit, Android SDK или сетевую библиотеку.
  • Use case показывает алерты или держит контроллер.
  • Строковые статусы API в бизнес-логике.
  • Слои ради слоёв без логики внутри.

Что спросят на собесе

  • Какие слои вы выделяете и что в каждом?
  • Что такое правило зависимостей и инверсия зависимостей?
  • Где объявлять интерфейс репозитория?
  • Зачем маппить DTO в доменные модели?
  • Когда Clean Architecture избыточна?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.