№1 в разработке мобильных решений для бизнеса
109,80
рейтинг
25 декабря 2014 в 17:15

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

Признак плохого дизайна №1:
Наличие объекта-«бога» с именем, содержащим «Manager», «Processor» или «API»


Ведущий iOS-разработчик Redmadrobot Егор BepTep Тафланиди — о том, как добиться стройного архитектурного дизайна мобильного приложения, используя классические шаблоны проектирования и логическое разделение исходного кода на модули.

Все архитекторы, которых я встречал, в разной степени обладали одной и той же профессиональной деформацией характера: в своей речи они старались избегать конкретики. Подобный подход легко понять, ведь суть гибкости любой системы заключается в абстрагировании от конкретных решений. Чем дольше решение может оставаться отсроченным — тем гибче система. Если UI достаточно абстрагирован от модулей уровня сохранения данных — то считывание файла с жёсткого диска с лёгкостью можно будет подменить скачиванием той же информации с серверного API.

И все же, попробуем разложить по полочкам архитектурный дизайн типичного iOS-приложения.
Я опишу глобальный подход к делению логики приложения на уровни и слои, а также приведу конкретных участников тех или иных процессов, каждый — со своей собственной ответственностью и взаимодействиями с окружением.

Установка


TL;DR: ПРОГРАММИСТЫ — ТОЖЕ НАУЧНЫЕ СОТРУДНИКИ

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

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

Книга «Язык паттернов» Кристофера Александра, давшая толчок в сторону использования шаблонов в программировании, была выпущена в 1977 году, а паттерн Model-View-Controller описан в 1979-м.
Вооружившись наработками в области разработки ПО и надев белый халат теоретика, попробуем выстроить типичный проект приложения для мобильной платформы.

N.B. Скорее всего, в реальной жизни (с учётом бизнес-требований к системе и принимая во внимание сроки разработки) ваш проект будет выглядеть несколько иначе. Тем не менее, можно будет с уверенностью сказать, что за спиной подобного продукта стоит не просто ваша выдумка, а обоснованный набор решений.



Дано


СФЕРИЧЕСКАЯ СИСТЕМА В ВАКУУМЕ

Итак, у нас есть:
  • сервер с документированным API из нескольких web-сервисов;
  • приложение-клиент и несколько типов устройств, которые это приложение должно поддерживать: планшеты, телефоны, часы и прочее;
  • макеты того, как должен выглядеть интерфейс приложения;
  • набор пользовательских историй, декларирующих, как приложение должно себя вести;
  • список нефункциональных требований, которым система должна соответствовать.

Модель песочных часов


СИТХОВ ВСЕГДА ДВОЕ: ОДИН СТРОИТ СЕРВИСЫ, ДРУГОЙ ВОЗДВИГАЕТ НА НИХ UI

Первым же шагом отделим модель от контроллера и представления, таким образом выделив два уровня приложения: «нижний» обращён в сторону серверных мощностей, «верхний» смотрит на пользователя.

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

Будем придерживаться классического принципа, который гласит, что слои приложения общаются между собой посредством модельных объектов — классов с «прозрачной» реализацией, состоящей исключительно из акцессоров и мутаторов (get- и set-методы; фактически, класс будет включать в себя только свойства). Подобные классы не несут с собой никакой логики, и предназначены лишь для хранения и перемещения данных.

Центральной сущностью приложения — «горлышком» песочных часов — будет выступать объект, иначе называемый «инвертор зависимостей». Он же станет единственным singleton-классом, и на нём будет лежать единственная ответственность — предоставлять «верхним» слоям приложения сервисы.

Сервисы


ИСТОРИЯ ШТЕПСЕЛЬНОГО СОЕДИНЕНИЯ

Как делаются нормальные серверные приложения? Очень просто. Всегда есть какая-то база данных (или их может быть несколько), в которой есть таблицы. Таблицы состоят из записей. Каждая запись содержит набор полей, в которых хранится информация.

Записи представляются в виде модельных объектов с полями, тем самым в приложении возникает логическое разграничение по типам данных: одна таблица — один тип данных. Таблица «Пользователи» — тип данных «Пользователь» (поля: ID, ФИО, адрес). Таблица «Сообщения» — тип данных «Сообщение» (поля: ID, Тема сообщения, Тело сообщения, Кому, От кого, Дата). И так далее.

