Блог

Полезные статьи и новости о жизни WaveAccess

Как организовать iOS-проект для параллельной работы нескольких команд

Вам когда-нибудь доводилось работать над очень большим приложением? А в условиях сжатых сроков и силами нескольких команд? Решая эту задачу, нужно четко обозначить границы ответственности команд и определить правила обращения с общим кодом, иначе можно провалить дедлайн, бесконечно споря о том, кто виноват в появлении багов, и как их теперь исправлять.

В одной из предыдущих статей мы уже касались этой темы: рассказывали, как была организована параллельная Android-разработка крупного приложения с развитым функционалом. Сегодня предлагаем поговорить о том, как технически подготовить рабочее пространство для разработки уже iOS-приложения и грамотно поделиться им с другими.

Задача

Нам требовалось разработать мобильное приложение для крупной телекоммуникационной компании, которое должно было решать задачи, связанные  с обслуживанием ее клиентов. Приложение должно было содержать основную функциональность и несколько подразделов.

Мы получили ряд функциональных требований для разработки и начали закладывать структуру приложения: выбрали одну из разновидностей архитектуры VIPER для модулей (экранов), выделили папку Common для базовых компонентов и утилит и начали работу.

Для решения проблемы создания сложных объектов и зависимостей в приложении используется Dependency Injection (далее DI).

Через какое-то время приложение выглядело так (Рисунок 1) :

Ios Первоначальная Архитектура Приложения

Рисунок 1. Первоначальная архитектура приложения

На данном этапе каждый из модулей содержал стандартные файлы для экрана и имел возможность обращаться к публичным классам из папки Common/Public напрямую или через DI. DI хранил зарегистрированные интерфейсы с объектами из папки Common/Public. Недоступные для модулей классы и интерфейсы, созданные в помощь публичным, были определены в папку Common/Core. Внутри рабочего пространства ресурсы приложения хранились в одной папке Resources.

Но это был только промежуточный этап работы. Заказчик искал способы увеличения скорости разработки. Работу над последующими фичами было решено разделить на несколько команд, так как основная часть приложения была готова, и новые задачи могли быть выполнены параллельно (поскольку были не связаны друг с другом).

Проблема

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

  1. Понимание границ ответственности между командами важно для избежания спорных ситуаций. Особенно остро это ощущается на этапе тестирования при правке багов.  Поэтому важно разделить работу так, чтобы пересечений было как можно меньше.

  2. Правка общего кода часто приводит к конфликтам даже внутри одной команды. С возникновением нескольких, количество конфликтов грозит существенно увеличиться из-за недостатка коммуникации. Необходимо выделить часы на общение и определить правила изменения общей части кода. Одна команда не должна делать этого без ведома других.

Добиться этого можно с помощью изолированной работы над каждой из фич  (features) по отдельности и выделением общего модуля для координации работы. Каждый модуль должен определить интерфейс для работы с ним, чтобы исключить доступ к ненужным для основного модуля данным.

Ios 2 Черновик Взаимодействия Основного Приложения С Фичами

Рисунок 2. Черновик взаимодействия основного приложения с фичами

Концепция

Для дальнейшей работы необходимо было создать дочерние проекты и перенести часть реализованных файлов соответственно их функциональному разделу. Каждый из проектов не может быть самостоятельным приложением, так как лишь содержит набор относящихся к нему файлов. В Xcode есть возможность организовать это с помощью динамических фреймворков.

При наличии нескольких таких файлов *.xcodeproj, которые должны будут разрабатываться только внутри одной команды, сокращается количество возможных конфликтов при слиянии кода, так как общий файл xcworkspace останется без изменений.

Таким образом, мы знаем, что у нас точно должны быть:

  • модуль основного приложения;

  • независимые модули функциональных разделов;

  • базовый модуль с утилитами и помощниками.

Осталось лишь решить задачу по их взаимодействию.

Мы знаем, что каждый независимый подпроект должен быть максимально изолирован для того, чтобы его разработка не пересекалась с другими проектами и не зависела от других разработчиков. Из этого следует, что напрямую импортировать один подпроект в другой никак нельзя. Мы не можем строить логику своего модуля на основе другого. Тогда как основное приложение будет взаимодействовать с независимыми модулями фич и работать с ними?

Для соблюдения этих правил было решено создать вспомогательный подпроект, описывающий все доступные для работы контракты (протоколы).

Контракт - это публичный протокол, формирующий открытые поля и методы, которые можно использовать в работе, и регламентирующий формат данных, который должен будет использоваться между модулями. Таким образом создается четкая схема того, что будет открыто для доступа, а что - нет.

Решение

В итоге, структура приложения была реализована так (Рисунок 3):

Ios 3 Текущая Архитектура Приложения

Рисунок 3. Текущая архитектура приложения  

Приложение содержит несколько модулей:

Application Module - основной модуль приложения, где хранятся все конфиги и регистрируются контракты,

Application Contracts Module - модуль контрактов,

Feature Module - независимый функциональный подраздел (которых может быть несколько в одном проекте),

Core - модуль с общими утилитами и менеджерами.

