Согласованное обновление связанных данных о заявке

CloudflareBeginner
Практиковаться сейчас

Введение

Заявку нельзя закрывать, пока не сохранена запись о решении. Вы используете подготовленный пакет D1, чтобы выполнить эти записи вместе, а затем передадите закладку API Sessions между запросами, чтобы последующие чтения видели ранее зафиксированные изменения.

В этой лабораторной работе рассматриваются две разные задачи: атомарный откат и согласованность последовательных сеансов. Используются одна независимая база данных и один Worker; включать реплики для чтения или воспроизводить гонку репликации не требуется.

Используйте собственную учебную учётную запись и новую виртуальную машину. Сначала настройка установит Node.js 22.22.0, затем выполнит npm install для локального Wrangler 4.131.1 и зависимостей проверки в каталоге /home/labex/project/ticket-database. Версии прямых зависимостей зафиксированы; установка создаст собственный lock-файл. Во время настройки вход в облако и операции с проверяемой базой данных не выполняются. На личном компьютере установите ту же версию Wrangler командой npm install --save-dev wrangler@4.131.1 в каталоге проекта.

В этом упражнении используются небольшие синтетические записи, которые укладываются в бесплатные лимиты D1. Текущее использование учётной записи также учитывается в этих лимитах. Покупать домен не нужно. Не удаляйте эту виртуальную машину, пока не проверите удаление ресурсов и выход из учётной записи.

Авторизуйте виртуальную машину и выберите учётную запись

На этом этапе вы подключите новый терминал к собственной учебной учётной записи. Одного входа в Dashboard недостаточно для авторизации виртуальной машины. Разрешение D1 позволяет создавать базы данных, изменять SQL-схему и удалять базы данных; разрешение Workers позволяет выполнять развёртывание, а разрешение KV поддерживает инвентаризацию ресурсов при очистке Wrangler. Перед авторизацией ознакомьтесь со страницей согласия, включая раздел Background Access.

Откройте подготовленный проект и проверьте зафиксированную версию CLI:

cd /home/labex/project/ticket-database
npx wrangler --version

Ожидается 4.131.1. Запустите авторизацию устройства. Параметр --device выведет код для браузера, а --browser=false позволит вам самостоятельно выбрать способ открытия браузера:

npx wrangler login --device --browser=false --scopes account:read user:read d1:write workers_scripts:write workers_kv:write

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

npx wrangler whoami --json

Проверьте значение loggedIn: true, затем прочитайте name и id учётной записи, даже если в списке указана только одна учётная запись. Скопируйте нужный ID в конфигурацию ниже. Следующая переменная оболочки использует 6 случайных байтов (12 шестнадцатеричных символов), чтобы избежать совпадений с ресурсами других учащихся. Here-document записывает JSON между строками JSON; переменная $RUN разворачивается внутри этого блока.

Обратная косая черта перед $schema сохраняет этот ключ JSON буквально; $RUN по-прежнему подставляет уникальное имя текущего запуска.

RUN=labex-c04-d05-$(openssl rand -hex 6)
cat > wrangler.jsonc <<JSON
{
  "\$schema": "./node_modules/wrangler/config-schema.json",
  "name": "$RUN",
  "account_id": "YOUR_ACCOUNT_ID",
  "main": "src/index.js",
  "compatibility_date": "2026-09-15",
  "workers_dev": true,
  "preview_urls": false
}
JSON

Перед выполнением блока замените YOUR_ACCOUNT_ID. Не закрывайте этот терминал, чтобы переменная RUN оставалась доступной. Поле name идентифицирует этот запуск, а account_id выбирает учётную запись для облачных операций. Файл содержит обычный JSON, который также является допустимым JSONC. При его создании Worker не разворачивается.

Подготовьте заявки и записи о решениях

На этом этапе вы подготовите две связанные таблицы. При закрытии заявки должна также сохраняться запись о решении. Если успешно выполнится только одна запись, сотрудники могут увидеть закрытую заявку без объяснения. Сценарий настройки предоставляет схему и HTTP-маршрутизатор; вам предстоит реализовать связанные операции SQL.

