Dev48
ЯЗЫК
  • О нас
  • Услуги
  • Индустрии
  • Технологии
  • Статьи
  • Контакты
Забронировать звонок
    Главная/Статьи/Kak ya perevel genlatte na fullstack dart
Dev48

© 2026 · All rights reserved.

Как я перевел GenLatte на fullstack Dart

Источник: Flutter

Как я перевел GenLatte на fullstack Dart

Источник: Flutter
25 сентября 2026 г.

И сократил счет за серверную часть приложения

22 сентября 2026 г. · 7 мин чтения

Если вы следили за запутанной сагой о том, как команда Flutter решила заняться кофейнями, то знаете, что мы объединили Flutter, Firebase и Gemini для приготовления необычного кофе. Вы также знаете, что, поскольку мы ничего не брали за это, нам не удалось получить прибыль.

Более того, если вы заглянули в код, то могли заметить, что наш фронтенд на Flutter дополнялся функциями Firebase, написанными на Node.js. Это довольно удивительный исторический артефакт, учитывая, что поддержка функций Dart в Firebase появилась в публичном доступе почти одновременно с первым появлением GenLatte на Google Cloud Next.

Но в том-то и дело — поскольку поддержка Dart в Firebase появилась в самый последний момент, мы просто не могли позволить себе использовать ее для производственного приложения в условиях жесткого дедлайна. Если бы поддержка Dart в Firebase задержалась по какой-либо причине, мы могли бы не успеть развернуться к Cloud Next. И поэтому, когда GenLatte появился в апреле, мае или июне, он работал на бэкенде Node.js.

И я принял это на свой счет.

Полное погружение в fullstack Dart

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

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

  • Использовать общие модели во всем стеке (что было несложно, учитывая мое первоначальное решение поместить все классы данных в отдельный пакет genlatte_data)
  • Сократить количество функций Firebase до одной развертываемой функции
  • Удалить все операции записи на стороне клиента; вместо этого сосредоточиться на вызове серверных функций
  • Удалить все серверные триггеры и вместо этого явно выполнять любые изменения данных в серверных функциях, вызываемых из клиента
  • Сохранить все проверки ACL на основе ролей
  • Наконец-то внедрить сквозное тестирование!

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

Выполнение миграции

В отличие от работы над оригинальным GenLatte, которая в значительной степени велась без использования ИИ, я знал, что сжатые сроки требуют привлечения помощника по написанию кода. Моим выбором стала модель Gemini 3.6 Flash, и ее сочетание низкой задержки и общих знаний очень помогло мне.

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

Общие модели

Безопасность типов во всем стеке означает общий код для клиента и сервера. Как упоминалось ранее, это означает выделение любого кода, который должен выполняться на сервере, в отдельный пакет, который, что критически важно, не должен зависеть от Flutter SDK. Это в значительной степени прямолинейно, но потребовало дополнений к моему собственному пакету управления данными, pkg:data_layer. Один из его вспомогательных пакетов, pkg:data_layer_firestore, зависит от клиентского Firebase SDK (который зависит от Flutter), поэтому я был вынужден добавить pkg:data_layer_firestore_admin, который является чистым Dart и, следовательно, удобен для сервера.

Использование одной функции Firebase

По умолчанию каждая развертываемая функция Firebase превращается в собственный сервис Cloud Run. (Если вы пользователь Firebase Functions и слышите об этом впервые, перейдите в Cloud Run в консоли Google Cloud и посмотрите, как делается «колбаса» Firebase!)

Однако, после использования более 15 отдельных сервисов Cloud Run в эпоху Node.js для GenLatte, я знал, что хочу более компактную настройку по нескольким причинам. Во-первых, развертывание было бы значительно быстрее, но, что еще важнее, это значительно сократило бы счет за сервер GenLatte.

«Но Крейг!» — скажете вы, — «Cloud Run сворачивается до нуля, когда не используется, так важно ли это?»

Отличный вопрос. Я рад, что вы внимательны.

И да, это очень важно! Пока GenLatte использовался, различные операции записи данных и асинхронные задачи запускали все 15 сервисов, и хотя каждый из них отключался в режиме ожидания, это все равно имело предсказуемые последствия для нашего счета за сервер. Но, что еще хуже, мы установили минимальное количество узлов для каждого сервиса равным 1, пока GenLatte был в использовании, чтобы избежать «холодных стартов», что, конечно, отключало функциональность сворачивания до нуля. Конечным результатом было то, что GenLatte был удивительно дорогим в запуске.

Как запихнуть все в один сервис Firebase

Чтобы сэкономить деньги, я решил вдохновиться Serverpod и внедрить DTO BackendMessage, чтобы сообщать моей единственной функции Firebase, какую внутреннюю функцию я хочу вызвать. С помощью некоторых хитрых приемов pkg:freezed, которые помогли мне написать Gemini, я даже смог получить типизированные ответы.

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

BackendMessage

Это родительский класс DTO, который я использую в сигнатурах своих функций.

dart

MessageParameters

