Практикум: от первого изменения до интеграции CMS
Упражнения ниже — задания для читателя. Они не реализованы появлением этой главы. Проводите их в отдельной Git-ветке и с локальными учебными данными. Каждое завершённое задание оформляйте отдельным коммитом с результатом проверки в описании.
1. Найти путь данных одной новости
Цель: проследить данные через реальные границы системы.
Создайте учебную News в Content Manager, задайте slug и publishedOn. Найдите соответствующие query/populate в CMS client, Zod schema в contracts, adapter, Server Component и tRPC procedure. Сравните draft и опубликованный ответ REST.
Результат: можете объяснить, почему Publish меняет страницу без rebuild, почему read-only token находится только на сервере и почему неизвестный slug отличается от недоступной CMS. Не исправляйте ошибки добавлением JSON fallback.
2. Добавить событие и проверить даты
Цель: сохранить смысл времени при разных UTC offsets.
Создайте Event в CMS с startsAt и без endsAt. Publish и проверьте одно время вместо диапазона. Добавьте конец, republish. Сравните запись 2025 и 2026 годов, затем два ISO timestamps с разными offsets. Объясните сортировку по Date.parse и отображение через Europe/Moscow. Не подставляйте полночь/конец, которых не было в источнике.
3. Изменить тему Mantine
Цель: отделить токены темы от геометрии конкретной страницы.
Измените один оттенок палитры или default button radius в packages/ui/src/theme.ts.
Проверьте, какие компоненты изменились, а какие остались с цветом из соседних *.module.css и общих CSS variables.
Согласуйте тему/CSS, если хотите глобального результата.
Результат: можете объяснить два источника цвета, не заменяя все размеры inline styles. Вручную проверьте мобильное меню, keyboard focus и контраст текста.
Вопрос: зачем ColorSchemeScript расположен в head, а provider — внутри body?
4. Добавить безопасную процедуру tRPC
Цель: изучить общий вход, серверный caller и HTTP-клиент.
Добавьте news.byCategory с входом { category: string }, валидируемым Zod.
Верните отфильтрованный массив, не добавляя mutation или доступ к CMS.
Напишите серверный тест: существующая категория, неизвестная, неправильный тип.
Результат: вызов работает через caller и tRPC client, а неверный вход отвергается до бизнес-фильтрации. Клиент получает тип новой процедуры без отдельного интерфейса.
Вопрос: где добавить права доступа, если процедура станет отдавать закрытые записи?
5. Ужесточить учебный runtime-контракт
Цель: увидеть разницу между типом и проверкой данных.
Прочитайте действующий контракт webhook и его тесты. Создайте отдельную учебную
схему для entry.publish: обязательный documentId, model только news | event.
Не подменяйте ею production-схему: инвалидирование всего кеша допускает другие
модели, а обработчик также принимает события медиа.
В серверном тесте сравните действующую и учебную схемы на корректном payload,
entry: {}, неверной модели и событии media.update. Используйте результаты
safeParse, не приводите внешние данные через as к желаемому типу.
Результат: можете объяснить, какой вход отвергнут каждой схемой и почему ужесточение контракта требует согласования отправителя и получателя. Очередь и отдельный consumer для этого упражнения не нужны.
6. Изучить расписание Strapi
Изучите встроенный cron. Не включайте его до появления конкретной задачи.
7. Исследовать повторы webhook
Цель: различать безопасный повтор инвалидирования и повтор произвольного эффекта.
В существующем серверном тесте webhook выполните один и тот же авторизованный payload дважды. Проверьте успешные ответы и повторные вызовы инвалидирования. Смоделируйте ошибку инвалидирования и убедитесь, что она не превращается в 200. Используйте учебные значения и контролируемые зависимости теста, без живой CMS.
Результат: повторное уведомление может снова сбросить кеш, не создавая новую запись CMS. Отсутствие собственного механизма retry в этом тесте не означает гарантированную доставку webhook. Не приписывайте обработчику очередь или exactly-once.
Углубление: после главы о транзакциях спроектируйте отдельную учебную операцию с ключом идемпотентности. Разберите потерянный ответ, одновременный повтор и рестарт; укажите, что должно сохраняться атомарно. Этот механизм не реализован появлением упражнения и не добавляется к production-webhook автоматически.
8. Проверить runtime-контракт CMS
Цель: проверить runtime-контракт внешнего REST ответа.
В CMS client test добавьте fixture с неверной датой, недопустимым Blocks link или неожиданным media origin. Добейтесь явного CmsUnavailableError всего ответа. Затем смоделируйте отказ второй страницы pagination: первая не должна стать «полным списком». Сравните гарантии TypeScript и Zod.
Результат: transport boundary покрыт поведением, тест не зависит от live CMS.
9. Подключить Strapi как реальный источник
Цель: подтвердить редакторский вертикальный сценарий уже работающей интеграции.
Через обычный Content Manager: draft → publish → edit/republish → unpublish.
Проверьте список, detail, metadata и фотографию без новой сборки. Не заменяйте
этот процесс mock-тестом. Затем временно остановите локальную CMS: сравните уже закешированную страницу
с /api/content-health, который должен вернуть 503. Верните CMS. Автоматических UI-тестов не добавляйте.
Результат: записи и изображения показывают настоящий state CMS, token не виден в browser data, unpublish убирает detail. Укажите отдельно, какие шаги реально проверены руками, какие покрыты серверными tests и что осталось непроверенным.
10. Проверить резервную копию и восстановление
Цель: получить проверяемую копию, а не просто файл dump.
Создайте тестовую запись и media в локальном Strapi. Снимите копию. Восстановите
базу в отдельную learning_restore и файлы в отдельный volume/экземпляр CMS.
Не подключайте production-приложение к учебной базе.
Результат: запись и файл открываются в восстановленной CMS; вы знаете, какие секреты и ключи требуются дополнительно к SQL. Запишите время восстановления и версию релиза. Не удаляйте оригинальные тома ради доказательства восстановления.
Вопрос: как проверить совместимость резервной копии с версией CMS?
11. Усовершенствовать deploy readiness
Проверьте отличие ответа /api/health от чтения настоящего контента через
/api/content-health. В локальной среде исследуйте поведение при недоступной CMS.
12. Проследить SSG → кеш → ISR
Цель: отличить генерацию страницы от её обновления.
Прочитайте scripts/check-static-site.mjs и найдите проверки prerender manifest,
cache HIT и webhook; их полное выполнение относится к CI. На локальной учебной CMS создайте новость с новым slug: убедитесь,
что адрес появляется без rebuild. Сначала откройте отсутствующий адрес, затем
опубликуйте запись с этим slug и проверьте сброс закешированного 404.
Результат: можете объяснить роль generateStaticParams, тега cms,
revalidatePath и страховочной часовой ревалидации. Не отключайте SSG для обхода
ошибки CMS и не смешивайте React cache с межзапросным кешем Next.js.
13. Собрать страницу состояния на UI kit
Цель: собрать интерфейс из темы, примитивов и CSS Module.
Прочитайте app/not-found.tsx. Измените пояснение и вторую ссылку, сохранив
семантический h1 и HTTP 404. Проверьте произвольный URL и отсутствующий slug CMS,
клавиатурный фокус, ширину 320 px и режим без JavaScript. Зафиксируйте отдельно
ограничение ISR fallback текущего Next.js, описанное в главе SSG.
Результат: объясняете, что задаёт тема, что — CSS Module, и почему ошибка сети не должна превращаться в сообщение об удалённой странице.
Итоговые сценарии учебника
Следующие сценарии связывают написанные главы. Выполняйте по одному, в собственной ветке/временной среде. Для каждого сохраните commit или набор временных файлов, входные данные, ожидаемое и фактическое наблюдение. «Не запускал» — отдельный статус, а не успешный результат. Внешние сервисы используются только в учебных копиях.
A. Проверенный контент → адаптивное расписание
Материалы: ADT, FP, HTML/CSS, композиция.
Возьмите fixture из серверных CMS tests, проследите schema → adapter → props расписания. В отдельной fixture испортите дату: ответ должен быть отвергнут на границе, а не сортироваться как NaN. Для корректных данных проверьте сортировку, группировку и отсутствие выдуманного endsAt. Соберите учебную story на UIKit, без импорта CMS-клиента в пакет UI. Проверьте 320 px, увеличенный текст и Tab.
Приёмка: неверный вход не попадает в props; корректный отображает время Europe/Moscow и однозначные подписи. Укажите отдельно серверную проверку и ручной layout.
B. Один фильтр и четыре модели состояния
Выполните сравнительную лабораторию. Один контракт, expected и порядок actions сохраняются во всех вариантах. Проверьте повтор reset и cleanup. Расширьте опыт независимыми экземплярами, сопоставьте draft поля с подтверждённым URL-фильтром и Back/Forward.
Приёмка: одинаковый список и объяснение подписок, отсутствие утечки состояния между экземплярами. Выберите для задачи один механизм и обоснуйте его сложность.
C. Draft → publish → webhook → новый HTML
Выполните задания 1, 9 и 12 на собственной CMS, следуя жизненному циклу контента. Начните с отсутствующего slug, затем создайте draft, опубликуйте, измените и снимите с публикации. Запишите HTML/detail/metadata/media на каждом шаге, webhook response и состояние открытой вкладки. Ошибка CMS не должна выглядеть как удалённая запись.
Приёмка: новый slug без rebuild, сброс прежнего 404 и объяснение, почему 200 webhook не обещает мгновенное обновление каждой уже открытой вкладки. HTTP-статус fallback сверяйте с ограничениями SSG/ISR.
D. Контракт API и потерянный ответ
Выполните четыре adapters, пагинацию и транзакционный журнал. Сначала сравните read/update/invalid/missing/forbidden/version conflict. Потом зафиксируйте операцию в БД, отбросьте ответ и повторите тот же key; сравните другой payload с тем же key и две конкурентные сессии.
Приёмка: общий бизнес-результат при разных протокольных ошибках; одна новая version и сохранённый ответ после повтора. In-memory adapter не является проверкой устойчивости журнала после рестарта БД.
E. Одна агрегация в JS, SQL и MapReduce
Выполните аналитический практикум и локальную модель MapReduce на одинаковых events. Сравните count/sum/average, пустой набор и разбиения. Повторите одну партицию: покажите, почему ассоциативность не устраняет дубли. Затем добавьте позднее событие к закрытому event-time окну и объясните policy коррекции/отклонения.
Приёмка: одинаковые итоги без дублей, контрпример среднего средних, явные допуски float и границы локальной модели распределения.
F. Колонки/JSONB и exact/ANN
Выполните SQL/JSONB и ANN. Выберите три частых запроса и два инварианта. Решите, какие поля должны быть колонками, что остаётся JSONB и какие индексы следуют из запросов. На фиксированном наборе векторов сравните exact top-k и HNSW с/без редкого фильтра.
Приёмка: CHECK/FK, фактические планы, recall@k и latency на одном наборе. Наличие CREATE INDEX без его использования не считается ANN-результатом.
G. Свежесть и конкурентный кеш
Выполните лабораторию гонок, затем сценарий CMS из C. Для браузера, Router Cache, серверных данных и HTML выпишите ключ, источник истины и событие обновления. Сравните холодный/прогретый запрос, потерянный webhook, новый slug и недоступность CMS. Не включайте Redis в сайт ради этого опыта; его cache-aside изучается отдельно.
Приёмка: старый response не записывается в новое поколение, промахи одного ключа объединяются, ошибка не кешируется навсегда. Отдельно фиксируйте, какой клиент всё ещё может получить старые данные и почему.
H. Отказ зависимости, релиз и восстановление
Выполните лабораторию релиза, диагностику и задание 10 по восстановлению. Различите process health и content readiness. Для релиза соберите свидетельства точного ref/digest/CI result; production-образы собираются штатным CI. Restore выполняется в отдельную учебную базу и media copy.
Приёмка: отказ CMS обнаруживается content-health, причины не подменяются общим «сервер сломан», восстановленные запись и файл открываются. Image rollback не выдаётся за откат schema/data; публикация требует отдельного разрешённого этапа.
DevOps и наблюдаемость
Прочитайте вводную и практическую главу.
Найдите путь релиза от commit до HTTP smoke. Объясните, почему pg_dump и откат
web-образа решают разные задачи. Создайте отдельное учебное правило на vector(0),
проверьте pending/firing и восстановление на vector(1). Затем сравните ноль с
отсутствием временного ряда. Критерий: вы можете объяснить каждый переход состояния,
не останавливая production-сервис. Подключение Telegram — необязательное упражнение.
Контрольные вопросы
- Почему contracts нужен build перед dev?
- Почему tRPC caller не использует URL
/api/trpc? - Почему
import typeважен для клиентского кода и native Node TypeScript? - Почему 200 webhook не означает, что новый HTML уже сгенерирован?
- Почему
.envи named volume живут дольше Docker-контейнера? - Почему смена
.envне меняет пароль существующей роли PostgreSQL? - Почему Docker
restartне применяет новое environment? - Почему robots.txt не защищает закрытые данные?
- Что восстановится при image rollback, а что останется новым?
- Каких проверок не выполняет зелёный
pnpm check?
Ответы можно найти по соответствующим главам. Если ответ состоит только из названия инструмента, попробуйте описать конкретный путь данных или отказ — это главный учебный результат этого проекта.
Практика доставки через PR и релизную линию
По главе Git создайте отдельный worktree/ветку в учебном repository. Добавьте осмысленный серверный тест, добейтесь RED→GREEN и обновите changelog, сохранив ручной текст PR. Переместите target и объясните, почему старый результат CI нельзя использовать для интеграции.
Затем создайте release-ветку с immutable base tag, подготовьте draft notes и сравните четыре digest staging и кандидата production. На изолированной копии измените модель CMS: promotion должен остановиться до запуска кандидата. Верните совместимую модель и воспроизведите отказ smoke после переключения: прежние routes, данные и приложения должны восстановиться.
Результат: можете различить draft/published/applied/live, доказать отсутствие пересборки при promotion и объяснить, почему охлаждается точная staging generation, а production не участвует в LRU. Эти упражнения не выполняются на public production.