Создайте временную облачную базу данных. Параметр --binding DB задаёт короткое имя для обращения к базе из кода, --update-config записывает её настоящее имя и UUID в wrangler.jsonc, а --use-remote=false оставляет разработку локальной:

npx wrangler d1 create "$RUN-db" --binding DB --update-config --use-remote=false

Прочитайте созданные имя и ID, затем проверьте сохранённую привязку:

cat wrangler.jsonc

Запись DB должна содержать имя базы данных этого запуска. Привязка — это настроенное соединение между кодом и ресурсом. Её UUID идентифицирует облачную базу данных, а параметр --local использует отдельную базу SQLite на этой виртуальной машине. В командах SQL всегда явно указывайте либо --local, либо --remote.

cat schema.sql
npx wrangler d1 execute DB --local --file schema.sql
npx wrangler d1 execute DB --remote --file schema.sql

Заявка 1 открыта и не содержит записи о решении. Заявка 2 закрыта и связана с событием решения 1. Занятый ID события создаёт контролируемый сценарий ошибки: вставка другого события с ID 1 должна нарушить первичный ключ.

Обеспечьте атомарность записей и последовательность чтения

На этом этапе вы решите две отдельные задачи согласованности. Атомарность означает, что либо обе связанные записи успешно сохраняются, либо не сохраняется ни одна. Метод D1 batch() выполняет подготовленные инструкции как транзакцию: при ошибке весь пакет откатывается. Два отдельных вызова с await такой гарантии не дают.

Сеанс отслеживает состояние базы данных, которое наблюдала последовательность запросов. Предоставленный маршрутизатор вызывает env.DB.withSession(...), начиная с first-primary, если у клиента нет закладки. Он возвращает результат getBookmark() в заголовке x-d1-bookmark. Последующий запрос может передать эту закладку, чтобы продолжить работу как минимум с тем же состоянием базы данных. Это последовательная согласованность, а не транзакция «всё или ничего» между HTTP-запросами.

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

cat > src/store.js <<'JS'
export async function closeTicket(session, id, eventId, note) {
  await session.batch([
    session.prepare("UPDATE tickets SET status = 'closed' WHERE id = ?").bind(id),
    session.prepare('INSERT INTO resolutions(event_id, ticket_id, note) VALUES (?, ?, ?)').bind(eventId, id, note)
  ]);
}
export async function readTicket(session, id) {
  const ticket = await session.prepare('SELECT id, subject, status FROM tickets WHERE id = ?').bind(id).first();
  if (!ticket) return null;
  const { results } = await session.prepare('SELECT event_id, note FROM resolutions WHERE ticket_id = ? ORDER BY event_id').bind(id).all();
  return { ...ticket, resolutions: results };
}
JS

Прочитайте src/index.js, чтобы найти вызов withSession, входящий заголовок с закладкой и возвращаемую закладку. Все операции с базой данных в рамках запроса используют этот сеанс. Закладка — это непрозрачная позиция: передавайте её обратно без изменений, не пытайтесь разобрать её или придумать собственное значение.

cat src/index.js
npx wrangler dev --ip 0.0.0.0 > dev.log 2>&1 &
cat dev.log

Дождитесь сообщения о локальном прослушиваемом адресе. Локальная имитация позволяет проверить откат пакета, но не демонстрирует настоящую удалённую репликацию или облачную закладку.

Наблюдайте откат перед успешным закрытием

На этом этапе вы намеренно отправите уже занятый ID события. Первая инструкция пакета попытается закрыть заявку 1, но вторая завершится ошибкой. Прочитайте результат после ошибки:

curl -i http://localhost:8787/tickets/1/close -H 'Content-Type: application/json' -d '{"event_id":1,"note":"Must roll back"}'
curl -i http://localhost:8787/tickets/1

