Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Архитектура эксплуатации и наблюдаемость
production_architecture

Архитектура эксплуатации и наблюдаемость

Модульные границы, границы ошибок, журналирование, телеметрия, флаги возможностей, интернационализация, CI/CD и эксплуатация фронтенда.

Открыть лабораториюv0.2.0Запускается локально из публичного репозитория

Архитектура эксплуатации и наблюдаемость

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

#Результат урока

Вы проведёте границы модулей, встроите безопасную телеметрию, постепенное включение и откат и свяжете ошибки с версией выпуска и идентификатором запроса.

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

#1. Граф зависимостей и публичные API

Сборка приложения зависит от маршрутов, сценариев и общих модулей; общий слой не импортирует сценарий. Каждый раздел экспортирует публичный API, а глубокие импорты запрещаются линтером. Границы следуют владельцам и совместной изменчивости, а не создают index для каждого div.

Направление импортов удобно закрепить правилом линтера — тогда его нельзя нарушить незаметно:

// eslint.config.js { rules: { 'import/no-restricted-paths': ['error', { zones: [ // общий слой не знает о сценариях { target: './src/shared', from: './src/features' }, { target: './src/shared', from: './src/widgets' }, // сценарии не лезут во внутренности друг друга { target: './src/features/*/!(index.ts)', from: './src/features', except: ['./*'] }, ], }], }, }

Правило ловит именно те случаи, которые ломают переносимость: функция из shared/utils, импортирующая хранилище конкретного сценария, делает общий слой незаменяемым и создаёт цикл. Обратная крайность — index.ts вокруг каждого файла — даёт много барьеров и ни одного полезного контракта.

Проверьте себя. Постройте граф и найдите циклы.

Частая ошибка. Общий модуль утилит импортирует хранилище конкретного сценария.

#2. Классы ошибок и границы

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

Классы ошибок описывают, что делать пользователю и что писать в журнал:

// src/shared/errors/types.ts export type AppError = | { code: 'invalid_input'; field: string; message: string } // показать у поля | { code: 'forbidden' } // предложить сменить аккаунт | { code: 'network'; retryAfterMs: number } // предложить повтор | { code: 'chunk_load' } // предложить перезагрузку | { code: 'internal'; traceId: string }; // код обращения

Дальше граница ошибок сопоставляет класс с действием, а не показывает одну строку на все случаи:

// src/app/RouteErrorBoundary.tsx export function RouteErrorBoundary({ error }: { error: AppError }) { switch (error.code) { case 'chunk_load': return <ErrorState title="Приложение обновилось" action={<ReloadButton />} />; case 'network': return <ErrorState title="Нет связи с сервером" action={<RetryButton />} />; case 'forbidden': return <ErrorState title="Недостаточно прав" action={<SwitchAccountLink />} />; default: return <ErrorState title="Ошибка" hint={`Код обращения: ${error.traceId}`} />; } }

Один catch с «что-то пошло не так» лишает пользователя действия, а инженера — причины: по такому сообщению нельзя отличить истёкшую сессию от упавшей части пакета.

Проверьте себя. Составьте матрицу ошибок.

Частая ошибка. Один catch с «что-то пошло не так» без кода причины.

#3. Безопасная телеметрия и связь событий

Событие интерфейса содержит имя, версию, шаблон маршрута, длительность, исход и разрешённые измерения. Не отправляйте исходную строку URL, значения формы, токены и DOM. traceparent или идентификатор связи (correlation id) соединяет браузер, BFF и службу. Выборка и ограничение частоты сдерживают стоимость.

Разница между небезопасным и безопасным событием — в том, что именно уезжает наружу:

