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 делает за вас.