---
title: Асинхронное сообщение GetScheduleMessage (Backend)
---

# Сценарий 2.3: `GetScheduleMessage` и `GetScheduleMessageHandler`

## Бизнес-цель

Получение расписания спроектировано через **Symfony Messenger**, чтобы:

- отделить HTTP-слой от интеграции с MIS и работы с кешем;
- унифицировать вызовы (тот же message может диспатчиться из других мест);
- заложить возможность смены транспорта с `sync` на очередь без переписывания контроллера.

По факту конфигурации в `config/packages/messenger.yaml` маршрут для `App\Message\GetScheduleMessage` указывает на транспорт **`sync://`**, то есть обработка **синхронная в том же PHP-процессе**, а не отложенная очередь.

## Точки входа

| Тип | Где создаётся сообщение | Класс |
| --- | --- | --- |
| HTTP | `GET /specialist/schedule` → `SpecialistService::getSchedule` | `App\Message\GetScheduleMessage` |
| Messenger | обработчик | `App\MessageHandler\GetScheduleMessageHandler` |

CLI для этого сообщения отдельно не зарегистрирован в коде репозитория.

## Пошаговый алгоритм

1. `SpecialistService::getSchedule` создаёт `new GetScheduleMessage($dto->toQueryString(), $dto->onlineMode)` и диспатчит через `MessageBusInterface`.
2. `GetScheduleMessageHandler::__invoke`:
   - стартует `PerformanceTrackerService`;
   - **кеш-hit**: `ScheduleCacheService::getCachedSchedule` — при успехе возвращает данные + `_meta.source = cached`;
   - **кеш-miss**: `InfoclinicaClientService::getSchedule($queryString, $isOnlineMode)` (второй аргумент — в коде хендлера; фактическая сигнатура клиента может отличаться — см. раздел Edge cases);
   - при успешном ответе MIS — `ScheduleCacheService::saveSchedule` и ответ с `_meta.source = api`;
   - при `HttpExceptionInterface` — `ScheduleErrorHandlerService::handleHttpException` возвращает массив диагностики;
   - при прочих исключениях — `handleGeneralException`.

## Mermaid

```mermaid
sequenceDiagram
    participant SC as SpecialistController
    participant SS as SpecialistService
    participant BUS as MessageBus
    participant H as GetScheduleMessageHandler
    participant CACHE as ScheduleCacheService
    participant MIS as InfoclinicaClientService
    participant ERR as ScheduleErrorHandlerService

    SC->>SS: getSchedule(ScheduleDto)
    SS->>BUS: dispatch(GetScheduleMessage)
    BUS->>H: __invoke
    H->>CACHE: getCachedSchedule
    alt hit
        CACHE-->>H: данные слотов
    else miss
        H->>MIS: getSchedule (HTTP)
        alt MIS OK
            MIS-->>H: JSON нормализован
            H->>CACHE: saveSchedule
        else HTTP error
            MIS-->>ERR: HttpException
            ERR-->>H: error payload
        end
    end
    H-->>SS: результат массива
    SS-->>SC: JsonResponse
```

## Внешние зависимости

| Система | Роль |
| --- | --- |
| PostgreSQL (`Schedule`) | кеш слотов |
| Инфоклиника | `GET /api/reservation/intervals?{query}` (см. `InfoclinicaClientService`) |
| Логирование | канал `infoclinica-cache`, `infoclinica-error` (через `withName`) |

## Обработка ошибок и edge cases

- **Ошибка HTTP MIS** — не исключение наружу; возвращается массив с `status_code`, `response` и др. Контроллер отдаёт его как JSON `200` с этим телом (поведение «мягкой ошибки» — важно для фронта).
- **Рассинхрон сигнатур**: в `GetScheduleMessageHandler` вызов `getSchedule` с двумя аргументами должен соответствовать PHP-интерфейсу клиента; при несоответствии будет `ArgumentCountError` на старте (сигнал провести рефакторинг интерфейса `InfoclinicaClientServiceInterface` и реализации).
- **sync-транспорт**: нет повторного выполнения из failed queue для этого message в штатной конфигурации (в отличие от реальной async-очереди).

## Ссылки на классы

- `apps/backend/src/Message/GetScheduleMessage.php`
- `apps/backend/src/MessageHandler/GetScheduleMessageHandler.php`
- `apps/backend/src/Service/Specialist/SpecialistService.php`
- `apps/backend/config/packages/messenger.yaml` (`routing` для `GetScheduleMessage`)

Дополнительно: кеш в БД — [schedule-cache.md](./schedule-cache.md).