// ❌ персональные данные, токен и точный URL в телеметрии track('save_failed', { url: window.location.href, // содержит ?email=... и идентификаторы payload: Object.fromEntries(formData), // значения полей пользователя token: session.accessToken, }); // ✅ шаблон маршрута, исход, длительность и связь с сервером track('save_failed', { route: '/tickets/:ticketId', // шаблон, а не конкретный адрес outcome: 'validation_error', fields: ['title', 'priority'], // имена полей без значений durationMs: 412, release: __APP_VERSION__, traceId: response.headers.get('traceparent'), });

traceId из ответа сервера — самое ценное поле: по нему обращение пользователя связывается с записями BFF и внутренних служб. Шаблон маршрута вместо конкретного URL решает сразу две задачи: не утекают идентификаторы и не взрывается кардинальность метрик.

Проверьте себя. Добавьте идентификатор трассировки к неудачному сохранению.

Частая ошибка. Логировать FormData целиком.

#4. Web Vitals и product signals

Собирайте LCP, INP и CLS с укрупнёнными признаками маршрута, выпуска и устройства и с выборкой. Добавляйте продуктовые задержки: от поиска до результата, от сохранения до подтверждения. Не отправляйте исходные URL с высокой кардинальностью. Панель должна разделять вклад интерфейса, сети и сервера.

Технические метрики собираются готовой библиотекой, продуктовые — вручную, и нужны обе:

// src/shared/telemetry/vitals.ts onLCP(sendVital); onINP(sendVital); onCLS(sendVital); function sendVital(metric: Metric) { send({ name: metric.name, value: Math.round(metric.value), rating: metric.rating, // good | needs-improvement | poor route: matchRoutePattern(window.location.pathname), release: __APP_VERSION__, deviceClass: getDeviceClass(), // укрупнённо: low | mid | high }); }
// продуктовый сигнал: то, что пользователь считает «быстро» const started = performance.now(); await saveTicket(draft); send({ name: 'ticket_save_to_confirm', value: Math.round(performance.now() - started) });

Среднее время загрузки страницы прячет ровно то, что важно: половина пользователей с медленными устройствами может видеть INP в 700 мс при «нормальном» среднем. Поэтому смотрят на p75 и распределение по классам устройств, а не на одно число.

Проверьте себя. Определите SLO критического взаимодействия.

Частая ошибка. Измерять только среднее время загрузки страницы.

#5. Флаги возможностей и совместимость

У флага (feature flag) есть значение по умолчанию, правило при сбое, целевые группы и журнал изменений. Обе ветви кода должны работать с API и схемой во время постепенного включения. Флаг не заменяет авторизацию. После стабилизации удалите старую ветвь и сам флаг.

Описание флага включает поведение при недоступности службы флагов — это и есть аварийный выключатель:

// src/shared/flags/definitions.ts export const flags = { newTicketEditor: { default: false, // при сбое службы флагов — старый путь rollout: [5, 25, 100], // этапы в процентах killSwitch: true, // выключается без развёртывания owner: 'tickets-team', removeAfter: '2026-10-01', // срок жизни: флаг не живёт вечно }, } as const;

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

Проверьте себя. Спланируйте включение для 5, 25 и 100% пользователей с аварийным выключателем.

Частая ошибка. Хранить секретное право доступа только во флаге.

#6. i18n как архитектурный контракт

Тексты, формы множественного числа, даты, числа и валюты форматируются с учётом локали через каталог сообщений и Intl. Не склеивайте предложение из фрагментов: порядок слов меняется. Разметка выдерживает длинный текст и RTL, часовой пояс задаётся явно. Ключи сообщений извлекаются и версионируются.

В русском языке три формы множественного числа, и склейка строк их не покрывает:

// ❌ «1 товара», «2 товара», «5 товара» const label = count + ' товара'; // ✅ выбор формы по правилам локали const plural = new Intl.PluralRules('ru-RU').select(count); // one | few | many const label = { one: 'товар', few: 'товара', many: 'товаров' }[plural];

Даты требуют явного часового пояса, иначе один и тот же момент времени покажется разным пользователям разными датами:

