Amiga
Оглавление курса · урок 5 из 13

Модуль 2 · Строим · урок 5 из 13

Первый вертикальный срез

Соблазн велик: попросить агента «сделай весь бэкенд», потом «весь фронтенд». Получится много кода, который ни разу не запускался вместе. Вертикальный срез — обратный подход: маленькая функция, но через все слои и сразу работающая. В этом уроке делаем первый срез «Переговорок» и разбираем, что в нём читать глазами.

Что такое вертикальный срез

Срез — это сценарий, который пользователь может пройти от начала до конца. Для нас это пункт 2 плана: «список комнат и бронь слота на сегодня, без входа». В нём есть таблица в базе, миграция, два эндпоинта, экран и тесты. Нет авторизации, недели, отмены — они придут следующими срезами.

Почему так:

  • агент получает обратную связь от работающей системы, а не от воображаемой;
  • ошибки интеграции (формат дат между API и интерфейсом, CORS, миграции) всплывают в первый день;
  • первый срез становится образцом, который агент будет копировать, — его стоит сделать хорошо.

Пока входа нет, автор брони — фиксированный сотрудник из переменной DEV_USER_EMAIL. Пометьте это в коде комментарием TODO(auth), чтобы агент в следующем срезе нашёл все места.

Сначала план

Один чат — одна задача. Начните новую сессию и попросите план, не код:

text
/plan Пункт 2 из docs/PLAN.md: список комнат и бронь слота на сегодня.
Правила R1, R2, R3 и сценарии S1, S2 из docs/SPEC.md.
Входа пока нет: автор брони — DEV_USER_EMAIL из окружения, пометь TODO(auth).
Составь план: модели Prisma, миграция, эндпоинты с Zod-схемами
в packages/shared, экран, тесты. Укажи, как будешь соблюдать R3
при одновременных запросах.

Читайте план внимательнее, чем потом будете читать код: исправить абзац дешевле, чем откатывать двести строк. Последний вопрос промпта — проверка. Если в плане написано «перед созданием проверим, что слот свободен», агент собирается сделать гонку: два запроса одновременно проверят, оба увидят свободный слот, оба запишут.

Задача на реализацию

Когда план устраивает, ставьте задачу по схеме из best practices: цель, контекст, ограничения, «готово, когда».

text
Реализуй план выше.

Цель: сотрудник видит комнаты и занятость на сегодня, бронирует свободный слот.
Контекст: docs/SPEC.md (R1–R3, S1, S2), AGENTS.md, apps/api/src/routes/health.ts как образец маршрута.
Ограничения: R3 обеспечивает база (exclusion constraint), а не проверка в коде.
Пересечение → HTTP 409, код ошибки BOOKING_OVERLAP. Новых зависимостей нет.
Готово, когда: npm run check зелёный; есть интеграционный тест на S1, S2
и на два параллельных запроса одного слота (ровно один успешен).

Что читать в диффе

Весь дифф первого среза читать необязательно — но три места обязательно. Посмотреть изменения можно командой /diff прямо в сессии.

Схема. Первое, что проверяем, — типы времени:

prisma · apps/api/prisma/schema.prisma
model Room {
  id         Int       @id @default(autoincrement())
  name       String    @unique
  capacity   Int
  hasScreen  Boolean   @default(false)
  archivedAt DateTime? @db.Timestamptz(3)
  bookings   Booking[]
}

model Booking {
  id          Int       @id @default(autoincrement())
  roomId      Int
  room        Room      @relation(fields: [roomId], references: [id])
  title       String
  bookedBy    String
  startsAt    DateTime  @db.Timestamptz(3)
  endsAt      DateTime  @db.Timestamptz(3)
  cancelledAt DateTime? @db.Timestamptz(3)
  createdAt   DateTime  @default(now()) @db.Timestamptz(3)

  @@index([roomId, startsAt])
}

По умолчанию Prisma хранит DateTime в PostgreSQL как время без зоны. Если агент не поставил @db.Timestamptz, через месяц вы будете искать, почему брони съехали на три часа.

Миграция. Prisma не умеет описывать ограничения исключения в схеме, поэтому их добавляют в SQL миграции вручную. Агент создаёт пустую миграцию (npx prisma migrate dev --create-only) и дописывает:

sql · apps/api/prisma/migrations/20261022120000_booking_no_overlap/migration.sql
CREATE EXTENSION IF NOT EXISTS btree_gist;

ALTER TABLE "Booking"
  ADD CONSTRAINT booking_no_overlap
  EXCLUDE USING gist (
    "roomId" WITH =,
    tstzrange("startsAt", "endsAt", '[)') WITH &&
  )
  WHERE ("cancelledAt" IS NULL);

Прочитайте это построчно. Полуоткрытый интервал [) означает, что встречи 14:00–15:00 и 15:00–16:00 не пересекаются. Условие WHERE исключает отменённые брони — иначе отменённый слот нельзя будет занять снова.

Обработка ошибки. Найдите место, где API превращает нарушение ограничения (код PostgreSQL 23P01) в ответ 409. Частая ошибка агента — ловить любую ошибку базы и отвечать «слот занят». Тогда упавшее соединение тоже выглядит как занятый слот.

Остальное — маршруты, разметку экрана — достаточно пробежать глазами: за них отвечают тесты и типы.

Проверьте руками

Отчёт агента «тесты прошли» — не то же самое, что работающий сервис. Запустите и попробуйте сломать:

bash
docker compose up -d && npm run db:migrate && npm run dev

# два одновременных запроса на один слот
for i in 1 2; do
  curl -s -o /dev/null -w "%{http_code}\n" -X POST localhost:3000/api/bookings \
    -H 'content-type: application/json' \
    -d '{"roomId":1,"title":"Планёрка","startsAt":"2026-10-23T11:00:00+03:00","endsAt":"2026-10-23T12:00:00+03:00"}' &
done; wait

Ожидаемый результат — один ответ 201 и один 409. Потом откройте экран в браузере и забронируйте слот мышкой: интеграционный тест не заметит, что кнопка не реагирует.

Закрываем срез

Попросите агента отметить пункт в docs/PLAN.md и коротко описать в нём, что сделано и что отложено (TODO(auth)). Закоммитьте одним коммитом с понятным сообщением. Этот срез — эталон: в следующих задачах пишите «сделай по образцу брони».

Попробуйте сами

10–15 мин на рабочем месте
  1. Сделайте первый срез своего проекта: план через /plan, затем задача по схеме «цель — контекст — ограничения — готово, когда».
  2. В диффе найдите три самых рискованных места и прочитайте их построчно. Запишите, что нашли.
  3. Проверьте гонку параллельными запросами через curl или напишите на неё интеграционный тест.

Коротко

  • Срез — маленький сценарий через все слои, который сразу работает.
  • План читайте внимательнее кода: его дешевле исправить.
  • В первом срезе читайте схему, миграции и обработку ошибок; остальное страхуют тесты.
  • Инварианты вроде «брони не пересекаются» держит база, а не проверка в коде.
  • Проверяйте руками: параллельные запросы и клик в браузере.
  • Первый срез — образец для всех следующих.
Отметка хранится только в вашем браузере
Хотите связаться с владельцем
компании напрямую?
Дмитрий Тарасов
Дмитрий Тарасов
СЕО

НАПИСАТЬ

Написать в MAX