Pull to refresh

RxVMS — практичная архитектура для Flutter-приложений

Reading time5 min
Views12K
Original author: Thomas Burkhart

Это первый пост из серии публикаций, в которых объясняется мое понимание архитектуры приложений для Flutter. Предупреждаю — это будет весьма самоуверенным.


Пока запланированы:



Предисловие


Я в программировании уже около 20 лет. Начал мобильную разработку 4 года назад с Xamarin.Forms, ибо кроссплатформенность была единственной побудительной причиной для меня в качестве инди-разработчика. Xamarin.Forms буквально толкают тебя к использованию паттерна MVVM, так как определение UI ведется в XAML, и тебе необходим какой-то слой, чтобы склеивать UI с Моделью. В процессе работы с Xamarin я познакомился с ReactiveUI и был буквально покорен потоками и реактивными расширениями (Rx), сделавшими мои приложения более надежными.


В то время, как в Xamarin.Forms MVVM были "из-коробки", при переходе к Flutter я был удивлен, что в нем не было никаких похожих шаблонов проектирования. Я начал исследовать различные предлагаемые подходы, но ничего из имеющегося не удовлетворило меня в полной мере:


  • InheritedWidget: никак не получалось заставить обновлять только изменившуюся часть дерева виджетов, так что я использовал его лишь для доступа к классам модели, публикующим dart-потоки (Dart Streams), но вскоре отказался от этой идеи в пользу шаблонных Service Locator
  • Scoped Model поинтереснее, нежели InheritedWidget, однако не давал мне столько гибкости, к которой я привык с ReactiveUI
  • Redux был тем шаблоном, который рекомендовало множество разработчиков, знакомых с React Native. У меня есть целый пост на тему, почему он мне не по душе
  • BLoC: если бы я не приступил уже к разработке собственного паттерна в то время, когда BLoC стал продвигаться, скорее всего я бы взялся за него, так как это действительно гибко и реактивно. Что мне не нравится, так это то, что он публикует приемники потоков (Stream Sinks) и я не могу просто взять и передать функции или команды в обработчик событий виджета. Кроме того, BLoC не говорит вам, как следует структурировать ваше приложение в целом, нет также четкого определения, насколько большим должен некий BLoC или же какова его область действия
  • MVVM: так как я работал именно с ним, это первое, что я надеялся претворить во Flutter. Но нет! Смысл ViewModel чтобы изящно обеспечить представление вашей модели во View посредством привязок. Но Flutter не обновляет свои модели новыми данными, он всегда перестраивает их, как я уже описывал. Кроме того, ViewModels должна всегда быть синхронизирована с базовой моделью, что приводит к неприятным багам, и реальность показывает, что обещанное преимущество повторного использования ViewModels в приложениях почти никогда не достигается. У Adam Pedley есть отличный пост по поводу этих недостатков

Жестокая правда об избыточности слоев


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


  • переиспользовать слои в других проектах
  • прозрачно заменять один слой на другой
  • упрощать тестирование

Однако:


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

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


Идеальная архитектура для Flutter


Так что же я ожидаю от идеальной архитектуры?


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

Самостоятельность виджетов


Исходя из природы элементов интерфейса "без состояния" (stateless) ни одна страница/виджет во Flutter не должна зависеть от других или влиять на них. Это приводит к мысли, что каждая страница/виджет должна автономно нести ответственность за отображение себя и всех своих взаимодействий с пользователем.


RxVMS


RxVMS это эволюция паттерна RxVAMS, описанного в предыдущем посте, в процессе практического применения которого были выявлены и исправлены некоторые проблемы.

Текущий результат всех этих мыслей — паттерн RxVMS, или Rx-View-Managers-Services. Он выполняет все вышеперечисленные задачи с единственным требованием, что вы должны понимать потоки и элементы Rx. Чтобы помочь вам с этим, я посвящаю следующий пост.


Вот краткая схема моего приложения


image


Services (Службы)


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


Managers (Менеджеры)


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


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


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


Менеджеры могут взаимодействовать между собой.


Views (Отображение, вьюхи)


Как правило это StatefullWidget или StreamBuilder, которые в состоянии использовать данные из менеджеров и сервисов. Это может быть целая страница или виджет. Вьюхи не хранят никакие состояния и могут напрямую контактировать с сервисами пока соблюдается это правило.


Domain Objects (объекты предметной области)


Хоть они и отсутствуют в диаграмме, это важные сущности, которые представляют бизнес-модель. В других паттернах они могут принадлежать отдельному слою (бизнес-модели) совместно с бизнес-логикой. Здесь, в RxVMS, они не содержат никакой логики, изменяющей состояние приложения. Почти всегда это простые типы данных — plain data objects (если бы я включил их в паттерн, он бы стал выглядеть как RxVMMS, что длинновато, и ведет к путанице — VM мог бы быть неправильно воспринят, как ViewModel). Логически domain objects располагаются в слое менеджеров.


В следующих постах будет раскрыта сущность и взаимодействие этих элементов.

Tags:
Hubs:
+5
Comments5

Articles