new Intl.DateTimeFormat('ru-RU', { dateStyle: 'medium', timeStyle: 'short', timeZone: 'Europe/Moscow', // явно: иначе берётся зона устройства }).format(new Date(ticket.createdAt));

Ту же логику ломает склейка предложения из фрагментов: в другой локали порядок слов и род меняются, и собранная фраза становится неверной. Поэтому в каталог кладут целое сообщение с подстановками, а не набор кусков.

Проверьте себя. Добавьте формы множественного числа и снимок RTL.

Частая ошибка. count + " товара".

#7. Артефакт CI и безопасное развёртывание

Собирайте один раз и продвигайте тот же неизменяемый артефакт. Настройка окружения во время выполнения не должна требовать непроверенной пересборки или раскрывать секреты. Ресурсы с хешем содержимого кешируются надолго, HTML — недолго и с актуальными ссылками. Развёртывание требует обратно совместимого API и возможности отката.

Разные типы файлов требуют разных правил кеширования — иначе старая вкладка получит ссылки, которых уже нет:

# HTML — короткий кеш: должен быстро узнать про новый выпуск location = /index.html { add_header Cache-Control "no-cache"; } # ресурсы с хешем в имени — неизменяемы, кешируются надолго location /assets/ { add_header Cache-Control "public, max-age=31536000, immutable"; }

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

// src/app/chunk-recovery.ts window.addEventListener('vite:preloadError', (event) => { event.preventDefault(); reportError({ code: 'chunk_load', release: __APP_VERSION__ }); showReloadPrompt(); // приложение обновилось — предложить перезагрузку });

Проверьте себя. Смоделируйте старую вкладку после нового развёртывания.

Частая ошибка. Немедленно удалить старые части пакета.

#8. Реакция на инцидент и инструкция

Инструкция (runbook) содержит сигнал, серьёзность, панели, последние выпуски и флаги, снижение ущерба, откат и проверку. Причиной инцидента интерфейса может быть CDN, ресурс, API, конкретный браузер или сторонняя служба. Сначала уменьшите ущерб и сохраните данные, затем ищите первопричину. Разбор добавляет защиту от повторения.

Инструкция полезна тогда, когда по ней может действовать дежурный, не писавший этот код:

Сигнал: доля ошибок chunk_load > 2% за 5 минут (панель «Frontend / Errors») Серьёзность: S2 — часть пользователей не может перейти между маршрутами Проверить: 1) выпуск за последние 30 минут; 2) наличие /assets/* предыдущей сборки на CDN Снизить: включить баннер «обновите страницу»; вернуть предыдущий артефакт (kill switch) Откат: promote release N-1; кеш HTML сбросить, /assets/* не удалять Проверка: доля chunk_load < 0.2%, успешность навигации восстановлена Разбор: добавить проверку наличия предыдущих ресурсов в шаг развёртывания

Порядок действий здесь не случаен: сначала уменьшить ущерб и сохранить данные пользователя, потом искать первопричину. Локальная отладка без контекста выпуска, устройства и идентификатора запроса на этом этапе бесполезна — воспроизвести проблему на своей машине обычно не удаётся вовсе.

Проверьте себя. Проведите учебный сбой загрузки части пакета.

Частая ошибка. Отлаживать только локально без контекста выпуска и пользователя.

#Лаборатория

Разделите монолит на приложение, маршруты, сценарии и общий слой; введите публичные API и правила импортов. Добавьте телеметрию границы ошибок с безопасным контекстом выпуска, Web Vitals, постепенное включение флага, локализацию, артефакт CI и список проверок отката.

Работа готова, когда граф зависимостей направлен, персональные данные не уходят в телеметрию, инцидент связывается с выпуском и запросом API, а возможность отключается без нового развёртывания.

#Источники для сверки

  • OpenTelemetry JavaScript
  • W3C Trace Context
  • MDN Intl

Устранение неисправностей

Далее: Внутреннее устройство и миграция