Пролог Кто-нибудь пробовал использовать Dart/Flutter как на клиенте на нескольких платформах, так и в качестве сервера? Кто-то, конечно.
Я попробовал, хотя не все ради этого зашли во Flutter. Такой эксперимент я провел на своем любимом проекте, и хотел бы поделиться результатами и выводами.
Так уж получилось, что тема кроссплатформенности не дает мне покоя уже довольно давно:
- Еще учась в институте, я занимался разработкой десктопных приложений с использованием Qt4. Позже я опробовал возможности Qt на мобильных устройствах на примере фреймворка Felgo.
- Сам разработал и запустил несколько информационных мобильных приложений на DrupalGap и Ionic.
- В рамках одного из стартапов моей компании было реализовано простое мобильное приложение с использованием фреймворка Quazar Vue.
- Для одного из клиентских проектов мы сделали десктопную версию React-приложения, обернув ее в Electron. А позже адаптировали под мобильные телефоны (точнее, электронные доски в школах, но они Android).
Однако они демонстрируют способность Dart и Flutter работать в разных средах, и именно в этом я вижу их ценность.
Наш испытуемый
Классический учебник ToDo написан много раз разными способами для огромного количества интерфейсных фреймворков.Это дело настолько избитое, что экспериментировать с ним нет ни малейшего желания.
К счастью, у меня под рукой оказалась «настоящая» реальная задача: «оцифровать» настольную игру, суть которой — совместное составление единого рассказа.
Чем-то напоминает Dungeons & Dragons, но все же существенно от нее отличается: здесь три колоды карт (место, персонаж и событие).
Перед началом игры выбирается тема рассказа, а затем игроки по очереди вытягивают карту из любой колоды и продолжают рассказ, начатый ведущим игры, подстраиваясь под ранд, который у них выпал на карте.
Ни у кого нет своего характера; в принципе можно делать что угодно с любым аспектом вселенной — лишь бы он соответствовал нарисованной карте.
Степень серьезности повествования определяют игроки «на берегу»; тут можно либо упорствовать, либо попробовать что-то серьёзно смоделировать.
Как собраться в оффлайне и вытянуть карты из колоды, когда все на карантине во время пандемии? Мы попробовали показывать карты в камеру Zoom, но из-за разного качества связи между игроками это была плохая идея.
Кроме того, даже в оффлайне были случаи, когда собирались люди и мы бы даже поиграли, но хозяин колоды карт не приходил, или не брал карты с собой.
Хотя обидно!
Часть 1. Бэкенд. Телеграм-бот
Написать чат-бота — наверное, самое простое решение на сегодняшний день, если вам нужно сетевое взаимодействие между игроками, а игра представляет собой что-то вроде текстового квеста.Бэкенд и база данных для такой задачи практически не нужны.
Однако я попробовал сделать по-другому.
не вдаваясь в подробности своих поисков в процессе разработки, дам отзывы о технологиях и библиотеках.
С кем мне довелось поработать:
- ТелеДарт — библиотека для создания бота для Telegram. Впечатления положительные, работает как в режиме длительного опроса, так и с вебхуком.
Он дорабатывается и обновляется автором, но сказать, что он прямо «динамично развивается», я не могу.
Однако это и к лучшему, код будет стабильнее, пока автор от него не забросит.
- Дартис — Клиент Redis для Dart. Прекрасно работает, но это как раз тот случай, когда либа была написана один раз, а потом заброшена автором.
Уже сейчас он не поддерживает null-safety, что исключает беспроблемное использование в ваших проектах, и в будущем он будет всё менее совместим с текущей версией Dart. И не все функции Redis поддерживаются.
Альтернативные библиотеки есть, но они находятся в еще более плачевном и заброшенном состоянии, чем эта.
Хотя общее впечатление от использования было приятным, позже я решил его вырезать и хранить все необходимое в переменных внутри приложения, хотя это не очень круто, особенно на случай краша.
- Mysql1 — очередная моя попытка найти более-менее традиционное для серверных приложений постоянное хранилище данных.
Увы, вот в чём проблема: либа работает, но функционал тоже в недоделанном состоянии.
Тем, кто привык к мощным ORM, это покажется болезненным и печальным.
Однако у меня были проблемы даже с обработкой написанных мной SQL-запросов: библиотека не реализует обработку некоторых типов столбцов (в моем случае это были blob), и такие столбцы просто игнорируются при выборке.
Печально, но боюсь, что это направление тоже еще очень долго не будет востребовано, а то и вовсе вымрет.
- Огневая база .
Я этим решением не пользовался, хотя тут вроде все нормально.
Но меня не устроил вариант закрепления за хранилищем данных, которые я не мог при необходимости перенести на свой хостинг.
Кроме того, смущала очень длинная схема авторизации, а у меня простое серверное приложение и авторизации по секретному токену было бы вполне достаточно.
Простите меня за мою лень.
- SDK сервера синтаксического анализа .
Этот вариант мне показался гораздо более дружелюбным, и в конце концов я остановился на нем.
Есть куча облачных продуктов, к которым можно подключиться на бесплатном тарифном плане, если не хотите обновляться самостоятельно.
Но вы также можете настроить у себя opensource-сервер, который легко будет работать по единому стандарту API. Библиотека поддерживает (де)сериализацию объектов, есть гибкий API для поисковых запросов и в конечном итоге все это можно сохранить в базе данных SQL. В общем, похоже, я нашел то, что искал, пусть и через «сервис-посредник», но хотя бы open source.
- Пакеты Файл И Архив — он прост и удобен в использовании, мне просто нужно было распаковать набор карточек из zip-архива, прочитать JSON и отправить его на сервер, превратив в сущности Parse.
SDK сервера парсинга для связи с бэкендом, API для работы с файлами и архивами.
Впечатления от бэкенд-разработки бота:
- Благодаря системе типов крайне приятно, что код сразу работает именно так, как задумано.
если, конечно, не сделать все переменные динамическими — тогда Dart превратится в PHP в худшем виде.
- Ловить исключения необходимо как отдельно в синхронном, так и отдельно в асинхронном коде.
А с асинхронным кодом сложнее, потому что.
там нельзя просто обернуть все содержимое main в try-catch и тем самым закрыть все проблемы.
Вам придется «лично» оборачивать каждый асинхронный вызов, что утомительно и загромождает код.
- На бэкенде нет привычных средств хранения данных; для производственного проекта это может быть решающим аргументом против, поскольку невозможно просто и быстро подключиться к существующей инфраструктуре.
Часть 2. Бэкенд. ОТДЫХ API
С точки зрения бэкендера могу сказать, что умение создавать API, хотя бы простейший REST, гораздо важнее всяких ботов.Поэтому следующим шагом было перенести основную логику игры на REST-сервер от телеграм-бота.
Кроме того, мне хотелось иметь «слой бизнес-логики», который бы ничего не знал о конкретном приложении, в котором он используется.
В поисках библиотеки для API-сервера Google, видимо по старой памяти, указал мне на Акведук.
Впоследствии выяснилось, что его поддержка и развитие давно прекратились; библиотека, можно сказать, умерла.
Еще один неприятный пример того, как в «всеплатформенном» языке вымирают «непрофильные» библиотеки.
В конце концов я решил остановиться на Полка – по крайней мере, создается впечатление, что это решение будет жить долго и счастливо и не закроется завтра.
Функциональность остального сервера минимальна.
Если сравнивать с возможностями даже самого простого PHP-фреймворка, то в Dart все выглядит бледно и печально.
Тем не менее, мне показалось, что он максимально подходит для создания крохотных сервисов, решающих одну или несколько небольших задач, без обширного API и «сложных» запросов.
В частности, мне не понравилась негибкость в настройке маршрутов; без дополнительных пакетов разобраться невозможно, и кто знает, может ли завтра эти пакеты постичь та же участь, что и Акведук? Подход к унифицированной валидации данных запроса тоже пришлось придумать и реализовать самому.
Но Шельф с удовольствием писал тесты.
Прежде всего потому, что не было необходимости отдельно настраивать сервер и отправлять на него реальные запросы.
Shelf можно запустить как просто класс, который не слушает какой-либо адрес или порт, а получает данные непосредственно из кода.
Это то, что я использовал Здесь .
Хотя, если очень хочется, то перед запуском тестов можно поднять сервер изолированно.
Часть 3. Бэкенд. Стабильность и потребление памяти
Тот факт, что мой телеграм-бот работает по схеме длинного опроса, сам по себе исключает тестирование производительности, т.к.сколько бы запросов не было в очереди, бот все равно будет их обрабатывать "в своем темпе", соответственно, не нагружая и остальной сервер тоже много.
К тому же для полноценного теста производительности хорошо бы с чем-то сравнить, а переписывать проект заново на том же PHP или node.js, чтобы с ними замерять скорость, я не готов.
Итак, давайте просто посмотрим на память.
Прежде всего, давайте посмотрим на данные мониторинга на производственной площадке:
Данные за два месяца Конечно, у меня там нет сотен пользователей, но видно, что за два месяца работы оба бэкенд-сервиса не сильно вышли за пределы своей памяти.
Просадка в верхнем графике скорее всего из-за пересборки и перезапуска докер-контейнера, иначе телеграм-бот занимает 60-70 мегабайт и больше не просит. «Лестница» на нижнем графике — это остальной сервер.
Он существенно легче и занимает гораздо меньше памяти, и здесь хорошо видна особенность GC в Dart: он не будет лишний раз тратить ресурсы на освобождение памяти, если потребление памяти не увеличилось существенно.
Чтобы посмотреть, как работает GC «в лабораторных условиях», я написал фейковый телеграм-сервер, который запускал одновременно 100 игр по 5 игроков, и они играли там какое-то время, после чего все игры заканчивались — ресурсы должны были очищаться .
Не такая уж большая нагрузка, но при более высоких значениях приходилось слишком долго ждать (долгий опрос, как я писал выше), и «страница памяти DevTools» полностью зависала задолго до окончания процесса.
Вот что я увидел на графике бота Telegram:
График потребления памяти для двух циклов запуска фейковых игр.
Обратите внимание на выделенное положение на временной шкале.
В первом варианте теста именно на этом заканчивались все запущенные мной игры, но память не очищалась.
Дарт продолжал занимать ненужные ресурсы, не вызывая GC, поскольку существенного увеличения потребления этих самых ресурсов не произошло.
Чтобы заставить Dart очистить занятую память, мне пришлось запустить второй проход «тестовых игр».
И только когда поступали новые запросы и требовалось еще больше памяти, приходил GC и очищал место, занятое предыдущими 100 играми.
Хорошо это или плохо — ну, для серверного приложения, особенно если там работает ряд процессов, конкурирующих за ресурсы, это явно не очень хорошо.
В целом можно сделать вывод, что хоть и нет утечек памяти, но и реально контролировать ее потребление не получится.
Также нет возможности вручную запустить GC в производственной сборке, поэтому вы можете рассчитывать только на то, что на вашем оборудовании достаточно памяти и других кандидатов на нее нет. Поведение GC может меняться от запуска к запуску — видимо, это зависит от других процессов, запущенных на машине.
Например, сразу проведя тест еще раз, для телеграмм-бота я получил немного другой график с «артефактом»:
Разная динамика потребления памяти в одних и тех же условиях тестирования С REST-сервером картина аналогичная, но из-за относительной «разгрузки» GC-сервера GC туда приезжает крайне редко.
Итак, после того, как приложение «потолстело» за счет активного выполнения запросов, оно остается висеть со всем лишним барахлом в памяти.
Я до сих пор не дошел до автоматической очистки.
Это все будет болтаться как балласт в памяти до следующего увеличения нагрузки.
На скриншоте показано именно то место, где я не выдержал и вручную вызвал GC:
В конце графика видно, что GC мог бы освободить приличный кусок памяти, если бы «захотел» Вот снимок памяти перед вызовом GC вручную:
Полный список созданных сущностей даже не поместился на экране.
Выделены лишь некоторые из них, хотя еще много неиспользуемых.
Выбранные объекты уже должны быть удалены, но Dart все равно сохраняет это в памяти.
И только после ручного GC всё это исчезает, оставляя только два типа сущностей, которые специально кэшируются:
После очистки осталось всего 12 экземпляров классов.
Часть 4. Фронтенд. Мобильные и другие приложения
Фронтенду уже уделено достаточно внимания, поэтому обозначим самое важное:- В разработке я решил не использовать ничего, кроме setState. Там такое бешеное обилие стейт-менеджеров, каждый из которых ориентирован на какие-то повадки фронтендеров из неизвестных мне фреймворков.
Меня как бывшего бэкендера вполне устраивает setState, все прозрачно и понятно.
- Я избегал использования платформо-специфичных библиотек, особенно ориентированных исключительно на мобильную разработку: какой же это кроссплатформенный, если он не работает на десктопе и в браузере?
- Для тех, кто вникает в код - да, я знаю, что в итоге получились ужасные спагетти.
Но главное для меня сейчас то, что это работает. Увы, я слишком устал от проекта, чтобы быть щедрым на рефакторинг.
Выше я писал, что полка может не получать данные по сети, а получать JSON напрямую для обработки.
Это позволило просто взять готовый слой «бизнес-логики» и подключить его как зависимость без каких-либо доработок под платформу.
Это не лучшее решение для производства, но оно прекрасно подходит как иллюстрация универсальности кода Dart. Что меня удивило в процессе разработки, так это то, что такие естественные для мобильных устройств вещи, как вибрация и управление звуком, являются не частью Flutter, а библиотеками, написанными сообществом.
Но в целом все работает. Также в приложении есть блок загрузки карт для автономного использования.
здесь удалось поработать с изолятами (чтобы не использовать платформенно-зависимые плагины для мобильных телефонов).
Кроме вибрации, звука, фоновой загрузки в изоляте и встроенного рест-сервера, ничего технически примечательного в приложении нет — обычные экраны с виджетами материалов и анимациями.
Нам удалось собрать получившуюся программу:
- Для Android
- Под Windows .
Здесь нам пришлось сделать одну специфичную для платформы вставку, чтобы кнопка «Выход» вызывала «exit()», а не SystemNavigator.pop().
И добавьте перехват исключений при вызове функций модуля Вибрация.
- В сети .
Здесь нам пришлось по сути скрыть кнопку выхода, а также убрать из интерфейса кнопку загрузки карт офлайн, потому что изоляты в вебе не работают. И немного поиграйте с HTML, чтобы он выглядел как заставка.
Кроме того, относительно новый модуль вибрации пришлось заменить на относительно старый, но с веб-поддержкой.
Плюс были проблемы с файлом .
env — я долго не мог понять, что его нельзя открыть по сети именно из-за точки, и мне нужно было просто переименовать его.
Ну сборку для Mac/iOS я не делал и не могу сделать, так как у меня ее нет. В заключение добавлю, что для любой платформы собранное приложение имеет весьма скромный вес — от 10 до 34 мегабайт в зависимости от платформы.
Сравнивать с решениями на Кордове, особенно если скачать Crosswalk - это 10 и более мегабайт только на HelloWorld, сравнивать с решениями на Qt так страшно - перевалит за сотню.
Часть 5. Релизы
В целом ничего особенного.Для серверных приложений можно легко собрать всё в Docker, как это было сделано для REST-сервер И Телеграм-бот .
Для Android все необходимое описано в официальном руководстве.
Пришлось только с Windows повозиться.
В первую очередь встал вопрос об установщике, а потом выяснилось, что их целый зоопарк, и пробиться на маркетплейс разработчику не так просто, как в Google Play. Мое заявление еще «на рассмотрении», прошло уже три-четыре месяца.
Второй сюрприз был связан с внешними плагинами.
Для воспроизведения звуков я использую Кплеер , набор привязок для VLC. И с тех пор, как я добавил эту зависимость в сборку релиза, у меня теперь есть каталог размером 335 мегабайт со всеми возможными кодеками для VLC!! Дело не только в размере, но и в 5-секундном зависании перед первым воспроизведением файла, потому что все эти "300 спартанцев" разом начали врываться в Фермопильский проход и загружаться в память.
К сожалению, есть никакого более быстрого/адекватного метода, чем "удалить ненужные файлы, пока звук не перестанет работать" я не нашел.
в итоге осталось 4 дллки общим объемом 15 мегабайт. В принципе, я не умер, просто несколько демотивирует то, что мне пришлось сделать то же самое 15 лет назад, и с тех пор прогресс не шагнул вперед.
Часть 6. Выводы
Итак, если примерно набросать, то получится вот такой набор приложений, использующих один язык программирования.
Извините, я не художник Кроме того, для всех используется одна и та же бизнес-логика из единой базы кода.
Мое личное мнение таково, что какими бы проблемами ни страдали сейчас Dart и Flutter, на данный момент это самое удачное кроссплатформенное решение, которое я видел.
Портирование на другие платформы требует минимальных усилий; большой объем кода можно просто скопировать и вставить, и он везде будет работать одинаково хорошо.
Да, не хватает тонкого контроля; Родной разработчик, судя по отзывам о родном функционале, лично меня расстроил ситуацией с памятью.
Я рад, что у меня есть личный «швейцарский нож» для решения мелких бытовых задач различного характера.
Но у крупного бизнеса другие потребности, что наглядно иллюстрируют постоянные споры между нативными и кроссплатформенными разработчиками.
Тем не менее, я считаю, что такой простой и гибкий инструмент непременно должен найти и прочно занять свою нишу для создания быстрых кроссплатформенных решений.
лишь бы внешних плагинов было достаточно для всех основных нужд. Теги: #Разработка Windows #Android #Разработка мобильных приложений #Windows #интерфейсы #web #flutter #dart #server-side
-
Мой Любимый Хостинг — Dreamhost.com.
19 Oct, 24 -
Apple Ipod Против Ms Zune
19 Oct, 24