feat: migrate to VitePress from monorepo docs, add test-contour section

This commit is contained in:
sova-bootstrap
2026-05-28 12:29:31 +03:00
parent e90dfe1bd4
commit e3e438df68
76 changed files with 11998 additions and 60 deletions
+113
View File
@@ -0,0 +1,113 @@
---
title: Расписание врача и кеш слотов (Backend)
---
# Сценарий 2.2: Расписание и «кеш» слотов
## Бизнес-цель
Пациенту показывают **свободные интервалы** записи к врачу в конкретном филиале и режиме (очный / онлайн). Чтобы не дёргать MIS на каждый запрос, backend хранит **недавно полученное расписание** и отдаёт его при повторных обращениях с теми же параметрами.
**Важно для архитектуры:** несмотря на название сервиса, **данные кеша живут в PostgreSQL** (таблица сущности `Schedule`), а не в Redis. Redis в этом сценарии **не используется** (см. `ScheduleCacheService`).
## Точки входа
| Тип | Метод + URL | Класс |
| --- | --- | --- |
| HTTP | `GET /specialist/schedule?...` | `SpecialistController::specialistSchedule` |
| CLI | `app:schedule:clear-cache` | `ClearScheduleCacheCommand` (очистка старых строк) |
Фактический вызов MIS выполняется внутри `GetScheduleMessageHandler` (см. [schedule-messenger.md](./schedule-messenger.md)).
## Параметры запроса расписания
Используется `App\Dto\ScheduleDto`:
- `st`, `en` — границы времени (целые);
- `dcode` — код врача;
- `filial` — филиал (в query строка строится как `filialId`);
- `onlineMode` — признак онлайн-расписания.
`ScheduleDto::toQueryString()` формирует строку для поиска в кеше и запроса к MIS (HTTP query).
## Пошаговый алгоритм HTTP
1. Контроллер наполняет DTO из query, валидирует.
2. `SpecialistService::getSchedule($dto)` диспатчит `GetScheduleMessage` (см. отдельную статью).
3. Хендлер сначала зовёт `ScheduleCacheService::getCachedSchedule($queryString, $isOnlineMode)`.
## Логика `ScheduleCacheService`
### TTL («как долго живёт кеш»)
Константа **`CACHE_TTL_MINUTES = 5`**.
`getCachedSchedule`:
- вычисляет порог `createdAt >= now - 5 minutes`;
- `ScheduleRepository::findByQueryModeAndTime($queryString, $isOnlineMode, $createdAfter)`;
- если строк нет — `null` (промах кеша).
То есть **инвалидация по времени**: записи старше 5 минут не считаются валидными для ответа.
### Запись и перезапись
`saveSchedule` перед вставкой новых слотов вызывает **`removeByQueryStringAndMode`**: удаляет все записи кеша с тем же `queryString` и `onlineMode`. Это **жёсткое обновление** среза расписания под ключ запроса.
Каждый слот — строка `Schedule` с полями врача, отделения, даты, интервала, `queryString`, `onlineMode`, `createdAt` и др.
### Ошибки чтения из БД
В `getCachedSchedule` перехват `Exception` → лог `Error reading from cache` → возврат `null` (как промах кеша, дальше пойдут в MIS).
## Инвалидация / уборка
| Механизм | Описание |
| --- | --- |
| По TTL при чтении | старше 5 минут не отдаются |
| `saveSchedule` | удаление предыдущих строк с тем же ключом перед insert |
| `app:schedule:clear-cache --hours=N` | массовое удаление по `createdAt < now - N hours` через `clearOldCache` |
| Опция `--stats` | статистика по таблице без удаления |
## `ScheduleErrorHandlerService`
Не часть кеша; вызывается из хендлера при ошибках HTTP-клиента или непойманных исключений: логирование и возврат массива с `status_code`, телом ответа MIS, длительностью и т.д. (см. [schedule-messenger.md](./schedule-messenger.md)).
## Mermaid
```mermaid
flowchart TD
A[GET /specialist/schedule] --> B[ScheduleDto + validate]
B --> C[SpecialistService.getSchedule]
C --> H[GetScheduleMessageHandler]
H --> D{Есть свежие Schedule строки?}
D -->|да ≤5 мин| R[reconstructFromDatabase → ответ cached]
D -->|нет| E[HTTP MIS intervals]
E --> S[saveSchedule: delete old + insert Schedule rows]
S --> R2[ответ api + _meta]
```
## Внешние зависимости
| Система | Роль |
| --- | --- |
| PostgreSQL | хранение «кеша» `Schedule` |
| Инфоклиника (MIS) | источник расписания при промахе |
| Redis | **не используется** в этом сценарии по текущему коду |
## Обработка ошибок и edge cases
- **БД недоступна при чтении кеша** — лог, ответ пойдёт в MIS; если и MIS недоступна — ошибка из хендлера.
- **Пустой ответ MIS** — `normalize` / пустые массивы зависят от клиента (см. `InfoclinicaClientService`).
- **Несогласованность `onlineMode` в DTO**: в контроллере в поле может попадать `0|1` из query — при строгой типизации возможны проблемы валидации (наблюдение для джуна при отладке).
## Ссылки на классы
- `apps/backend/src/Controller/SpecialistController.php` (`specialistSchedule`)
- `apps/backend/src/Dto/ScheduleDto.php`
- `apps/backend/src/Service/ScheduleCache/ScheduleCacheService.php`
- `apps/backend/src/Repository/ScheduleRepository.php`
- `apps/backend/src/Command/ClearScheduleCacheCommand.php`
- `apps/backend/src/Service/ErrorHandler/ScheduleErrorHandlerService.php`
Связанный сценарий Messenger: [schedule-messenger.md](./schedule-messenger.md).