Основные характеристики:

  • Core доступен из любого модуля приложения.

  • Feature Module не может импортировать другие функциональные разделы к себе и не имеет прямого доступа к основному приложению.

  • Feature Module может обратиться к другому функциональному разделу через контракт.

  • В каждом Feature Module создается новая папка Common, включающая общие утилиты для работы только внутри него, которые не должны быть в общем доступе, как утилиты из Core.

  • Внутри Application Contracts описываются все контракты для работы с каждым из Feature Module, за исключением общедоступного Core.

  • Feature Modules регистрируют свои контракты и передают объекты в DI-контейнер основного приложения.

  • У каждого Feature Module есть папка Resources, в которой хранятся те ресурсы, которые использует только этот модуль.

Пример работы

Мы создавали приложение для работы на рынке ценных бумаг, однако и его реальная архитектура, и возможности приложения по договору с клиентом разглашать мы не можем. Чтобы привести живой пример, давайте обратимся к смежной и не менее обширной области - области банковских приложений.

Разделим возможности такого приложения на несколько направлений: работа со счетом пользователя (клиента банка), работа с кредитами, работа с вкладами и депозитами. Остальные возможности в этом примере не играют роли и могут быть опущены. Для координации этих разделов нам понадобится наше  основное приложение, которое будет содержать главный экран, авторизацию и т.п.

Допустим, мы хотим добавить переход на кредитный раздел с нашего главного экрана. Так как мы выбирали для нашего реального приложения разновидность VIPER-архитектуры для работы с экранами, то здесь мы тоже используем понятие билдера, как инициализатора всех необходимых компонентов: контроллера, интерактора, презентера и так далее.  

Допустим, кредитный раздел приложения должен содержать как минимум два экрана: экран для заявки на кредит и список текущих кредитов пользователя.

Для того, чтобы создать экран списка кредитов внутри раздела кредитов нам необходимо сделать следующее:

  1. Создать контракт для CreditList билдера - CreditListBuilderProtocol. Описать внутри доступные методы для работы с экраном.

  2. Создать модуль экрана CreditList внутри проекта Credit Module, описать его логику. CreditListBuilder должен реализовывать методы и поля публичного протокола CreditListBuilderProtocol.

  3. Регистрируем в DI контейнере основного приложения объект CreditListBuilder за контрактом CreditListBuilderProtocol.

Теперь, когда мы зарегистрировали контракт для работы с модулем CreditList Module, мы можем в любой нужный нам момент обратиться к нему из контроллера любого экрана основного приложения. Для реализации экрана создания заявки на кредит нужно проделать те же самые шаги.

Схематически этот сценарий будет выглядеть так (Рисунок 4):

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

Ios 4 Сценарий Открытия Экрана Другого Модуля

 Рисунок 4. Сценарий открытия экрана другого модуля

Мы разобрались, как работать с независимым модулем из основного приложения. Но что делать, если нам нужны данные из одного модуля внутри другого?

Возвращаясь к банковскому приложению, мы определили, что нам может понадобиться раздел со счетом пользователя, User Account Module. Баланс пользователя хранится в этом функциональном разделе, а это очень важные данные для оформления заявки на кредит. Что же мы сделаем?

  1. Создадим контракт UserBalanceStatusProtocol, который формирует формат данных, который нам доступен.

  2. Реализуем класс UserBalanceStatus внутри модуля User Account, который будет обращаться к какому-то общему хранилищу данных и реализовывать UserBalanceStatusProtocol.

  3. Зарегистрируем объект  UserBalanceStatus в DI-контейнере основного приложения.

Эти действия позволят нам получить данные из одного модуля в другом, не создавая лишних зависимостей и не нарушая целостность модулей.

Схематически этот сценарий будет выглядеть так (Рисунок 5):

Ios 5 Сценарий Обращения Независимого Модуля К Данным Другого Модуля

Рисунок 5. Сценарий обращения независимого модуля к данным другого модуля

Таким образом, через реализацию контрактов можно настроить модульное взаимодействие, не создавая жестких ссылок между модулями и распределяя работу между командами. Данное техническое решение применимо к любой области, будь то банковские приложения, решения для финансового рынка или другие проекты, в которых требуется быстрая параллельная разработка.

Кстати, такой подход позволяет отключать логические блоки приложения без  дополнительных серьезных доработок, но логика отключения заслуживает отдельной статьи.

Заключение

У нас получилось реконструировать мобильное приложение так, чтобы над ним было удобно работать нескольким независимым командам и большому количеству людей: точно так же, как мы сделали это для платформы Android.

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

Статистика показывает разницу в двух этапах работы: в течение трёх месяцев разрабатывался основной фундамент приложения, было написано более 22-х тысяч строк кода, но уже после внедрения новой идеи проект вырос более чем в 4 раза за 4 месяца и содержал уже 104 тысячи строк. За короткий срок удалось выполнить все требования заказчика, а также получить отличный опыт разработки модульного проекта.

Если вам требуется разработка сложного iOS-приложения или восстановление проекта в оптимальный срок, мы готовы ответить на все ваши вопросы: hello@wave-access.com 

 

 

 

 

Заказать звонок

Удобное время:

Отменить

Пишите!

Присоединить
Файл не больше 30 Мб.
Отменить