Ожидайте 409 event_conflict, а затем заявку 1 со статусом open и пустым массивом resolutions. Одного ответа 409 недостаточно: следующий запрос на чтение доказывает, что частичное изменение не сохранилось.

Теперь используйте свободный ID события 2:

curl -i http://localhost:8787/tickets/1/close -H 'Content-Type: application/json' -d '{"event_id":2,"note":"Access restored"}'
curl -i http://localhost:8787/tickets/1

Ожидайте HTTP 200, статус closed у заявки 1 и событие решения 2 с текстом Access restored. Теперь обе записи согласованы. Не сбрасывайте локальные данные и не изменяйте удалённую базу, чтобы искусственно создать задержку репликации.

Продолжите работу с удалённым сеансом, используя его закладку

На этом этапе вы выполните тот же пакет в D1 и передадите настоящую закладку между запросами. Удалённая тестовая база по-прежнему находится в исходном состоянии.

npx wrangler deploy

Скопируйте фактический URL развёрнутого Worker. Сначала повторите неудачный пакет и подтвердите откат:

URL='YOUR_DEPLOYED_HTTPS_URL'
curl -i "$URL/tickets/1/close" -H 'Content-Type: application/json' -d '{"event_id":1,"note":"Must roll back"}'
curl -i "$URL/tickets/1"

Ожидайте 409, а затем открытую заявку без записей о решении. Если развёртывание ещё распространяется, повторяйте чтение в течение не более минуты; не принимайте страницу ошибки платформы за JSON-контракт приложения.

Отправьте запрос на успешное закрытие:

curl -i "$URL/tickets/1/close" -H 'Content-Type: application/json' -d '{"event_id":2,"note":"Access restored"}'

Ожидайте закрытую заявку и её запись о решении. Скопируйте непустой заголовок ответа x-d1-bookmark без лишних пробелов в следующую переменную:

BOOKMARK='YOUR_RESPONSE_BOOKMARK'
curl -i "$URL/tickets/1" -H "x-d1-bookmark: $BOOKMARK"

Последующий запрос должен увидеть закрытую заявку и событие решения 2. Закладка ограничивает давность состояния, которое может увидеть чтение; это не токен аутентификации. Этот сценарий не требует устаревшего чтения или включения репликации для чтения. Мы проверяем контракт сеанса, а не утверждаем, что произошла гонка реплик.

Откройте в Dashboard именно этот Worker и убедитесь, что его привязка DB указывает на базу данных этого запуска. Завершите функциональную проверку до очистки ресурсов.

Привязка Worker к D1

В этом примере привязка DB Worker указывает на его базу D1. Сгенерированный префикс ресурсов обозначает этот пример запуска; ваши имена будут другими. Снимок подтверждает только привязку. Проверки HTTP выше подтверждают атомарный откат, успешное обновление и продолжение чтения с закладкой.

Удалите временные ресурсы

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

npx wrangler delete

Убедитесь, что удаляется только имя Worker из конфигурации этого запуска.

npx wrangler d1 delete DB

Изучите запрос подтверждения и убедитесь, что удаляется только база данных этого запуска. Затем выведите список баз данных:

npx wrangler d1 list --json

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

Также остановите локальный процесс разработки. Выведите список заданий и завершите только запущенное вами задание wrangler dev (замените %1, если номер задания отличается):

jobs
kill %1

Завершите авторизацию этой виртуальной машины

На этом этапе завершите авторизацию только после успешной независимой проверки удаления. Выход удаляет сохранённые на этой виртуальной машине данные авторизации Wrangler; простого закрытия виртуальной машины недостаточно для очистки облачных ресурсов.

npx wrangler logout
npx wrangler whoami --json

Ожидайте loggedIn: false. Этот запрос без аутентификации может завершиться с ненулевым кодом; это ожидаемо только в том случае, если структурированный ответ явно сообщает, что вы вышли из системы. Завершите проверку, затем закройте окружение лабораторной работы.

Резюме

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