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

© 2026 · All rights reserved.

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

Источник: Flutter

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

Источник: Flutter
27 сентября 2026 г.•Обновлено: 27 сентября 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 на сервере может обеспечить сквозную типизацию (end-to-end type safety) совместно с Dart на клиенте, и было бы настоящим позором оставаться на нетипизированных Map.

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

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

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

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

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

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

Общие модели

Сквозная типизация fullstack означает общий код между клиентом и сервером. Как упоминалось ранее, это означает выделение любого кода, который должен выполняться на сервере, в отдельный пакет, который, что критически важно, не должен зависеть от Flutter SDK. В целом это просто, но потребовало доработок в моем собственном пакете управления данными, pkg:data_layer. Один из его сопутствующих пакетов, pkg:data_layer_firestore, зависит от клиентского SDK Firebase (который зависит от 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 сервисов, и хотя каждый из них отключался в режиме простоя, это все равно оказывало предсказуемое влияние на наши счета за серверы. Но, что еще хуже, пока GenLatte использовалась, мы установили минимальное количество узлов для каждого сервиса равным 1, чтобы избежать холодных стартов, что, конечно же, отключило эту функцию масштабирования до нуля. В конечном итоге запуск GenLatte обходился на удивление дорого.

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

Чтобы сэкономить деньги, я решил вдохновиться Serverpod и внедрить BackendMessage DTO, чтобы сообщить моей единственной функции 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, чтобы написать как тесты на стороне сервера, так и, что очень увлекательно, замокировать поведение бэкенда в клиентских тестах, которые внезапно стали сквозными (end-to-end). Учитывая мои безопасные относительно типов возвращаемые значения, это привело к стабильности и надежности приложения, которыми я остался доволен!

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

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

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

← Все статьи

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

Все →
Обзор Sennheiser Momentum 5: великолепный звук, невероятное время автономной работы и минимум компромиссовПресса
Momentum

Обзор Sennheiser Momentum 5: великолепный звук, невероятное время автономной работы и минимум компромиссов

Boeing сообщает о программном сбое в 737 Max, затрагивающем некоторые функции автоматического захода на посадкуПресса
Boeing

Boeing сообщает о программном сбое в 737 Max, затрагивающем некоторые функции автоматического захода на посадку

Apple обязали выплатить 5,7 млрд долларов по иску о нарушении патентных прав на тактильные технологии в iPhone и Apple Watch
Пресса
Apple

Apple обязали выплатить 5,7 млрд долларов по иску о нарушении патентных прав на тактильные технологии в iPhone и Apple Watch

Компании выбирают САПР от PTC для разработки и проектирования продуктов
PTC

Компании выбирают САПР от PTC для разработки и проектирования продуктов

Clas Ohlson выбирает PTC FlexPLM для поддержки своей стратегии роста
PTC

Clas Ohlson выбирает PTC FlexPLM для поддержки своей стратегии роста

Canon ITS и PTC Japan запускают «Smart PLM Support Service» для поддержки трансформации рабочих процессов в сфере производства
PTC

Canon ITS и PTC Japan запускают «Smart PLM Support Service» для поддержки трансформации рабочих процессов в сфере производства

Ещё от Flutter

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

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

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

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

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

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

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

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