Таким образом, вся логика крутится вокруг таблиц и типов данных:

1. Модельный объект, представляющий тип данных.
2. Сериализация модельного объекта для сохранения его в таблицу.
3. Десериализация модельного объекта из базы данных.
4. Преобразования модельного объекта: подсчёт какой-то статистики,
сортировка данных, поиск данных и т.д.
5. Сериализация модельного объекта для передачи его по сети.
6. Десериализация модельных объектов, приходящих по сети.
7. Выставленный в сеть web-сервис, соответствующий данному типу
данных.

Всё это выстраивается в своего рода стек, а само серверное приложение состоит из нескольких таких независимых стеков, каждый из которых соответствует какому-то типу данных.

Грубо говоря, если по спецификации API у вас имеется web-сервис api.domain.com/1-0/messages с классическим CRUD-интерфейсом — это означает, что за ним стоит вышеупомянутый «стек» для типа данных «сообщение».

Create = POST;
Read = GET;
Update = PUT;
Delete = DELETE.

N.B. Существуют различные интерпретации операторов POST и PUT. В одних реализациях POST отвечает за создание сущностей, в других — за обновление. Канонического трактования, увы, не существует, но в этом нет ничего страшного.

Как правило, сервер поддерживает стандартизованный набор запросов, дифференцируемый посредством URL-суффиксов к соответствующему web- сервису:

{api}/messages/{id} — операции с сущностью по заданному ID;
{api}/messages/{id}/{property} — операции с полем {property} сущности с ID = {id}.

ИТОГО:

Всё, что нам следует сделать — это изготовить вилку к розетке.
А именно:

1. Оформить транспортный уровень приложения в виде сущности, которая будет инкапсулировать под собой HTTP-соединение вместе со всеми его настройками (включая безопасность, таймауты и пр.)
• Интерфейс сущности должен представлять собой точно такой же CRUD, как и выставленный в Интернет web-сервис. Если есть доступ к адресу {api}/messages — должны быть соответствующие четыре метода; если есть доступ к GET {api}/messages/{id}/{property} — сделайте отдельный метод, который будет получать данные этой {property}.
• Сущность обязана быть модульной. Если в один прекрасный момент ваш замечательный web-сервис {api}/messages перестанет функционировать, вам достаточно будет реализовать одну-единственную сущность, повторяющую интерфейс транспортного уровня, но берущую данные с
файловой системы.

2. Оформить парсеры и сериализаторы, которые будут генерировать модельные объекты и, наоборот, преобразовывать их в вид, удобоваримый для вашего транспортного уровня.
Парсеры и сериализаторы несут в себе знания о том, как преобразовывать модельные объекты в словари а-ля JSON или XML. Не пытайтесь прикрутить эти знания к самим модельным объектам — они никак не относятся к другим слоям приложения.
• Существует русскоязычный термин «топографический преобразователь», который мог бы заменить англоязычные «парсер» и «сериализатор», но только вот сам термин как-то не прижился…

3. Оформить сущность, которая будет отвечать за кеширование.
• Сущность должна просто уметь потокобезопасно предоставлять доступ к полученным данным, переписывать и обновлять эти данные более свежими.

4. Оформить сам «сервис» — сущность, которая будет ответственна за координацию транспортного уровня, парсеров, сериализаторов и кеша.
• Интерфейс сервиса должен полностью соответствовать бизнес-требованиям слоя UI. Если для UI необходимо получить какую-то сущность — должен быть метод для получения этой сущности. Если UI требует сохранить сущность с определёнными параметрами — у сервиса должен быть метод с этими параметрами. В случае, если UI требует массив объектов данного типа, отсортированный по какому-то критерию, — сервис должен предоставлять соответствующий метод, который вернёт отсортированный массив.
• И да, не забываем классический принцип: слои приложения общаются между собой посредством модельных объектов. Использование словарей/map для передачи данных недопустимо — только строгая типизация.

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

Сколько типов данных — столько и сервисов. Эту цифру довольно просто вычислить из спецификации API.

N.B. «Чистые» сервисы в природе встречаются не так часто. Бывает, тот или иной сервис обслуживает сразу несколько типов данных. Это обусловлено тем, что модельные сущности могут быть вложенными друг в друга: одна сущность может нести в себе массив объектов другого типа, или иметь подобный объект в качестве свойства.

