Go: Один requestid для вызова и выяснения результата — задача с проверкой

Практическая задача на Go по теме «Один request_id для вызова и выяснения результата». Условие содержит контракт функций, предоставленные типы и требования к результату. Используйте выданный шаблон и сохраняйте его публичные объявления. Перед проверкой разберите успешный путь, обработку ошибок и отмену операции, когда она предусмотрена контрактом. Здесь оценивается конкретный компонент сервиса; в полном курсе микросервисов на Go эти компоненты связываются с HTTP, PostgreSQL, gRPC и Kafka.

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

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

Предоставленный тип:

type LabelOperation struct {
    RequestID string // идентификатор намерения, создан и сохранён caller-ом до первого POST
    ParcelID  string // посылка, для которой создаётся этикетка
}

Функции для реализации:

func BuildLabelCreate(ctx context.Context, baseURL string, op LabelOperation) (*http.Request, error)
func BuildLabelLookup(ctx context.Context, baseURL string, op LabelOperation) (*http.Request, error)

Дополните TODO в редакторе и отправьте весь код, сохранив package main, импорты, предоставленные типы и публичную функцию RunScenario. Не добавляйте свою функцию main: JSON-адаптер запуска платформа добавит сама. Скрытые входы и ожидаемые результаты находятся на стороне проверки.

BuildLabelCreate собирает POST-запрос:

  • Метод: POST
  • URL: baseURL + "/labels"
  • Заголовок: Content-Type: application/json
  • Тело: один JSON-объект ровно с двумя строковыми полями — request_id равно op.RequestID и parcel_id равно op.ParcelID. Дополнительных полей нет. Порядок полей и пробелы не оцениваются.

BuildLabelLookup собирает GET-запрос:

  • Метод: GET
  • URL: baseURL + "/label-requests/" + op.RequestID
  • Тело отсутствует.

Оба запроса должны содержать переданный ctx без замены. Вызывающий код может передать разные контексты для POST и для GET. Уже отменённый контекст остаётся отменённым в запросе — подменять его на context.Background() нельзя.

Почему один и тот же RequestID для POST и GET:
После timeout меняющего POST клиент не может по одной ошибке определить, был ли запрос принят сервисом. Lookup с тем же op.RequestID читает снимок состояния того же намерения. Если сервис вернул 200 state=created — операция была завершена (ответ на POST просто не дошёл). Если 200 state=processing — принята, ещё выполняется. Если 404 — записи нет на момент чтения, но это не доказывает, что POST никогда не был принят: он мог ещё быть в пути. Функции сборки запросов обеспечивают корреляцию по идентификатору; серверная устойчивость — отдельная тема.

Гарантированный вход (проверка не требуется):

  • ctx не равен nil
  • baseURL — корректный HTTP origin без пути, завершающего /, query, fragment и userinfo
  • op.RequestID и op.ParcelID непустые, состоят только из символов [A-Za-z0-9_-]

Для гарантированно корректного входа каждая функция должна вернуть ненулевой *http.Request и nil в качестве ошибки. Это требование действует и при уже отменённом переданном контексте: запрос должен быть создан с этим контекстом.

Ограничения реализации:
Функции только собирают запросы. Не выполняйте HTTP-вызов, retry или автоматический lookup. Не генерируйте новые идентификаторы. Не меняйте op. Не используйте изменяемое глобальное состояние. Не декодируйте ответы. Не подменяйте предоставленный контекст. Не печатайте ничего в stdout.

Доступные пакеты: bytes, context, encoding/json, fmt, io, net/http, net/http/httptest.

Вопрос: Клиент сохранил операцию с RequestID = "req-55", отправил POST /labels и получил транспортную ошибку без HTTP-ответа. Затем клиент выполнил GET /label-requests/req-55 и получил 404. Можно ли из одного этого 404 сделать вывод, что POST никогда не был принят сервисом, и немедленно отправить второй POST с новым идентификатором? Обоснуйте.

РешениеGo

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Связанные задачи