[Разработка мобильных приложений, Dart, Flutter] Model-Widget-WidgetModel, или какой архитектурой пользуется Flutter-команда в Surf
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Привет, меня зовут Артём. Я руководитель Flutter-разработки в Surf и со-ведущий FlutterDev подкаста.
Flutter-отделу в Surf уже больше года. За это время мы сделали несколько проектов: от маленьких служебных, до полноценных е-коммерс и банкинга. Как минимум, многие из вас уже могли видеть приложение аптеки «Ригла». В статье я расскажу про недавно вышедший пакет mwwm — архитектуру, на которой построены все наши проекты.
Что такое MWWM?
MWWM — это архитектура и реализация паттерна Model-View-ViewModel, которую мы в Surf переложили на Flutter. Мы заменили слово View на Widget, потому что View не очень часто используется во Flutter и так будет нагляднее для разработчиков. Главное, что она позволяет делать — разделять вёрстку и логику, как бизнесовую, так и презентационного слоя.
Немного истории
Почему именно MWWM мы используем в Surf? Давайте вернёмся к началу 2019 года, когда у нас зародился Flutter отдел. Что было на тот момент?
Flutter зарелизился буквально месяц назад: хайп начал подниматься, активных игроков на рынке и в опенсорсе пока нет. Отличное время чтобы залететь, занять неплохое место в индустрии и завоевать внимание, не правда ли? Фреймворк молод, коммьюнити развивается и это открывает все дороги.
В это время от Android-отдела Surf отделяется Flutter направление. И сразу же стоит задача сначала заложить основу для наших будущих приложений, а потом уже кодить. Конечно же, одним из основных моментов здесь являлся выбор архитектуры.
В начале 2019 года нет явно выделяющегося мнения в комьюнити какую архитектуру использовать (хотя и сейчас ведутся активные холивары). Да, есть основные концепции: BLoC, Redux, Vanilla, MobX и тд. Самыми массовыми являются BLoC и Redux. Замечу, что речь ведётся не о пакетах, а о концепции.
Итак, встал вопрос BLoC или Redux нам взять? Или же придумать нечто своё?
Почему мы не BLoC?
Business Logic Component — отличная концепция. Блоки кода, по факту, «чёрные ящики» с некоторым входным воздействием и выходным потоком, внутри которых крутится бизнес-логика на чистом Dart — это просто потрясающе. Чистый Dart для кросс-платформенного переиспользования с вебом (да, тогда было далеко до Flutter for Web и сайты писали на Angular Dart, при необходимости). Удобство использования и достаточная изоляция самих блоков. Круто одним словом. Но есть одно «но»: где писать логику презентационного слоя? Где писать навигацию? Как работать с чисто UI событиями?
Чуть позже вышел в релиз всем известный Bloc от Felix Angelov. А также flutter_bloc. Многие стали рассматривать блоки как логику презентационного слоя, но это уже не вязалось с аббревиатурой. Сама же библиотека не давала указаний, где писать подобную логику, например, валидировать поля ввода. Это было неприемлемо для выбора хорошей архитектуры.
Redux?
Как я написал выше — мы выходцы из Android разработки. В то время в мире царили Clean Architecture, подходил MVVM. Веб-технологии были чем-то чуждым и непонятным. Redux мы быстро отмели: на него надо было переучиваться довольно долго и отчасти менять мышление, к которому мы привыкли во время Android-разработки с Rx и CleanArchitecture.
Отбросив основные концепции и принимая во внимание реалии Surf, мы поставили задачу создать архитектуру, которая позволит Android-разработчикам из Surf при необходимости быстро конвертироваться во Flutter-разработчиков. И чтобы при необходимости Flutter-разработчики могли быстро перейти на Android, потому что архитектура понятна, знакома, а язык выучить несложно. Так и появился Model – Widget – WidgetModel.
Model-Widget-WidgetModel
Выглядит он примерно вот так.
Эту картинку можно видеть на странице в GitHub. Здесь есть несколько основных частей.
- Widget-UI — та самая вёрстка.
- WidgetModel — логика этого самого UI и его состояния.
- Model — контракт с сервисным слоем. На данный момент это экспериментальная часть. Исторически у нас использовались Interactor’ы напрямую в WM.
Пройдёмся по каждой из этих частей поподробнее.
Widget в нашем контексте — полностью пассивная вёрстка. Максимум что допускается в ней – наличие условного оператора, когда мы пишем там if (условие), покажи мне это, иначе – покажи мне loader. А всё остальное: вычисления этих условий, обработка нажатий и так далее — уходят в WidgetModel. Причём виджетом может быть как целый экран, так и конкретный маленький элемент на экране со своим состоянием.
Сразу замечу, кроме MwwmWidget (буду называть их так, чтобы не путаться) мы также используем обычные Flutter-виджеты. Потому что куда без них? Есть места, где нет необходимости усложнять простенький переключатель ради архитектуры.
WidgetModel – это, по сути, состояние виджета и описание логики его работы. Что входит в логику? Это обработка тех или иных действий пользовательского интерфейса. Это обращение к любым другим слоям, которые стоят выше. Это вычисление тех или иных значений, обработка, mapping данных, необходимых для вёрстки. В общем-то, все то, чем должна заниматься стандартная ViewModel.
Рассмотрим на небольшом примере.
Допустим, у нас есть некоторый экран и у него есть некоторое состояние и кнопка, которая входит в сеть. Widget экрана будет содержать только вёрстку. При этом состояние его и реакция на взаимодействия в WidgetModel. Замечу, что WM описывает также некоторые микросостояния на экране. Этими микро-состояниями могут являться Stream’ы.
class SomeWidgetModel{
final _itemsController = StreamController<List<Items>>.broadcast();
get items => itemsController.stream;
}
Внутри команды мы как раз используем стримы (а точнее обёртку над ними) внутри виджет-модели. Поэтому по форме она очень сильно похожа на конценпцию BLoC’а. Это коробка с input/output. Каждый input — это действие пользователя или событие, output — данные, влияющие на UI. Как я уже сказал, каждый стрим — микросостояние внутри виджет-модели. Такими состояниями могут быть маленькие элементы: кнопка, которая дизейблится при каких-то условиях; поток текста с экрана и т.д.
Вспомним о кнопке на экране. Её состояние может быть одним из таких стримов и тогда она просто будет обернута в StreamBuilder на экране. Но надо понимать, что в этом случае логика кнопки должна быть довольно проста.
Stream<bool> get isBtnDisabled => btnController.stream;
А теперь представим, что такая кнопка встречается ещё в 5 местах по всему приложению. И кроме некоторого изменения состояния на, к примеру, дизейбл, она ещё и триггерит запрос в Сеть. Причём запрос один и тот же, у него лишь разные аргументы. В этом случае каждый раз копипастить логику из экрана в экран, прописывать запросы и вообще загрязнять виджет-модель не очень хорошо. Гораздо лучше просто выделить эту кнопку в Widget+WidgetModel и целиком и полностью переиспользовать из экрана в экран, передавая те или иные параметры на вход.
Ещё одно важное замечание, что виджет-модель – единственный способ привести виджет или часть виджета в некоторое состояние. Что под этим надо понимать? В Flutter, например, мы можем передавать некоторые аргументы виджет в конструктор. Это полезно, если вы используете stateless-виджеты.
Но что у нас получается при использовании WM? Самим состоянием виджета должна управлять именно виджет-модель. Это главный источник. Если передаём какие-то данные в виджет, то мы должны передать их в WM, и только из неё их взять. Напрямую эти данные использовать в виджете нельзя, потому что иначе получается два пути приведения виджета в состояние. И это вас в конечном счёте может запутать или привести к очень неочевидным багам, которых лучше не допускать.
Поток данных W-WM
Какой поток данных происходит между виджетом и виджет-моделью? Из виджета идут некоторые действия — события пользовательского интерфейса. Из виджет-модели идут некоторые состояния в виджет. Состояния могут быть в виде потоков (так делаем мы в Surf), могут быть просто переменными. На эти состояния мы подписываем части виджета с помощью StreamBuilder’ов.
//…
child: StreamBuilder<Item>(
stream: wm.item,
builder: (ctx, snapshot) => //...
),
Важно заметить, что связь между Widget и WidgetModel явно не прописана внутри пакета. Мы не стали сужать возможности фреймворка и дали свободу пользователям пакета самим определять связь. При этом, тот подход, что работает в нашей компании является отдельным пакетом на pub.dev.
Relation
Мы рекомендуем использовать наш пакет MWWM вместе с модулем Relation. Relation – это связь, которую мы используем между виджетом виджет-моделью. Это просто семантическая обёртка над потоками. У нас есть некоторые потоковые состояния в виде StreamedState и действия под названием Action. С Relation довольно просто работать.
final toggleAction = Action<int>();
final contentState = StreamedState<int>(0);
//…
subscribe(toggleAction.stream, (data) => contentState.accept(data));
Обработка ошибок
В больших проектах очень важно правильно обрабатывать ошибки. В рамках MWWM предусмотрен специальный интерфейс ErrorHandler, который обязательно поставляется в WM. WidgetModel перехватывает ошибки, которые приходят из сервисного слоя (или происходят внутри презентационного), и передаёт их обработчику. Автоматическая обработка происходит при использовании методов WM с постфиксом ...HandleError().
subscribeHandleError(someAction, (data) => doOnData());
doFutureHandleError(someFuture, (data) => doOnData());
Реализацию ErrorHandler можно посмотреть в примере проекта.
Model
Model – это та самая экспериментальная и недавно появившаяся фича, которая пока что опциональна в рамках архитектуры. Это описание контракта между виджет-моделью и бизнес-логикой приложения.
Рассмотрим подробнее.
Model — контракт и унифицированное АПИ для взаимодействие с сервисным слоем. По факту это конкретная сущность с двумя методами: perform и listen. При этом в модель передаётся некоторый список Performer’ов, но обо всём по порядку.
Суть взаимодействия основана на том, что WM говорит: «Model, хочу внести Изменение (совершить действие) в сервисный слой. Сделай это» и ждёт результата. Такая конструкция позволяет полностью абстрагироваться друг от друга при написании кода, просто условившись на контракте.
Change
Это первая часть контракта и это класс, который описывает намерение что-либо изменить, получить и тип получаемого результата. Он может содержать данные, но не может содержать никакой логики. Просто data-класс в терминах того же Kotlin.
class Authenticate extends FutureChange<Result> {
final String name;
Authenticate(this.name);
}
Performer
Вторая часть контракта – Performer. Performer – это реализация логики. Самый близкий аналог перформера — UseCase. Это такая функциональная часть контракта. Если Change – это параметры, название метода, то Performer – тело метода и, по сути, код, который исполняется по этому Change. В перформер могут поставляться любые сервисы, бизнес-логика, интеракторы и так далее. Такая конструкция полностью отвязывает виджет-модель от реализации этой бизнес-логики.
Идеальный перформер – это только одно действие. То есть это такой очень атомарный кусок кода, который просто выполняет что-то одно и отдаёт результат. Performer однозначно связан с типом Change.
class AuthPerformer extends FuturePerformer<Result, Authenticate> {
final AuthService authService;
AuthPerformer(
// сущности, которые нужны перформеру для работы
this.authService,
);
Future<Result> perform(Authenticate change) {
return authService.login(change.name);
}
}
Был один метод, стало два класса
Зачем это надо было разделять? Потому что если у интерактора был один метод, то здесь у нас получается два класса вместо одного метода. Но таким образом из виджет-модели исчезает полностью информация об интеракторах, о реализации сервисного слоя. Всё что нужно – знать, что вы хотите сделать. То есть знать Change. И предоставить Model с необходимым набором перформеров.
Неприятный минус: если вы не предоставили Performer, то узнаете об этом только в рантайме.
Бизнес-логика
Тут полная свобода действий. MWWM не декларирует, как реализовать бизнес-логику вашего приложения. Рекомендуется использовать тот подход, что принят в вашей команде. В Surf мы используем CleanArchitecture, я в своём pet-проекте работаю с сервисами, которые поставляются в перформеры и там работают. Тут может быть действительно всё, что угодно. Вся суть MWWM в том, что он довольно гибок в использовании, и его можно адаптировать под свою команду.
Стек Surf
Наш стек в Surf – это вот такой набор пакетов для архитектуры.
По факту для нас это один пакет под названием surf_mwwm. Если интересно посмотреть подробнее, то можно найти на нашемGitHub.
На диаграмме:
- injector — пакет для реализации DI. Основан на InheritedWidget. Маленький и простой.
- relation — связь между слоями.
- mwwm — герой этой статьи
- surf_mwwm — всё вместе с небольшой добавочкой, специфичной для нашей команды.
Вместо заключения
Надеюсь, данный подход и пакет поможет командам. Особенно большим, потому что тут можно, описав контракты между слоями, параллельно работать над фичами, и даже никак не сталкиваться в работе над одними и теми же файлами. Данный факт позволяет быть автономными.
Человеку, который пишет проект в одиночку, это может показаться сильным оверхедом и замедлением разработки. Можно написать гораздо быстрее на других фреймворках, где всё поставлено на рельсы, в ущерб гибкости и кастомизации. Здесь же больше свободы и гибкости для адаптации под свои нужды.
Но в любом случае этот подход позволяет полностью разделить все слои, что явно улучшает читаемость и поддержку написанного кода.
Если вкратце, что мы имеем по MWWM:
Плюсы:
- Разделяет и изолирует все слои (UI, Presentation Logic, Business Logic).
- Не имеет лишних зависимостей.
- Гибко настраивается под нужды команды.
- Позволяет описать контракт и работать параллельно.
Минусы:
- Слишком сложно, если пишешь «в одного».
- Многословное описание контракта.
MWWM – это часть нашего большего репозитория. У неё есть свой отдельный репозиторий — SurfGear. Это набор наших стандартов и библиотек, которые мы используем в Surf.
Часть этих библиотек уже в релизе на pub.dev:
И команда Surf не собирается останавливаться на этом.
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка под iOS, Разработка мобильных приложений, Разработка под Android, Kotlin] Архитектурный шаблон MVI в Kotlin Multiplatform, часть 2
- Google и Canonical реализовали во Flutter поддержку создания десктоп-приложений для Linux
- [DIY или Сделай сам, Интернет вещей, Компьютерное железо, Разработка на Raspberry Pi, Разработка под Linux] Встраиваемый компьютер AntexGate. От прототипа к серийному производству
- [Анализ и проектирование систем] Документирование микросервисов в LeanIX (EAM)
- [Информационная безопасность, Разработка мобильных приложений, Разработка под iOS, Тестирование мобильных приложений] Приложение LinkedIn под iOS тоже читает контент из буфера обмена, компания назвала это багом
- [Habr, IT-компании, Карьера в IT-индустрии, Управление медиа] Как вести технический блог?
- [PostgreSQL, Ruby on Rails, Разработка мобильных приложений] Оптимизация SQL запросов или розыск опасных преступников
- [Kotlin, Разработка мобильных приложений, Разработка под Android] Приручая MVI
- [IT-инфраструктура, ReactJS, Планшеты, Разработка мобильных приложений] «Сим-сим, откройся!»: доступ в ЦОД без бумажных журналов
- [Разработка мобильных приложений, Разработка под iOS] Бюджетный DI на антипаттернах
Теги для поиска: #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_dart, #_flutter, #_surf, #_flutter, #_mobile, #_architecture, #_mobile_development, #_razrabotka_mobilnyh_prilozhenij (разработка мобильных приложений), #_blog_kompanii_surf (
Блог компании Surf
), #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
), #_dart, #_flutter
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:56
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Привет, меня зовут Артём. Я руководитель Flutter-разработки в Surf и со-ведущий FlutterDev подкаста. Flutter-отделу в Surf уже больше года. За это время мы сделали несколько проектов: от маленьких служебных, до полноценных е-коммерс и банкинга. Как минимум, многие из вас уже могли видеть приложение аптеки «Ригла». В статье я расскажу про недавно вышедший пакет mwwm — архитектуру, на которой построены все наши проекты. Что такое MWWM? MWWM — это архитектура и реализация паттерна Model-View-ViewModel, которую мы в Surf переложили на Flutter. Мы заменили слово View на Widget, потому что View не очень часто используется во Flutter и так будет нагляднее для разработчиков. Главное, что она позволяет делать — разделять вёрстку и логику, как бизнесовую, так и презентационного слоя. Немного истории Почему именно MWWM мы используем в Surf? Давайте вернёмся к началу 2019 года, когда у нас зародился Flutter отдел. Что было на тот момент? Flutter зарелизился буквально месяц назад: хайп начал подниматься, активных игроков на рынке и в опенсорсе пока нет. Отличное время чтобы залететь, занять неплохое место в индустрии и завоевать внимание, не правда ли? Фреймворк молод, коммьюнити развивается и это открывает все дороги. В это время от Android-отдела Surf отделяется Flutter направление. И сразу же стоит задача сначала заложить основу для наших будущих приложений, а потом уже кодить. Конечно же, одним из основных моментов здесь являлся выбор архитектуры. В начале 2019 года нет явно выделяющегося мнения в комьюнити какую архитектуру использовать (хотя и сейчас ведутся активные холивары). Да, есть основные концепции: BLoC, Redux, Vanilla, MobX и тд. Самыми массовыми являются BLoC и Redux. Замечу, что речь ведётся не о пакетах, а о концепции. Итак, встал вопрос BLoC или Redux нам взять? Или же придумать нечто своё? Почему мы не BLoC? Business Logic Component — отличная концепция. Блоки кода, по факту, «чёрные ящики» с некоторым входным воздействием и выходным потоком, внутри которых крутится бизнес-логика на чистом Dart — это просто потрясающе. Чистый Dart для кросс-платформенного переиспользования с вебом (да, тогда было далеко до Flutter for Web и сайты писали на Angular Dart, при необходимости). Удобство использования и достаточная изоляция самих блоков. Круто одним словом. Но есть одно «но»: где писать логику презентационного слоя? Где писать навигацию? Как работать с чисто UI событиями? Чуть позже вышел в релиз всем известный Bloc от Felix Angelov. А также flutter_bloc. Многие стали рассматривать блоки как логику презентационного слоя, но это уже не вязалось с аббревиатурой. Сама же библиотека не давала указаний, где писать подобную логику, например, валидировать поля ввода. Это было неприемлемо для выбора хорошей архитектуры. Redux? Как я написал выше — мы выходцы из Android разработки. В то время в мире царили Clean Architecture, подходил MVVM. Веб-технологии были чем-то чуждым и непонятным. Redux мы быстро отмели: на него надо было переучиваться довольно долго и отчасти менять мышление, к которому мы привыкли во время Android-разработки с Rx и CleanArchitecture. Отбросив основные концепции и принимая во внимание реалии Surf, мы поставили задачу создать архитектуру, которая позволит Android-разработчикам из Surf при необходимости быстро конвертироваться во Flutter-разработчиков. И чтобы при необходимости Flutter-разработчики могли быстро перейти на Android, потому что архитектура понятна, знакома, а язык выучить несложно. Так и появился Model – Widget – WidgetModel. Model-Widget-WidgetModel Выглядит он примерно вот так. Эту картинку можно видеть на странице в GitHub. Здесь есть несколько основных частей.
Пройдёмся по каждой из этих частей поподробнее. Widget в нашем контексте — полностью пассивная вёрстка. Максимум что допускается в ней – наличие условного оператора, когда мы пишем там if (условие), покажи мне это, иначе – покажи мне loader. А всё остальное: вычисления этих условий, обработка нажатий и так далее — уходят в WidgetModel. Причём виджетом может быть как целый экран, так и конкретный маленький элемент на экране со своим состоянием. Сразу замечу, кроме MwwmWidget (буду называть их так, чтобы не путаться) мы также используем обычные Flutter-виджеты. Потому что куда без них? Есть места, где нет необходимости усложнять простенький переключатель ради архитектуры. WidgetModel – это, по сути, состояние виджета и описание логики его работы. Что входит в логику? Это обработка тех или иных действий пользовательского интерфейса. Это обращение к любым другим слоям, которые стоят выше. Это вычисление тех или иных значений, обработка, mapping данных, необходимых для вёрстки. В общем-то, все то, чем должна заниматься стандартная ViewModel. Рассмотрим на небольшом примере. Допустим, у нас есть некоторый экран и у него есть некоторое состояние и кнопка, которая входит в сеть. Widget экрана будет содержать только вёрстку. При этом состояние его и реакция на взаимодействия в WidgetModel. Замечу, что WM описывает также некоторые микросостояния на экране. Этими микро-состояниями могут являться Stream’ы. class SomeWidgetModel{
final _itemsController = StreamController<List<Items>>.broadcast(); get items => itemsController.stream; } Внутри команды мы как раз используем стримы (а точнее обёртку над ними) внутри виджет-модели. Поэтому по форме она очень сильно похожа на конценпцию BLoC’а. Это коробка с input/output. Каждый input — это действие пользователя или событие, output — данные, влияющие на UI. Как я уже сказал, каждый стрим — микросостояние внутри виджет-модели. Такими состояниями могут быть маленькие элементы: кнопка, которая дизейблится при каких-то условиях; поток текста с экрана и т.д. Вспомним о кнопке на экране. Её состояние может быть одним из таких стримов и тогда она просто будет обернута в StreamBuilder на экране. Но надо понимать, что в этом случае логика кнопки должна быть довольно проста. Stream<bool> get isBtnDisabled => btnController.stream;
А теперь представим, что такая кнопка встречается ещё в 5 местах по всему приложению. И кроме некоторого изменения состояния на, к примеру, дизейбл, она ещё и триггерит запрос в Сеть. Причём запрос один и тот же, у него лишь разные аргументы. В этом случае каждый раз копипастить логику из экрана в экран, прописывать запросы и вообще загрязнять виджет-модель не очень хорошо. Гораздо лучше просто выделить эту кнопку в Widget+WidgetModel и целиком и полностью переиспользовать из экрана в экран, передавая те или иные параметры на вход. Ещё одно важное замечание, что виджет-модель – единственный способ привести виджет или часть виджета в некоторое состояние. Что под этим надо понимать? В Flutter, например, мы можем передавать некоторые аргументы виджет в конструктор. Это полезно, если вы используете stateless-виджеты. Но что у нас получается при использовании WM? Самим состоянием виджета должна управлять именно виджет-модель. Это главный источник. Если передаём какие-то данные в виджет, то мы должны передать их в WM, и только из неё их взять. Напрямую эти данные использовать в виджете нельзя, потому что иначе получается два пути приведения виджета в состояние. И это вас в конечном счёте может запутать или привести к очень неочевидным багам, которых лучше не допускать. Поток данных W-WM Какой поток данных происходит между виджетом и виджет-моделью? Из виджета идут некоторые действия — события пользовательского интерфейса. Из виджет-модели идут некоторые состояния в виджет. Состояния могут быть в виде потоков (так делаем мы в Surf), могут быть просто переменными. На эти состояния мы подписываем части виджета с помощью StreamBuilder’ов. //…
child: StreamBuilder<Item>( stream: wm.item, builder: (ctx, snapshot) => //... ), Важно заметить, что связь между Widget и WidgetModel явно не прописана внутри пакета. Мы не стали сужать возможности фреймворка и дали свободу пользователям пакета самим определять связь. При этом, тот подход, что работает в нашей компании является отдельным пакетом на pub.dev. Relation Мы рекомендуем использовать наш пакет MWWM вместе с модулем Relation. Relation – это связь, которую мы используем между виджетом виджет-моделью. Это просто семантическая обёртка над потоками. У нас есть некоторые потоковые состояния в виде StreamedState и действия под названием Action. С Relation довольно просто работать. final toggleAction = Action<int>();
final contentState = StreamedState<int>(0); //… subscribe(toggleAction.stream, (data) => contentState.accept(data)); Обработка ошибок В больших проектах очень важно правильно обрабатывать ошибки. В рамках MWWM предусмотрен специальный интерфейс ErrorHandler, который обязательно поставляется в WM. WidgetModel перехватывает ошибки, которые приходят из сервисного слоя (или происходят внутри презентационного), и передаёт их обработчику. Автоматическая обработка происходит при использовании методов WM с постфиксом ...HandleError(). subscribeHandleError(someAction, (data) => doOnData());
doFutureHandleError(someFuture, (data) => doOnData()); Реализацию ErrorHandler можно посмотреть в примере проекта. Model Model – это та самая экспериментальная и недавно появившаяся фича, которая пока что опциональна в рамках архитектуры. Это описание контракта между виджет-моделью и бизнес-логикой приложения. Рассмотрим подробнее. Model — контракт и унифицированное АПИ для взаимодействие с сервисным слоем. По факту это конкретная сущность с двумя методами: perform и listen. При этом в модель передаётся некоторый список Performer’ов, но обо всём по порядку. Суть взаимодействия основана на том, что WM говорит: «Model, хочу внести Изменение (совершить действие) в сервисный слой. Сделай это» и ждёт результата. Такая конструкция позволяет полностью абстрагироваться друг от друга при написании кода, просто условившись на контракте. Change Это первая часть контракта и это класс, который описывает намерение что-либо изменить, получить и тип получаемого результата. Он может содержать данные, но не может содержать никакой логики. Просто data-класс в терминах того же Kotlin. class Authenticate extends FutureChange<Result> {
final String name; Authenticate(this.name); } Performer Вторая часть контракта – Performer. Performer – это реализация логики. Самый близкий аналог перформера — UseCase. Это такая функциональная часть контракта. Если Change – это параметры, название метода, то Performer – тело метода и, по сути, код, который исполняется по этому Change. В перформер могут поставляться любые сервисы, бизнес-логика, интеракторы и так далее. Такая конструкция полностью отвязывает виджет-модель от реализации этой бизнес-логики. Идеальный перформер – это только одно действие. То есть это такой очень атомарный кусок кода, который просто выполняет что-то одно и отдаёт результат. Performer однозначно связан с типом Change. class AuthPerformer extends FuturePerformer<Result, Authenticate> {
final AuthService authService; AuthPerformer( // сущности, которые нужны перформеру для работы this.authService, ); Future<Result> perform(Authenticate change) { return authService.login(change.name); } } Был один метод, стало два класса Зачем это надо было разделять? Потому что если у интерактора был один метод, то здесь у нас получается два класса вместо одного метода. Но таким образом из виджет-модели исчезает полностью информация об интеракторах, о реализации сервисного слоя. Всё что нужно – знать, что вы хотите сделать. То есть знать Change. И предоставить Model с необходимым набором перформеров. Неприятный минус: если вы не предоставили Performer, то узнаете об этом только в рантайме. Бизнес-логика Тут полная свобода действий. MWWM не декларирует, как реализовать бизнес-логику вашего приложения. Рекомендуется использовать тот подход, что принят в вашей команде. В Surf мы используем CleanArchitecture, я в своём pet-проекте работаю с сервисами, которые поставляются в перформеры и там работают. Тут может быть действительно всё, что угодно. Вся суть MWWM в том, что он довольно гибок в использовании, и его можно адаптировать под свою команду. Стек Surf Наш стек в Surf – это вот такой набор пакетов для архитектуры. По факту для нас это один пакет под названием surf_mwwm. Если интересно посмотреть подробнее, то можно найти на нашемGitHub. На диаграмме:
Вместо заключения Надеюсь, данный подход и пакет поможет командам. Особенно большим, потому что тут можно, описав контракты между слоями, параллельно работать над фичами, и даже никак не сталкиваться в работе над одними и теми же файлами. Данный факт позволяет быть автономными. Человеку, который пишет проект в одиночку, это может показаться сильным оверхедом и замедлением разработки. Можно написать гораздо быстрее на других фреймворках, где всё поставлено на рельсы, в ущерб гибкости и кастомизации. Здесь же больше свободы и гибкости для адаптации под свои нужды. Но в любом случае этот подход позволяет полностью разделить все слои, что явно улучшает читаемость и поддержку написанного кода. Если вкратце, что мы имеем по MWWM: Плюсы:
Минусы:
MWWM – это часть нашего большего репозитория. У неё есть свой отдельный репозиторий — SurfGear. Это набор наших стандартов и библиотек, которые мы используем в Surf. Часть этих библиотек уже в релизе на pub.dev: И команда Surf не собирается останавливаться на этом. =========== Источник: habr.com =========== Похожие новости:
Блог компании Surf ), #_razrabotka_mobilnyh_prilozhenij ( Разработка мобильных приложений ), #_dart, #_flutter |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:56
Часовой пояс: UTC + 5