Он реализует BackendMessage и использует pkg:freezed для привязки отдельных типов сообщений к ожидаемым классам ответов.

Трюк @Implements.fromString создает классы, которые удовлетворяют требованию родителя привязывать тип MessageResponse. И хотя использование исходным классом необработанных строк кажется опасным с точки зрения типов, любые опечатки приводят к ошибкам в сгенерированных классах, что означает, что функционально это безопасно с точки зрения типов.

dart

MessageResponse

Это замыкает цикл, объявляя классы ответов, которые ожидает каждый вызов метода. Он предлагает сочетание отдельных типов ответов для методов, которым нужна немедленная обратная связь, и пустых ответов, представляющих своего рода HTTP-ответ 202 Accepted или 204 No Content.

dart

С моими DTO в порядке, мне нужна была единственная серверная функция для получения и маршрутизации каждого входящего BackendMessage соответствующим образом.

dart

Наконец, мне нужны были методы saveOrder и completeOrder, которые соответствовали бы их контракту.

dart

Благодаря этим системам я смог запихнуть произвольное количество серверных операций в одну функцию Dart. Привеееет, экономия!

Рефакторинг всех операций записи

Вышеупомянутая система BackendMessage создала основу для немедленного достижения еще трех моих подцелей:

  • Я удалил все операции записи на стороне клиента, заблокировав их в файле firestore.rules. Я также переработал свои уровни управления данными, чтобы вызывать единственную бэкенд-функцию вместо прямого вызова функций Firestore, таких как docRef.set().
  • Я аналогичным образом удалил все триггеры Firestore, но восстановил недостающую функциональность в функциях, которые я мог явно вызывать из клиента.
  • Я сохранил строгую модель разрешений GenLatte, внедрив проверки ACL в коде Dart, что, как тестируемая система, помогло мне спать спокойно.

Добавление сквозных тестов

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

Радость от использования полностекового языка

Независимо от того, любите ли вы реляционные данные и поэтому используете что-то вроде Serverpod, или нереляционные решения вроде Cloud Firestore, писать на одном и том же языке для всего стека — это просто мечта. И преимуществ становится только больше, если этот язык — Dart, а вы наслаждаетесь полностью надежной и безопасной по отношению к null типизацией. Это действительно создает иллюзию, что компьютеры созданы для того, чтобы быть вашими друзьями!

Мне понравилось переводить GenLatte на повсеместное использование Dart, и благодаря этому счет за сервер сократился до крошечной доли от того, что было с Node, а надежность и производительность выросли. Деньги, потраченные на токены Gemini при выполнении этих изменений, с лихвой окупились уже после нескольких минут работы. Теперь GenLatte сможет еще долго радовать участников мероприятий персонализированным кофе или до тех пор, пока нам не надоест его поддерживать.

Больше материалов о Flutter

← Все статьи

Ещё в разделе «Разработка ПО»

Все →
Новый навык обнаруживает риски ИИ-агентов, устраняет их и доказывает эффективность исправлений
Microsoft

Новый навык обнаруживает риски ИИ-агентов, устраняет их и доказывает эффективность исправлений

Некоторые клиенты Supabase открывают в публичном доступе огромные массивы личных данных пользователейПресса
Supabase

Некоторые клиенты Supabase открывают в публичном доступе огромные массивы личных данных пользователей

Основы Blazor: SEO для веб-приложений на Blazor
Telerik

Основы Blazor: SEO для веб-приложений на Blazor

Вас затронули сокращения? Не упустите возможность приобрести пропуск Expo+ на TechCrunch Disrupt 2026 всего за 75 долларовПресса
Expo

Вас затронули сокращения? Не упустите возможность приобрести пропуск Expo+ на TechCrunch Disrupt 2026 всего за 75 долларов

Последние 24 часа, чтобы сэкономить до 200 долларов на TechCrunch Disrupt 2026. Причина 5 из 5 для участия: ИмпульсПресса
Momentum

Последние 24 часа, чтобы сэкономить до 200 долларов на TechCrunch Disrupt 2026. Причина 5 из 5 для участия: Импульс

Мы создаем Copilot как новую операционную систему для работы, охватывающую любую модель, любой форм-фактор и любую задачу. Сегодня мы объявляем о самом масштабном обновлении Copilot на сегодняшний день, объединяющем четыре компонента [Читать далее]
Microsoft

Мы создаем Copilot как новую операционную систему для работы, охватывающую любую модель, любой форм-фактор и любую задачу. Сегодня мы объявляем о самом масштабном обновлении Copilot на сегодняшний день, объединяющем четыре компонента [Читать далее]

Ещё от Flutter

Как Flutter опережает релизы iOS
Flutter

Как Flutter опережает релизы iOS

Быстрые и надежные вычисления с помощью клиентских функций A2UI
Flutter

Быстрые и надежные вычисления с помощью клиентских функций A2UI

Разделение Material и Cupertino уже здесь
Flutter

Разделение Material и Cupertino уже здесь

Что нового во Flutter 3.47
Flutter

Что нового во Flutter 3.47