scrpt .ru
· 6 min
Иллюстрация к материалу о spec-driven генерации email-шаблонов

Команда ЮMoney обслуживает около 410 вариантов писем: их собирает серверное приложение на NestJS, к которому обращается оркестратор, когда одному из примерно сотни backend-микросервисов нужно отправить пользователю email. Каждое письмо делалось руками, и добавление одной нотификации требовало правок примерно в 15 файлах — создать React-компонент, подключить его в модуле, описать параметры, добавить регистрацию и вспомнить ещё несколько мест. Фронтенд-разработчик Денис (на Хабре — Denisfg) описал, как команда заменила эту процедуру одной OpenAPI-схемой на письмо и генератором, который разворачивает из неё весь шаблонный код.

Результат измеряется во времени: сборка нового письма заняла минуты вместо часов. Сам выигрыш в скорости — следствие двух решений, которые полезно рассмотреть по отдельности: ограничение вариативности интерфейса четырьмя шаблонами и перенос описания параметров в формальный контракт.

Где терялось время

Пятнадцать файлов на письмо — это медленно и вдобавок хрупко. Однажды команда добавила несколько писем в одном pull request и забыла подключить их в модулях; дальше был долгий поиск причины, почему ничего не работает. Ручная регистрация в нескольких местах не проверяется компилятором, поэтому пропущенный шаг обнаруживался уже во время выполнения.

Вторая проблема — дрейф одинаковых по задумке блоков. Письма собираются из повторяющихся элементов: заголовок, описание, список, кнопки, ссылки, изображения. Дизайнер мог взять готовый компонент, а мог собрать похожий блок заново. У списка в одном письме появлялось нижнее подчёркивание, в другом его не было; изображение то лежало внутри контейнера, то выходило за его границы. По отдельности мелочи незаметны, вместе они увеличивали time-to-market и число согласований между разработчиками, дизайнерами и тестировщиками.

Четыре шаблона на 90% писем

Команда собрала существующие React-письма и вместе с дизайнерами выяснила: около 90% случаев закрываются четырьмя шаблонами. Каждый состоит из изображения, заголовка, описания, кнопок и ссылок, где необязательные элементы включаются и выключаются.

Шаблон здесь работает как ограничитель: внутри него зафиксированы размеры, отступы, типографика и список допустимых компонентов. Дизайнеру не приходится заново рисовать знакомые паттерны — он выбирает шаблон. Для оставшихся 10% писем возможность написать уникальный код сохранилась, и именно на этот порог команда советует ориентироваться: гнаться за стопроцентным покрытием смысла нет.

Один контракт вместо похода к соседней команде

Раньше, чтобы узнать, какие данные приходят в конкретное письмо, разработчик шёл к backend-разработчику, а если письмо принадлежало другой команде, путь удлинялся. Добавляла путаницы историческая деталь: на входе NestJS все параметры приводились к нижнему регистру. Приложению 11 лет, когда-то это было оправдано, а теперь удалить логику без последствий нельзя — разработчик ожидал walletNumber, а получал walletnumber.

Единой точкой правды сделали OpenAPI-спецификацию. Для каждого письма есть схема с бизнес-сценарием, letterId, параметрами, их типами и признаком обязательности, примерами и отдельными внутренними параметрами для обратной совместимости. Тестировщики и аналитики теперь смотрят параметры сами через Swagger. Для новых писем историческое приведение к нижнему регистру отключили: контракт уже задаёт имена.

Генерация из спецификации и проверка на сборке

CLI читает спецификации и разворачивает цепочку: OpenAPI → типизированная конфигурация → NestJS-сервис → React-компонент. Инструмент находит схемы, парсит YAML, приводит данные к типизированным объектам, создаёт на их основе сервисы и генерирует типы для React-пропсов.

Здесь появляется страховка, которой не было при ручной сборке. Если параметр изменился в OpenAPI, а компонент забыли обновить, типы перегенерируются во время билда и приложение падает на этапе сборки. Проверка согласованности контракта и потребителя переехала с ручного тестирования на компиляцию — ошибка ловится раньше и без участия человека.

Роль AI в этой схеме

Денис отдельно разбирает вопрос, почему письмо не пишет модель. Сгенерированный ею код может содержать не ту логику или пропущенную деталь, а ревью такого кода обходится дорого. AI применили на другом участке — он помог написать сам генератор. Дальше CLI работает одинаково для всех и выдаёт детерминированный результат: инструмент создавали с помощью модели один раз, повторяемость обеспечивает уже он.

Когда такой подход оправдан

Spec-driven генерация подходит не любой системе. Признаки, при которых её стоит рассматривать: добавление элемента сводится к копированию десятка похожих файлов; повторяющуюся часть можно отделить от уникальной бизнес-логики; параметры разбросаны по коду или держатся в памяти коллег и их реально вынести в формальную спецификацию; большинство экранов или сообщений собирается из общего набора блоков.

Ограничения команда называет прямо: нужно читать документацию, полностью описывать спецификацию и время от времени согласовывать с дизайнерами расширение шаблонов. Обмен состоит в том, что инженерное время тратится один раз на постройку системы, а экономится на каждой следующей нотификации. Запись доклада о кодогенерации на основе OpenAPI Денис выложил с митапа Frontend Mix.

Обсудить проект

Остались вопросы? Напишите нам

Оставьте имя и телефон — перезвоним в течение часа, разберём задачу и предложим решение.

Также читайте нас в Telegram.