{ "index": 130, "slug": "editorial-2024-05-field-platform-templates", "title": "Платформенный шаблон без ловушки fork: как провести границу решения", "excerpt": "Шаблон ускоряет повторяемый путь, но не заменяет архитектурное решение. Разбираем симптомы слишком широкой формы, проверяем golden path, узкое расширение и корректный отказ.", "contentHtml": "
Новая команда просит создать сервис, а платформа предлагает одну форму: имя, владелец, runtime, репозиторий и несколько флагов. Сначала это выглядит удобно. Через месяц появляются локальные правки, особые healthcheck, другой retention и ручные исключения в CI. Два сервиса уже не похожи на исходный шаблон, но команда всё ещё считает их его вариантами. Цена ошибки — не только лишняя работа. Теряется владелец контракта, обновления перестают доходить до копий, а рискованный выбор прячется за кнопкой Create.
\nТезис простой: шаблон должен принимать только повторяемый класс задач. Совпадение с базовым контрактом ведёт в golden path. Одно заранее описанное отличие ведёт на review расширения. Несовместимый или одноразовый запрос нужно остановить. Отказ дешевле fork-а, который создаёт ложное ощущение поддержки.
\nПервый симптом — просьба добавить универсальное поле: «если понадобится, разрешим внешний data class», «пусть runtime выбирается позже», «добавим произвольный adapter». Поле кажется небольшим, но меняет инвариант. После него шаблон уже не описывает один тип сервиса. Он принимает несколько архитектурных решений без владельца.
\nВторой симптом — локальный patch сразу после создания репозитория. Команда удаляет обязательный шаг, переписывает pipeline или меняет доступы, а потом обещает вернуть полезное изменение в общий шаблон. Если различие не имеет имени, владельца, границы и условия удаления, это не extension. Это отдельный проект, который маскируется под стандартный путь.
\nТретий симптом — платформа выдаёт skeleton для задачи, у которой ещё нет data policy, access model или ответственного. Файлы создаются быстро, но структура начинает диктовать решение. Команда подгоняет требования под уже созданный репозиторий. Технический артефакт появляется раньше архитектурного договора.
\nРазделите решение на три слоя. Первый — входные факты: тип компонента, владелец, runtime, класс данных, требования к доставке и срок жизни. Второй — контракт: допустимые значения и обязательные шаги. Третий — результат: применить базовый путь, отправить ограниченное расширение на review или отказать до уточнения требований.
\nBackstage описывает шаблон как набор параметров и последовательных шагов. Это полезный механизм, но он не делает любой параметр безопасным. Параметр собирает значение. Решение о том, разрешено ли значение, должно жить в контракте и проверке. GitHub template repository копирует структуру и файлы в новый репозиторий, но создаёт несвязанную историю. Автоматического канала изменений исходного шаблона это не даёт.
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Просят «универсальный» флаг | Поле меняет runtime, ownership, data class, access или retention | Сравнить поле с базовым инвариантом и назвать владельца | Убрать поле из golden path; оформить отдельный путь |
| После создания нужен patch | Различие не описано как versioned extension | Проверить identifier, owner, boundary и rollback | Остановить копирование; вынести различие на review |
| Шаблон приняли для миграции | Нет повторяемого типа и жизненного цикла | Проверить повторяемость того же контракта | Отказать шаблону и провести отдельное решение |
| Repository считают fork-ом | Смешаны копирование и наследование изменений | Проверить историю и канал обновлений | Зафиксировать самостоятельное владение или другой механизм |
Ниже — учебный фрагмент. Он не запускает реальную задачу и не доказывает пригодность набора полей для вашей организации. Базовый путь принимает внутренний HTTP-сервис с известным владельцем и утверждённым runtime. Observability adapter разрешён как именованное расширение. Внешние регулируемые данные форму не проходят.
\napiVersion: scaffolder.backstage.io/v1beta3\\nkind: Template\\nmetadata:\\n name: internal-http-service\\nspec:\\n owner: group:platform\\n type: service\\n parameters:\\n - title: Service contract\\n required: [name, owner, runtime, dataClass]\\n properties:\\n runtime:\\n type: string\\n enum: [node20, go122]\\n dataClass:\\n type: string\\n enum: [internal]\\n extension:\\n type: string\\n enum: [none, observability-adapter]\\n steps:\\n - id: write-skeleton\\n action: fetch:template\\n - id: publish\\n action: publish:github\nВ примере enum ограничивает форму, но не заменяет проверку прав и политики. Owner должен ссылаться на существующую группу, а публикация требует разрешений и проверки credentials. В рабочем шаблоне эти условия подтверждаются средствами вашей платформы. YAML не доказывает успешный запуск, безопасность или пригодность runtime.
\nПервая заявка содержит известный service type, владельца, approved runtime и internal data. Она совпадает с контрактом: golden path. Вторая содержит те же факты и один adapter из закрытого списка. Если у расширения есть owner, граница и способ удаления, это review extension. Третья описывает одноразовую регулируемую миграцию, но не содержит владельца, retention и access model. Ей нужен отказ от шаблона и отдельное решение.
\nFork отвечает на вопрос «как начать самостоятельный проект на основе текущих файлов». Он не отвечает на вопрос «как поддерживать общий контракт между проектами». Repository, созданный из template, получает несвязанную историю. Pull request между копией и шаблоном не становится штатным каналом синхронизации.
\nFork допустим, когда команда принимает независимый жизненный цикл. Тогда нужно записать владельца, область ответственности и способ получать будущие изменения. Если ожидается обновление всех созданных проектов из базового шаблона, нужен другой механизм доставки или честная граница поддержки.
\nНе расширяйте базовый шаблон ради редкого запроса. Новое поле увеличивает число состояний для всех пользователей. Особенно опасны поля, которые откладывают решение: «позже выберем runtime», «потом определим доступ», «retention настроит команда». Отсутствующее решение нельзя превратить в безопасный default названием параметра.
\nШаблон не выбирает владельца, не определяет классификацию данных и не делает action безопасным. Список runtime устаревает. Документация объясняет форму и порядок шагов, но не знает ваших сетевых прав, требований регулятора и правил отката. Эти условия проходят отдельную проверку.
\nУчебный YAML не является production-конфигурацией. В нём нет конкретной схемы прав, политики секретов, branch protection, SLO и обязательных проверок поставки. Не переносите его в рабочую систему без адаптации и review. Цифры adoption, скорости и снижения дефектов здесь не заявлены.
\nРешение готово, когда другая команда берёт ту же заявку и получает тот же вердикт по тем же фактам. Для golden path форма принимает только значения контракта. Для extension документ содержит owner, boundary, version и rollback. Для отказа есть причина и следующий вопрос вне шаблона. Неизвестное или рискованное значение останавливается до создания репозитория и не превращается в локальный patch.
\nПроверка состоит из четырёх записей: базовая заявка, узкое расширение, несовместимая заявка и повторный запуск первой. Готовность есть, если базовые записи дают одинаковый результат, расширение не меняет инварианты, отказ не создаёт артефакт, а повторный запуск не дублирует обязательные действия.
\n