В этом нет ничего страшного, просто ваши парсеры и сериализаторы должны адекватно воспринимать подобного рода вложенность, и генерировать объекты соответствующих типов.

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

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

Выстраивание уровня model подобным образом суть реализация паттерна слоистой архитектуры с наложением идей сервис-ориентированной архитектуры.

Мы не изобрели ничего нового.

Уровень UI


ВРЕМЯ ЗАМЕЧАТЕЛЬНЫХ ИСТОРИЙ

Итак, вы пришли на проект, скачали с репозитория исходники разрабатываемого приложения. Какой самый простой способ выяснить, что это приложение делает? Правильно! Запустить его! Перед вами знакомые таблицы с ячейками, стилизованные кнопки, барабаны с датами, навигация, названия экранов… Теперь задача: как соотнести это всё с исходным кодом, который разбросан по нескольким сотням файлов, что маячат перед вами в IDE?

Мы уже обособили один из уровней приложения — фактически model теперь может выступать совершенно самостоятельным модулем, который можно переподключить к какому-нибудь другому проекту, использующему тот же back-end. Или даже переиспользовать для другого back-end’a, если вы соблюли все принципы проектирования и сохранили определённый уровень абстракции.

Но вот незадача: у нас ещё целых два (!) слоя, а единственная намётка на то, что и где в исходных кодах находится — это само работающее приложение, запущенное на устройстве или в эмуляторе. Тут-то на помощь и приходят пользовательские истории.

Если ваше приложение хоть сколько-нибудь соответствует здравому смыслу, оно будет следовать определённым пользовательским историям, которые декларируют сценарии, необходимые для реализации того или иного действия. Самый классический пример — это истории регистрации и авторизации. Во время регистрации пользователь вводит какие-то личные данные, эти данные проверяются на адекватность, задаются логины и пароли, всё это может быть сдобрено мерами безопасности в виде SMS и так далее. Соответственно, в приложении существует целая история, касающаяся регистрации. Следуя логике, дабы облегчить собственную жизнь, наиболее очевидным решением будет следование этим пользовательским историям при построении структуры уровня UI.

Более того, SDK приложений под iOS предоставляют весь необходимый для этого инструментарий: достаточно просто завести под каждую историю свой Storyboard — и разделить структуру проекта так, чтобы все классы строились вокруг этих историй. Далее эти Storyboard’ы можно даже динамически соединять друг с другом посредством нехитрых библиотек, но это уже детали реализации.

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

N.B. И да, конечно, не забываем классические принципы проектирования. Применяем SOLID, DRY, KISS и YAGNI по полной программе, главное — не лениться, и разделять сущности в соответствии с ответственностью, которую они несут. Остальное подскажет опыт.

Можно набросать приблизительную диаграмму классов:

Естественно, совершенству нет предела, и данный подход не претендует на звание эталонного.

Заключение


ИТОГИ И ОБСУЖДЕНИЕ

Мы кратко рассмотрели, как можно возвести стройный архитектурный дизайн мобильного приложения. Для этого мы применили сплав из классических шаблонов проектирования (MVC, SOA и Multilayered Architecture), прибегнув также к логическому разделению исходного кода на модули, основываясь на пользовательских историях.

А теперь небольшой вопрос к community:
Использовал ли кто-нибудь на боевых проектах архитектуру View-Interactor-Presenter-Entity-Router, и каким образом было достигнуто переиспользование сущности/шаблона Router? Как можно избежать разрастания Router’a?
Автор: @redmadrobot
REDMADROBOT
рейтинг 109,80
№1 в разработке мобильных решений для бизнеса

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

          И было бы совсем замечательно, если бы можно было посмотреть исходники, хотя бы критичные с т.з. архитектуры части :)
  • 0
    Ох, уж эти архитектуры!

    Являюсь одним из разработчиком одного 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)

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

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

  • –1
    Очень вдохновляющая статья!
    Сам только начинаю разрабатывать кое какие мелочи, перейдя из сисадминской области IT.
    И в ближайшем будущем уже собираюсь замутить небольшой проектик — который будет на самом деле школой в начале пути.

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

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

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

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

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

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

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



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

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

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

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

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

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

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

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

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

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

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

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка