8 lines
16 KiB
JSON
8 lines
16 KiB
JSON
{
|
||
"index": 130,
|
||
"slug": "editorial-2024-05-field-platform-templates",
|
||
"title": "Платформенный шаблон без ловушки fork: как провести границу решения",
|
||
"excerpt": "Шаблон ускоряет повторяемый путь, но не заменяет архитектурное решение. Разбираем симптомы слишком широкой формы, проверяем golden path, узкое расширение и корректный отказ.",
|
||
"contentHtml": "<p>Новая команда просит создать сервис, а платформа предлагает одну форму: имя, владелец, runtime, репозиторий и несколько флагов. Сначала это выглядит удобно. Через месяц появляются локальные правки, особые healthcheck, другой retention и ручные исключения в CI. Два сервиса уже не похожи на исходный шаблон, но команда всё ещё считает их его вариантами. Цена ошибки — не только лишняя работа. Теряется владелец контракта, обновления перестают доходить до копий, а рискованный выбор прячется за кнопкой Create.</p>\n<p>Тезис простой: шаблон должен принимать только повторяемый класс задач. Совпадение с базовым контрактом ведёт в golden path. Одно заранее описанное отличие ведёт на review расширения. Несовместимый или одноразовый запрос нужно остановить. Отказ дешевле fork-а, который создаёт ложное ощущение поддержки.</p>\n<h2>Симптомы широкой формы</h2>\n<p>Первый симптом — просьба добавить универсальное поле: «если понадобится, разрешим внешний data class», «пусть runtime выбирается позже», «добавим произвольный adapter». Поле кажется небольшим, но меняет инвариант. После него шаблон уже не описывает один тип сервиса. Он принимает несколько архитектурных решений без владельца.</p>\n<p>Второй симптом — локальный patch сразу после создания репозитория. Команда удаляет обязательный шаг, переписывает pipeline или меняет доступы, а потом обещает вернуть полезное изменение в общий шаблон. Если различие не имеет имени, владельца, границы и условия удаления, это не extension. Это отдельный проект, который маскируется под стандартный путь.</p>\n<p>Третий симптом — платформа выдаёт skeleton для задачи, у которой ещё нет data policy, access model или ответственного. Файлы создаются быстро, но структура начинает диктовать решение. Команда подгоняет требования под уже созданный репозиторий. Технический артефакт появляется раньше архитектурного договора.</p>\n<h2>Механизм: три слоя вместо одной кнопки</h2>\n<p>Разделите решение на три слоя. Первый — входные факты: тип компонента, владелец, runtime, класс данных, требования к доставке и срок жизни. Второй — контракт: допустимые значения и обязательные шаги. Третий — результат: применить базовый путь, отправить ограниченное расширение на review или отказать до уточнения требований.</p>\n<p>Backstage описывает шаблон как набор параметров и последовательных шагов. Это полезный механизм, но он не делает любой параметр безопасным. Параметр собирает значение. Решение о том, разрешено ли значение, должно жить в контракте и проверке. GitHub template repository копирует структуру и файлы в новый репозиторий, но создаёт несвязанную историю. Автоматического канала изменений исходного шаблона это не даёт.</p>\n<table><caption>Симптом → причина → проверка → действие</caption><thead><tr><th>Симптом</th><th>Причина</th><th>Проверка</th><th>Действие</th></tr></thead><tbody><tr><td>Просят «универсальный» флаг</td><td>Поле меняет runtime, ownership, data class, access или retention</td><td>Сравнить поле с базовым инвариантом и назвать владельца</td><td>Убрать поле из golden path; оформить отдельный путь</td></tr><tr><td>После создания нужен patch</td><td>Различие не описано как versioned extension</td><td>Проверить identifier, owner, boundary и rollback</td><td>Остановить копирование; вынести различие на review</td></tr><tr><td>Шаблон приняли для миграции</td><td>Нет повторяемого типа и жизненного цикла</td><td>Проверить повторяемость того же контракта</td><td>Отказать шаблону и провести отдельное решение</td></tr><tr><td>Repository считают fork-ом</td><td>Смешаны копирование и наследование изменений</td><td>Проверить историю и канал обновлений</td><td>Зафиксировать самостоятельное владение или другой механизм</td></tr></tbody></table>\n<figure><img src=\"/assets/editorial/2024/platform-templates-2024-adoption-loop.svg\" alt=\"Цикл выбора платформенного шаблона: базовый контракт, golden path, ограниченное расширение или отказ\"><figcaption>Сначала запрос сверяется с контрактом, затем выбирается путь. Схема не обозначает измеренный adoption или production-результат.</figcaption></figure>\n<h2>Пример контракта</h2>\n<p>Ниже — учебный фрагмент. Он не запускает реальную задачу и не доказывает пригодность набора полей для вашей организации. Базовый путь принимает внутренний HTTP-сервис с известным владельцем и утверждённым runtime. Observability adapter разрешён как именованное расширение. Внешние регулируемые данные форму не проходят.</p>\n<pre><code>apiVersion: 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</code></pre>\n<p>В примере enum ограничивает форму, но не заменяет проверку прав и политики. Owner должен ссылаться на существующую группу, а публикация требует разрешений и проверки credentials. В рабочем шаблоне эти условия подтверждаются средствами вашей платформы. YAML не доказывает успешный запуск, безопасность или пригодность runtime.</p>\n<p>Первая заявка содержит известный service type, владельца, approved runtime и internal data. Она совпадает с контрактом: golden path. Вторая содержит те же факты и один adapter из закрытого списка. Если у расширения есть owner, граница и способ удаления, это review extension. Третья описывает одноразовую регулируемую миграцию, но не содержит владельца, retention и access model. Ей нужен отказ от шаблона и отдельное решение.</p>\n<h2>Почему fork не исправляет несовпадение</h2>\n<p>Fork отвечает на вопрос «как начать самостоятельный проект на основе текущих файлов». Он не отвечает на вопрос «как поддерживать общий контракт между проектами». Repository, созданный из template, получает несвязанную историю. Pull request между копией и шаблоном не становится штатным каналом синхронизации.</p>\n<p>Fork допустим, когда команда принимает независимый жизненный цикл. Тогда нужно записать владельца, область ответственности и способ получать будущие изменения. Если ожидается обновление всех созданных проектов из базового шаблона, нужен другой механизм доставки или честная граница поддержки.</p>\n<p>Не расширяйте базовый шаблон ради редкого запроса. Новое поле увеличивает число состояний для всех пользователей. Особенно опасны поля, которые откладывают решение: «позже выберем runtime», «потом определим доступ», «retention настроит команда». Отсутствующее решение нельзя превратить в безопасный default названием параметра.</p>\n<h2>Порядок действий</h2>\n<ol><li>Запишите заявку: component type, owner, runtime, data class, access, retention и срок жизни. Не начинайте с копирования файлов.</li><li>Сверьте каждый факт с контрактом. Пометьте значения, для которых нет допустимого варианта или владельца.</li><li>Выберите результат. Полное совпадение даёт golden path. Одно названное отличие с границей и rollback даёт review extension. Остальные запросы получают decline.</li><li>Проверьте отрицательный путь: неизвестный runtime, внешний data class, отсутствие owner и неподдерживаемое расширение должны остановиться до создания артефакта.</li><li>Для расширения зафиксируйте identifier, owner, boundary, version и условие удаления. Если поле нельзя заполнить, расширение не готово.</li><li>После review запускайте реальную публикацию. Проверьте credentials, права, pipeline, healthcheck, наблюдаемость и rollback в вашей среде.</li><li>Пересмотрите расширение через согласованный срок. Повторяемый класс можно включить в новую версию контракта. Одноразовый случай не превращайте в обязательную опцию.</li></ol>\n<h2>Ограничения</h2>\n<p>Шаблон не выбирает владельца, не определяет классификацию данных и не делает action безопасным. Список runtime устаревает. Документация объясняет форму и порядок шагов, но не знает ваших сетевых прав, требований регулятора и правил отката. Эти условия проходят отдельную проверку.</p>\n<p>Учебный YAML не является production-конфигурацией. В нём нет конкретной схемы прав, политики секретов, branch protection, SLO и обязательных проверок поставки. Не переносите его в рабочую систему без адаптации и review. Цифры adoption, скорости и снижения дефектов здесь не заявлены.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Решение готово, когда другая команда берёт ту же заявку и получает тот же вердикт по тем же фактам. Для golden path форма принимает только значения контракта. Для extension документ содержит owner, boundary, version и rollback. Для отказа есть причина и следующий вопрос вне шаблона. Неизвестное или рискованное значение останавливается до создания репозитория и не превращается в локальный patch.</p>\n<p>Проверка состоит из четырёх записей: базовая заявка, узкое расширение, несовместимая заявка и повторный запуск первой. Готовность есть, если базовые записи дают одинаковый результат, расширение не меняет инварианты, отказ не создаёт артефакт, а повторный запуск не дублирует обязательные действия.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://backstage.io/docs/features/software-templates/\" target=\"_blank\" rel=\"noopener noreferrer\">Backstage: Software Templates</a> — параметры, шаги и задачи Scaffolder.</li><li><a href=\"https://backstage.io/docs/next/features/software-catalog/descriptor-format/\" target=\"_blank\" rel=\"noopener noreferrer\">Backstage: Descriptor Format of Catalog Entities</a> — форма сущностей и ответственность владельца Template.</li><li><a href=\"https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Docs: Creating a repository from a template</a> — копирование структуры и несвязанные истории.</li></ul>"
|
||
}
|