Модуляризация iOS-приложений через SPM: как навести порядок в зависимостях
Всем привет! Сегодня хочу обсудить , в которой автор делится подходом к организации зависимостей в крупных проектах с помощью локальных SPM-пакетов. Когда проект вырастает из пары десятков файлов, монолитный таргет начинает болеть. Сборки замедляются, изменение в одном месте перекомпилирует половину проекта, любой файл может импортировать что угодно.
Три слоя, один поток зависимостей:
Автор делит все приложение на три уровня:
Common - самые низовые утилиты: логгеры, расширения, хелперы. Не зависят ни от чего внутри проекта.
Services - два подмодуля: API (сетевые модели и эндпоинты) и Domain (бизнес-логика, сервисы, моки). Domain зависит от API и Common.
Features - экраны на SwiftUI. Импортируют только Domain и Common. Никогда - API.
Зависимости текут строго снизу вверх. Модуль может зависеть только от того, что лежит под ним.
Как это выглядит в Package.swift:
В манифесте все зависимости объявляются явно, с помощью удобного dot-синтаксиса. Каждый таргет знает, от кого он зависит. Если кто-то попытается импортировать модуль сетевого слоя прямо из фичи - проект просто не соберется. Компилятор не даст нарушить архитектуру.
Что в каждом модуле:
API - только модели, которые зеркалируют ответ сервера, и типизированные эндпоинты.
Domain - здесь происходит основная работа: модели предметной области, маппинг из API-моделей в доменные, сервисы (closure-based, без протоколов) и моки. Моки тоже живут здесь, потому что они возвращают доменные модели, а не API-шные.
Features - чисто UI. Импортируют только Domain, используют моки для превью. Никакого сетевого слоя внутри.
ServiceEnvironment для массовой инъекции:
Когда сервисов становится больше двух, можно использовать контейнер ServiceEnvironment, который собирает все сервисы в одну структуру. Через кастомный модификатор все прокидывается в окружение одной строкой. Превью используют .mock, приложение - .live.
Что дает такой подход:
Сборки ускоряются: поменяли фичу - перекомпилируется только фича.
Границы соблюдаются на уровне компилятора, а не код-ревью.
Тесты изолированы: домен тестируется без SwiftUI, фичи превьюшатся с моками.
Параллельная разработка: один пишет сетевой слой, другой - интерфейс на моках, конфликтов почти нет.
Вывод:
Модуляризация через локальные SPM-пакеты - это не про идеальную архитектуру ради архитектуры. Это про скорость и масштабируемость. Начинать лучше с малого: вынести сначала Domain, потом API, а фичи - по мере роста. Результат - приложение, где новый разработчик за пять минут понимает структуру, а CI не ждет десять минут на сборку.