Как стать автором
Обновить

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

Время на прочтение 9 мин
Количество просмотров 98K
Всего голосов 43: ↑33 и ↓10 +23
Комментарии 26

Комментарии 26

Ага, х*як х*як архитектура приложения
Скажите пожалуйста, каким образом было достигнуто переиспользование сущности х*як? И как можно избежать разрастания х*яка? :)
Спасибо за статью. Модульная архитектура на мой взгляд это самый разумный выбор в условиях постоянно меняющихся (или формирующихся на ходу) требований. И большая часть сказанного относится к любым типам приложений, не только к мобильным
На самом деле, изначально у меня была задумка написать обобщённую статью, касающуюся проектирования приложений не только под iOS, но и под Android.
Ведь в действительности большая часть подходов к архитектурному дизайну приложений от платформы не зависит, и все принципы уже давным-давно придумали и проработали.
Для сложного UI когда необходимо разделять логику между несколькими UI-элементами вполне себя также оправдывает выделение презентерa в отдельный слой (MVPC, MVPVM).

Что касается роутера — здесь опять же приходит на помощь модульная архитектура. Для того чтобы роутер не разрастался просто необходимо отказаться от использования одного центрального роутера. При инициализации модули регистрируют свои view и необходимую навигацию. Нет модуля — нет навигации, нет вьюх, нет сервисов. Подобный подход можно посмотреть в роутере Marionettejs ( там принято на каждый инстанс модуля создавать свой роутер) или в MS PRISM, где в RegionManager добавляются регионы вместе с модулями (а как следствие и навигация на эти регионы, где будут сменять друг друга view).

Минус такого подхода в отсутсвии строгой типизации (js сам по себе не типизированный, а в PRISM при навигации используются строковые константы), однако это на мой взгляд не слишком большая плата за масштабируемость
У меня были мысли следующего характера.
По сути, в приложениях под iOS существует всего три типа переходов: посредством навигационного контроллера, модальный и «custom».

Каждый подобный переход при наличии продуманного дизайна будет обусловлен структурой модели, используемой в приложении.
Список элементов => Детальное представление элемента — явно будет использовать «push».
Детальное представление элемента => контекстное действие над элементом («отослать по почте») — тут явно будет использовано модальное представление.

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

Вам не приходило ничего подобного в голову?
Статья понравилась. В будущем можно ли ожидать от вас статей на эту же тематику?

Еще вопрос. Когда Вы говорите об использовании сторибордов (причем в контексте лишь наглядности, как мне показалось), какого размера проект/команда подразумевается? Неоднократно слышал жалобы от людей из больших команд, что массивные (да и даже маленькие) сториборды очень усложняют жизнь при мерджах. К чему они приходят, так это использование отдельных nib'ов для каждого вью или вьюконтроллера, а то и попросту создание многих относительно простых вью программно.

У вас такой проблемы нет, или наглядность «историй пользователей» для важнее?
Статьи на подобную тематику обязательно будут.

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

Мы придерживаемся принципа «один человек — одна пользовательская история».
Таким образом проблемы слияния при использовании VCS не возникает вообще. Теоретически, проектная команда может быть любого соответствующего размера.

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

Один — занимается логикой бизнес-уровня, второй — строит UI.
Бизнес-уровень выстраивается довольно быстро, поэтому со временем первый разработчик попросту переключится на оставшийся UI-функционал.
Больше людей подключать нет смысла: проекты слишком мелкие для задействования больших команд.

Как уже было сказано в статье, для динамического объединения storyboard'ов можно использовать сторонние библиотеки.
Тем не менее, storyboard — это всё же не панацея, и, скажем, некоторые виды ячеек — используемые в разных user story — мы держим в отдельных XIB'ах для последующего переиспользования.

И да, за последний год у нас не было ни единого конфликта с вовлечением storyboard'ов (-:
Вероятно, нужно просто аккуратно раздавать задачи в команде.
Router можно построить с использованием конечного автомата. И для каждого сценария будет свой автомат со своей логикой и один автомат более высокого порядка — для управления переходами между сценариями.
VIPER/CLEAN архитектура выглядит очень красиво, но лично для меня некоторые вопросы реализации для iOS приложения остались неизведанные. Думаю следующий проект попробовать построить на этой архитектуре.
Присоединяюсь насчет конечного автомата.
Прямо сейчас реализую небольшой проект (15-20 тысяч строк кода) который строю сразу на КА. Логика каждой истории описывается при помощи КА. И это не только пользовательские истории. Это, например, истории взаимодействия клиента с серверами (серверов больше одного).

И есть КА верхнего уровня для «межавтоматного» взаимодействия. Причем эти КА работают параллельно. То есть, попадания в некоторые состояния автомата нижнего уровня являются событиями (символами) для КА верхнего уровня, которые могут приводить к возникновению событий для другого КА нижнего уровня.
А может напишите статью про мобильный проект, построенный на КА, с примерами и т.д? Было бы здорово почитать об этом.
В планах есть. Но пока отдаленных. Так как, если писать с какими-то реальными примерами, а не высосанными из пальца, надо какой-то не совсем тривиальный, но, тем не менее, свободный от кода заказчика проект.
Плюсую в сторону статьи и примера проекта с использованием КА.
Очень-очень интересно.
Меня в этом смущает один аспект — переходы в таком КА по определению асинхронные, так как анимации. «Классический» КА для этого не очень подходит.

Вариантов я вижу два:

  • во время перехода игнорировать поступающие в КА события
  • не менять состояние КА до окончания перехода; соответственно, поступающие события будут обрабатываться по таблице переходов «предыдущего» состояния


