Перейти к основному содержимому

Server-Side Dart

Backend, написанный на Dart — обычно на Serverpod. Один язык на клиенте и на сервере, один набор моделей и сгенерированный клиентский код вместо написанного руками слоя работы с API.

Почему это важно. Большая часть ошибок на стыке клиента и сервера — это ошибки перевода: поле переименовали с одной стороны, nullable оказался не nullable, в enum пришло значение, о котором приложение никогда не слышало. Общий язык и генерация клиента убирают сам этап перевода, а не делают его безопаснее.

Что нужно понимать

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

Ключевые темы

Из чего состоит сервер

  • Точка входа, инициализация, порядок запуска
  • Endpoint-ы, сессии и жизненный цикл запроса
  • Конфигурация под каждое окружение и секреты, которых нет в репозитории
  • Структурированное логирование и что писать на каждом уровне

Собственно Serverpod

  • Модели описываются один раз и порождают сервер, клиент и протокол
  • Сгенерированные вызовы клиента вместо написанного руками HTTP
  • Сериализация через границу, включая enum-ы и даты
  • Миграции как часть рабочего процесса

Корректность на сервере

  • Транзакции и что происходит, когда два запроса гонятся за одним и тем же
  • Идемпотентность для всего, что клиент может повторить
  • Блокировки там, где блокировать нечего — строки ещё нет
  • Отсутствие состояния, чтобы второй экземпляр ничего не менял

Эксплуатация

  • Локальная разработка в Docker, достаточно близкая к production
  • Health check и readiness
  • Развёртывание и судьба запросов, которые в этот момент выполняются

Уровни

УровеньКак это выглядит
JuniorДобавляет endpoint и модель, запускает генератор, доводит данные до приложения.
MiddleАккуратно работает с транзакциями, валидацией и ошибками. Ведёт конфигурацию и миграции по всем окружениям.
SeniorПроектирует с расчётом на конкурентность и повторы, держит сервер без состояния и понимает его поведение под нагрузкой и во время развёртывания.

Практика

Для начала

  • От начала до конца Добавьте модель, запустите генерацию и прочитайте данные в приложении, не написав ни строчки HTTP руками.

  • Разделите окружения Вынесите захардкоженное значение в конфигурацию и задайте ему разные значения локально и в production.

  • Пишите полезные логи Возьмите падение, которое ничего не логирует, и добейтесь, чтобы причина была видна по одним только логам.

Дальше

  • Сделайте операцию идемпотентной Возьмите то, что не должно выполниться дважды, и сделайте повтор безопасным.

  • Разберите гонку Найдите два запроса, которые могут конфликтовать, и разрешите конфликт транзакцией.

  • Запустите два экземпляра Поднимите второй экземпляр и посмотрите, что сломается, — сломается всё, что жило в памяти.

Проверьте себя

  • Что в вашем сервере сломается, если прямо сейчас поднять второй экземпляр?
  • Какие операции небезопасны при повторе со стороны клиента?
  • Откуда берутся секреты в каждом окружении и кто может их прочитать?
  • Что упавший запрос оставляет после себя в логах?
  • Как миграция доезжает до production и что будет, если она упадёт на середине?
  • Какая валидация есть на сервере, а какая только в приложении?

Материалы

  • Serverpod documentation — framework, на котором работает большинство Dart-backend-ов. Раздел с концепциями стоит прочитать целиком, прежде чем что-то писать.
  • The Twelve-Factor App — текст старый, но по-прежнему верный, и это самый быстрый способ понять, почему конфигурация, отсутствие состояния и логирование — это архитектура, а не бытовые мелочи.
  • Idempotency keys — Brandur Leach, по опыту разработки API Stripe. Самое внятное объяснение того, как сделать повторы безопасными, — а именно на этом сервера чаще всего и ошибаются.
  • Effective Dart — рекомендации по стилю работают и на сервере, а серверный код живёт дольше, чем код интерфейса.
  • dart:io — что Dart умеет без всякого framework. Полезно знать, чтобы понимать, что именно framework делает за вас.