Doc gRPC Contracts - PlutoGr/TravelAgency GitHub Wiki

Межсервисные контракты (gRPC)

Сервисы вызывают друг друга напрямую по gRPC, минуя Gateway. Контракты лежат в src/Shared/TravelAgency.Contracts.Grpc/Protos/: identity.proto, catalog.proto, booking.proto, media.proto.

Простой пример

Клиент пишет в чат по заявке. Chat не знает, чья это заявка, поэтому спрашивает Booking:

Chat → booking-service:8081  BookingService/ValidateBookingAccess { booking_id: "…", user_id: "…" }
Booking → Chat               { has_access: true }

Если has_access = false, Chat отвечает клиенту 403 (REST) или ошибкой хаба (SignalR).

Как устроен вызов

  • gRPC-сервер каждого сервиса слушает отдельный порт 8081 (HTTP/2 без TLS, внутри сети Docker). REST работает на 8080.
  • Адреса задаются настройками GrpcClients__<Сервис>ServiceUrl, например http://catalog-service:8081.
  • Каждый вызов несёт метаданные x-internal-auth с общим сервисным токеном (GrpcSettings__InternalServiceToken, на сервере переменная GRPC_INTERNAL_SERVICE_TOKEN). Проверяет его GrpcAuthInterceptor из Shared.Infrastructure; без токена ответ Unauthenticated (код 16).
  • Через Gateway gRPC наружу не публикуется.
  • После каждого деплоя смоук-проверка шлёт по одному вызову без токена на каждый сервер и ждёт ответ HTTP/2 с кодом 16 (см. Где и как работает).

Кто кого вызывает

Вызывающий Сервер RPC Когда
Booking Catalog GetTourSnapshot создание заявки, добавление в избранное
Booking Catalog GetTourSnapshotForExistingBooking создание предложения по заявке
Booking Identity GetUserSummary карточка заявки для менеджера и админа; проверка роли в ValidateBookingAccess
Chat Booking ValidateBookingAccess чтение истории, вход в группу хаба, отправка сообщения
Catalog Media GetMediaFiles сохранение фото тура и публикация: владелец, размеры, ширина обложки
Catalog Media MarkMediaFilesPublic публикация тура и PUT images у уже опубликованного тура

identity.proto

Сервис IdentityGrpc.

RPC Запрос Ответ
GetUserSummary user_id user_id, email, first_name, last_name, role

catalog.proto

Сервис CatalogService.

RPC Запрос Ответ
GetTourSnapshot tour_id TourSnapshotResponse; found = true только для опубликованного тура
GetTourSnapshotForExistingBooking tour_id тот же ответ; found = true для опубликованного и снятого тура, для черновика false

TourSnapshotResponse: tour_id, title, description, price, currency, duration_days, snapshot_taken_at, found.

Цена в снимке: самая низкая цена предложения, действующего сегодня; если такого нет, самая низкая цена среди всех предложений тура. Если предложений нет, price = 0, currency = "USD".

booking.proto

Сервис BookingService.

RPC Запрос Ответ
ValidateBookingAccess booking_id, user_id has_access

Логика: доступ есть у клиента заявки; иначе Booking спрашивает роль у Identity и даёт доступ Manager и Admin. Неизвестная заявка или пользователь: false.

media.proto

Сервис MediaService.

RPC Запрос Ответ
GetMediaFiles ids[] files[]: id, owner_id, content_type, width, height, is_public, available_sizes (w200, w800, w1600), purpose (tour-image или general). Неизвестные и удалённые id пропускаются
MarkMediaFilesPublic ids[] пусто. Ставит IsPublic = true только у активных файлов с purpose = tour-image. Другое назначение, неизвестные и удалённые id не меняются. Флаг никогда не снимается, повтор безопасен

Совместимость

Поля в .proto не переименовываются и не переиспользуются по номерам: новые поля добавляются с новым номером, старые клиенты их игнорируют.

⚠️ **GitHub.com Fallback** ⚠️