Ни один не могу назвать окончательно верным. В первом не самый лучший UX, во втором надо приседать с отменой переходов.
Отличная статья, но не хватает небольшого примера.
Может быть есть проекты в open source, использующие подобные решения?
Спасибо.
НЛО прилетело и опубликовало эту надпись здесь
Я думаю, следующая статья от меня так или иначе будет подробнее раскрывать эту архитектуру.
Этот подход используется практически на всех наших новых проектах («новый» — возраст от полугода), поэтому попросту описать один из них будет довольно легко.
НЛО прилетело и опубликовало эту надпись здесь
Ох, уж эти архитектуры!

Являюсь одним из разработчиком одного enterprise-проекта.
Архитекутура приложений следующая

1) Сайт ИБ
1.1) Контроллеры и вью (минимум логики)
1.2) Слой бизнес-логики
1.3) Слой доступа к данным

2) WCF cервис (по сути фасад) для доступа к системам хранения данных банка (системы большие, разрабатываются разными депаратаментами)
2.1) по сути слой контроллер (реализация ServiceContract)
2.2) слой бизнес логики
2.3) слой доступа к данным во внешние системы (например в другие wcf сервисы)
(Конечно, схема была бы нагляднее, но публиковать схему к сожалению запрещает NDA.)

Проблема следующая: куча библиотек (сборок) с моделями, и как следствие много маппинга из одних типов в другие (по сути такие же) типы, но уже из других библиотек. Всего сборок с моделями три:
1) модель представления на стороне сайта (о ней знает только слой 1.1)
2) бизнес модель на стороне сайта (о ней знают слои 1.1, 1.2 и 1.3)
3) контракты сервиса (о ней знают слои 1.3 и 2.1)
4) бизнес модель для доступа к данным (о ней знают слои 2.1, 2.2, 2.3)

Моделей столько нужно для того чтобы одни слои ничего не знали о других слоях.

Маппинг — это ад, какой-то. Плата за независимость модулей.
Про передачу данных между слоями систем вообще мало чтива. Очень бы хотелось узнать об опыте других разработчиков. Поделитесь информацией, о том как эффективнее, быстрее (в плане разработки) передавать данные от одного слоя к другому? Как организован мапинг классов и тп?

Думаю Вам следует посмотреть в сторону систем Dependency Injection, например в этой статье подробно описан один из них: Управляем зависимостями в iOS-приложениях правильно: Знакомство с Typhoon
Очень вдохновляющая статья!
Сам только начинаю разрабатывать кое какие мелочи, перейдя из сисадминской области IT.
И в ближайшем будущем уже собираюсь замутить небольшой проектик — который будет на самом деле школой в начале пути.

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

Одних 1000 страничных книжек и стенфордского онлайн курса по релятивным базам данных не хватает.
А хочется уже начать делать. Конечно же учиться на ошибках, но и чтото полезное смочь делать.

Вобщем надеюсь ваши будущие статьи будут такимиже интересными и заденут интерисующие меня вопросы )
Успехов!
Спасибо за отзыв.

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

В целом, здравый смысл — он везде.

Мне кажется, что достаточно просто периодически задаваться вопросом «А зачем я это делаю?», и я серьёзно.
Часто наблюдаю ошибки, что порождены безалаберным отношением к собственному коду.
«А сделаю-ка я тут класс».
Зачем? Почему? Что этот класс даст?

Хорошо спроектированное приложение, как чертёж здания — должно «кричать» о своём предназначении.
У концертного зала будет большой холл с рядами кресел.
У библиотеки — комната со стеллажами.



<сарказм>Но нет! Давайте же сделаем DataManager! И он будет обрабатывать данные! </сарказм>
По полочкам разложено знатно, спасибо.

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

По поводу роутера, на мой взгляд, не стоит заморачиваться на конкретный паттерн. То есть, если есть роутер, то должен быть класс MyAppRouter. Мой опыт показывает, что лучше выделять сгруппированные по сценарию навигации. Скажем, если в приложении есть setup wizard, то в его реализации можно использовать NavigationalRouter.
Благодарю.

Пользовательские сценарии — будут в следующих статьях (-:

Смотри, если ты знаешь о том, что твой API будет меняться, но ты этого не учёл в архитектурном дизайне своего приложения, то… улавливаешь? (-: Ты делаешь что-то неправильно.
На уровне проектирования, чтобы предусмотреть возможные изменения API — хорошо ложатся шаблоны «фабрика» и «стратегия» по отношению ко всем изменяемым элементам (а это, как правило, парсеры и сериализаторы).

Я предполагаю, что API изменяться не будет.

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

В конечном итоге, я ведь не зря привёл в статье типичный URL для сервиса:
api1.domain.com/1-0/messages
— тут уже заложено версионирование.
Для версии 1-1, если она сильно будет отличаться, то можно будет придумать, как выкрутиться.
Вспоминаем принцип YAGNI, и не делаем предварительной оптимизации.

И да, если у твоего клиента серверный разработчик — дурак — и обратной совместимости в API не закладывает, тогда сам знаешь, куда такого клиента посылать.
Нельзя просто так взять — и перелопатить структуру API, не перелопатив при этом БД, а это стоит дорого.
Стоит дорого = занимает много времени серверного разработчика.
Следовательно, займёт много и твоего времени.
А клиент пусть платит, да.
В случае, если UI требует массив объектов данного типа, отсортированный по какому-то критерию, — сервис должен предоставлять соответствующий метод, который вернёт отсортированный массив.

Насколько это применимо в случае, когда используется CoreData+NSFetchedResultsController?
NSFetchedResultsController представляет паттерн MVC с использованием активной модели.
В данной статье предполагается использование пассивной модели, без оповещений о её изменениях.

До активных моделей я тоже постараюсь добраться в последующих статьях — это неплохая альтернатива, но требует немного иной идеологии.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий