Найти тему
Nuances of programming

Как создать масштабируемую архитектуру для крупных мобильных проектов

Оглавление

Источник: Nuances of Programming

Принципы создания крупного масштабируемого проекта

Для начала определим нормы разработки крупного приложения:

  1. Сокращение зависимостей. Любое изменение должно затрагивать как можно меньшее количество кода.
  2. Возможность повторного использования. Отдельные части кода должны быть пригодны для других проектов.
  3. Масштабируемость. Добавление новых функций в код не должно вызывать затруднений.
  4. Стабильность. У разработчика должна быть возможность отключить некоторые блоки кода с помощью переключателей функций. Это пригодится, когда нужно заблокировать устаревшие функции в старой версии приложения или избежать ошибок и сбоев в новой, особенно если в проекте применяется магистральная разработка (trunk-based development — TBD).
  5. Отзывчивость. Проект следует разделить на модули. Это позволит назначать ответственного за проверку для каждого из них, что значительно упростит ревью кода. Делается это через Github с помощью файла CODEOWNERS . Этот пункт применим не только к крупным частям приложения, таким как модули Gradle и Pod, но и к обычным функциям, за проверку которых отвечают разные специалисты.

Компонент

-2

На картинке представлен стандартный экран профиля. Как правило, для него используются архитектуры уровня представления (те, что начинаются с MV) и создаются классы Presenter/ViewModel/Interactor/Something.

Эта техника подходит для небольших команд, однако по мере их роста в ней возникает путаница. К примеру, при написании кода изменения в одних и тех же классах влияют друг на друга. У целой команды могут возникнуть проблемы с объединением веток. В результате не связанных напрямую изменений начинаются поломки в бизнес-логике. Разработчики будут вынуждены постоянно переписывать и обновлять юнит-тесты.

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

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

-3

Требования к компоненту

  1. Единая ответственность: должен представлять только один бизнес-объект.
  2. Простая реализация: должен содержать минимальное количество кода.
  3. Независимость: он не должен знать ничего о других компонентах в приложении и на экране.
  4. Анонимная связь: общение между компонентами должно происходить через специальный объект, который наблюдает за входящими событиями и не знает, какой именно компонент отправляет каждое из них.
  5. Определенное состояние UI: упрощает восстановление состояния экрана и предоставляет информацию о том, что видит пользователь в любой момент времени.
  6. Однонаправленный поток данных: состояние компонента должно быть однозначно определенным и неизменяемым. Это также означает, что приложение будет применять односторонне связанный поток данных и обладать единой точкой истины, которая способна изменять состояние.
  7. Удаленное управление: компоненты можно настроить через сервер. По крайней мере, стоит добавить возможность их отключения в любой момент времени с помощью переключателей функций. Это пригодится, если вы, например, решите больше не отображать стоимость продукта на экране.

Схема работы компонентов

-4
  1. Компонент получает данные (DomainObject или “внешнее состояние”) из внешнего источника (назовем его Service).
  2. Приложение применяет бизнес-логику компонента к входным данным, а затем создается новое состояние UI.
  3. Приложение отображает новое состояние пользователю.
  4. Если пользователь взаимодействует с компонентом (нажимает на кнопку, пролистывает страницу и т.д.), создается новое действие (Action). Оно перенаправляется к объекту, отвечающему за бизнес-логику компонента.
  5. Бизнес-логика решает, стоит ли создавать новое состояние UI или передать Action в Service.
  6. Другие компоненты могут просматривать данные из Service и обновлять их (пункт #1).

Архитектура экрана

Как было сказано ранее, компоненты на одном экране не знают ничего друг о друге, но могут отправлять Action в общие объекты  —  Service, которые позволяют им отслеживать изменения.

Service бывает двух типов:

  • Глобальные охватывают целое приложение (например, UserService, PaymentsService, CartService).
  • Локальные используются для каждого отдельного экрана (например, ProductDetailsService, OrderListService).
-5

Стоит упомянуть, архитектура слоя представления в проекте может быть любой (MVP/MVC/MVVM/MVI и прочие). Тем не менее каждый компонент должен учитывать указанные выше требования.

Машина состояний

Архитектура такого экрана представляет собой машину состояний, которая получает Action (событие) из UI и внешнего Service . На его основе она создает Data State (состояние данных). Затем компоненты обрабатывают его и отображают результаты в UI.

Определим новые объекты:

  • Функции промежуточной обработки (middleware) получают бизнес-логику. Они обрабатывают входящие Action и создают новое состояние. Также они могут создать новое Action, чтобы связаться с другими функциями промежуточной обработки или внешними объектами.
  • Редьюсер (reducer) получает текущее состояние и объединяет его с новым, полученным из middleware. Затем редьюсер отправляет объединенное состояние всем подписчикам.
-6

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

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

-7

Управляемый сервером интерфейс

Наличие автономных компонентов, в которые можно передавать входные данные (DomainObject) для отображения в UI, позволяет получать их список из сервера и динамично настраивать структуру экрана. Основным преимуществом этого подхода является возможность динамически изменять содержимое экрана без необходимости загружать новую версию приложения в Play Store или App Store. Это значительно упрощает работу маркетинговой команды.

-8

Мы получаем список компонентов из сервера. Каждый из них содержит информацию о положении на экране и необходимые для работы данные.

Ниже показан пример ответа.

Заключение

  • Разбейте экран на несколько небольших компонентов, каждый из которых будет отвечать за один бизнес-объект.
  • Следуйте указанным в статье правилам, чтобы повысить надежность этих компонентов.
  • Чтобы обеспечить связь между компонентами, задействуйте родительские объекты (в данном случае Service).
  • Однонаправленный поток данных и единая точка истины повышают стабильность приложения и позволяют сэкономить время при отладке и исправлении ошибок.
  • Назначение отвечающего за проверку отдельного компонента или функции улучшает устойчивость и качество кода.

Читайте также:

Читайте нас в Telegram , VK

Перевод статьи Alexey Glukharev : Scalable Architecture For Big Mobile Projects