Практикум: от первого изменения до интеграции 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-контракт
Цель: увидеть разницу между типом и проверкой данных.
В новой схеме для отдельного учебного события потребуйте documentId и ограничьте
model списком news | event. Сначала покажите, какие payload текущая schema ещё
принимает. Затем добавьте проверки положительного/отрицательного случая.
Результат: invalid JSON не доходит до отправки события; consumer также проверяет вход. Изменение проверено сборкой и typecheck обоих потребителей.
Вопрос: как обновить producer и consumer без окна несовместимости? Можно ли поддержать старую и новую версию события одновременно?
6. Зарегистрировать новую функцию
Цель: пройти trigger → queue → worker → result.
Используйте учебный learning-echo из главы воркера. Зарегистрируйте
его в том же worker и отправьте событие через SDK с тестовым payload. Затем создайте
свою схему входа вместо повторного использования StrapiWebhook.
Результат: в Hatchet видны отдельное определение и успешное выполнение; остановленный воркер не мешает producer принять событие, а после запуска задача выполняется. Не выдавайте остановку локального worker за проверку production HA.
Вопрос: что ограничивают slots, и что произойдёт при запуске двух экземпляров?
7. Исследовать повторы и идемпотентность
Цель: перестать считать retry гарантией однократного эффекта.
На локальной задаче запланируйте детерминированную ошибку и наблюдайте retries. Смоделируйте побочный эффект на учебном ресурсе. Разделите ошибку до эффекта и после. Затем спроектируйте idempotency key и атомарное сохранение факта обработки.
Результат: объясняете, почему повтор входа может повторить эффект и почему in-memory Set теряется при рестарте. Положительные и отрицательные сценарии проверены без отправки настоящих писем, платежей или изменения production.
Вопрос: что делать, если внешняя система выполнила операцию, но ответ потерялся?
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. Запишите время восстановления и версию релиза. Не удаляйте оригинальные тома ради доказательства восстановления.
Вопрос: может ли восстановление Hatchet выполнить старую задачу повторно?
11. Усовершенствовать deploy readiness
Цель: отличить готовый HTTP-сервер от рабочего асинхронного сервиса.
Спроектируйте проверку worker connection и выполнения специального безопасного
smoke-события с deadline. Добавьте проверку exp токена и понятную ошибку до запуска
приложений. Изолируйте smoke от бизнес-обработки.
Результат: неисправная очередь/воркер не дают ложный успешный deploy, а успешная проверка не создаёт реальные побочные эффекты. Проверьте локально и в тестовом окружении, прежде чем менять критерий production-отката.
Вопрос: какой false positive возможен, если задачу выполнил старый воркер?
12. Проследить SSG → кеш → ISR
Цель: отличить генерацию страницы от её обновления.
Запустите node scripts/check-static-site.mjs и найдите проверки prerender manifest,
cache HIT и webhook. На локальной 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, и почему ошибка сети не должна превращаться в сообщение об удалённой странице.
Контрольные вопросы
- Почему 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?
Ответы можно найти по соответствующим главам. Если ответ состоит только из названия инструмента, попробуйте описать конкретный путь данных или отказ — это главный учебный результат этого проекта.