Просто код
28 subscribers
9 photos
1 video
1 file
6 links
Сейчас пишу на языке Dart и последние несколько лет использую, в основном, Flutter. Походу дела я делаю пометки, когда изучаю новые подходы или получаю фидбек об успехе прошлых решений. Многие из них актуальны и для других языков.
Download Telegram
Channel created
Channel photo updated
Просто код
Channel photo updated
Designed by catalyststuff / Freepik
Введение

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

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

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

Основные действующие лица:

- Модель — это класс, описывающий единицу данных, с которыми работает приложение: "пользователь", "задача", "проект".
- Логика — это use cases, варианты использования. Это действия, которые пользователь может совершить над данными, чтобы их изменить. Например, "изменить права пользователя".
- Презентатор — платформо независимая штука, которая подготавливает данные к отображению и обрабатывает действия пользователя.
- ВьюМодель, ViewModel — класс, который содержит данные, необходимые для отображения интерфейса пользователя: подготовленные данные моделей, состояния экрана и т.д.
- Представление — реализация интерфейса пользователя, код, написанный на каком-то фреймверке, зависимый от платформы.
- data source — класс, загружающий данные из внешнего источника.
- Фактори — класс, который раздает конкретные реализации интерфейсов. Нужен для установки архитектурных границ.

Не все мысли являются законченными. Часто с опытом я их пересматриваю, ищу лучшие решения.
Безопасное программирование

Выделю два типа ошибок в коде: ошибки, которые есть прям щас, и ошибки будущие.

Первый тип ошибок устранять в основном просто: запусти код и убедись, что он работает. Можно даже написать тесты для проверки. Или даже сначала написать тесты, а потом код, который их проходит.

Второй тип ошибок сложный для обнаружения и исправления.

Например, в одном месте проекта есть перебор вариантов значения параметра value через множество if-else. Когда через месяц в другом месте проекта мы расширим набор возможных значений value, ничто не скажет нам о том, что нужно изменить первое место.

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

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

То есть, если мы пишем код определенным образом, ошибки становятся очевидными и простыми для исправления. Это я и называю безопасным программированием.

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

Однажды мальчик использовал на проекте мутабельные модели... И у него отвалилась жопа.

Были времена еще первых версий языка Dart и веб-фреймверка Polymer v0.5. И было задание написать программу для редактирования и просмотра всяких дашбордов. Дашборды состояли из виджетов, были группы, было миллион пропертей и внутренний язык программирования.

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

Был там такой интерфейс Observable, с помощью которого дарт мог заполнять HTML шаблоны данными. И вот пришла идея все-все данные этого дашборда хранить как observable-классы.

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

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

Но вот Google Chome сказал: "А ну ка все обновили Shadow Dom, а то ваши сайты работать не будут." То есть, надо было перебираться на новые версии своих фреймверков, так как спецификация WebComponents опять изменилась.

Полимер вдруг умер, а пакет observable обновился, потеряв обратную совместимость.

И к черту полимер, есть и другие фреймверки. Но вот только этот Observable насколько густо пропитал проект, что наверное, в нем увязло более 80% всех классов.

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

О чем это я. Никогда, никогда не используйте никакие сторонние библиотеки в слоях бизнес-логики и бизнес-моделей. И никогда не делайте модели прослушиваемыми. Пусть будут иммутабельными.
Use cases и два типа API

В клиент-серверных приложениях могу выделить два вида серверного API: CRUD и методы.

CRUD

В первом варианте модели хранятся по таблицам, и из front-end мы обращаемся к методам типа create, update, delete, read, readAll и так далее. То есть, у data source для каждого типа данных есть такие методы.

В таком варианте use cases принимают на себя много обязанностей:

- Разделяют сложные модели на более простые data objects (если нужно).
- Обращаются к соответствующим data sources по каждому типу таблиц в отдельности и принимают решения при обработке ошибок.
- Собирают результаты из разных data sources и формируют новые модели.
- Обновляют репозитории.

Методы сервера

Во втором варианте при хорошем серверном API, наверное, получится так, что имена методов будут совпадать с именами use cases.

Тогда выходит, что и у data source имена методов тоже будут такими, как имена методов API. У data sources все так же мало обязанностей, как и в варианте с CRUD: принять data objects, вызвать метод сервера, отдать data objects.

А uses cases становятся более "прозрачными". Возникает вопрос: а нужны ли они нам?

И вот что думаю: в них просто становится чуть меньше проверок, а обязанности они сохраняют прежние:

- Наверняка все еще придется иногда делать разделение одной модели на несколько более мелких (серверное api может оказаться не безупречным).
- Все еще нужно самостоятельно собирать новые модели из data objects.
- Все еще нужно принимать решения об ошибках обращений к серверу (пользователь прав не имеет, нет сущности с таким id, и т.д.).
- Все еще нужно обновлять репозитории.