Files
docs/infrastructure/backend-external-services.md
T

23 KiB
Raw Blame History

title
title
Backend — внешние сервисы и стратегия для test/stage/prod

Backend: внешние сервисы, БД и интеграции

Разбор всех сторонних зависимостей apps/backend: что они делают, где используются в коде, и что делать в тестовом контуре (K8s) — поднять свой инстанс, поставить заглушку или эмулятор.

Связанные документы: K8s + Terraform + ArgoCD + Gitea, локальный контур (local/, см. корень репозитория), бизнес-сценарии.


0. Сводная матрица решений (test-контур)

# Зависимость Тип Где в коде Test-контур Stage Prod
1 PostgreSQL (основная) БД DATABASE_URL, Doctrine ORM Новый инстанс (1×) 1× на stage-сервере HA: primary + sync replica (§7.6 плана)
2 PostgreSQL (cabinet) БД DATABASE_CABINET_URL, UsrlogController Новый инстанс + seed Отдельный Prod
3 MySQL (Bitrix CMS) БД DATABASE_BITRIX_URL, BitrixService, SQL views Новый инстанс + seed из local/mysql-bitrix Отдельный Prod Bitrix
4 Redis Кеш/сессии REDIS_URL Новый инстанс в K8s Отдельный Prod Redis
5 MIS / Инфоклиника (HTTP) API MIS_URL, InfoclinicaClientService Mock-сервис (WireMock / свой stub) Test MIS или read-only prod Prod MIS
6 Widget API (widget.sovamed.ru) API Hardcoded в Upload*Command Mock или MIS_URL после рефакторинга Stage widget Prod
7 Bitrix site (HTTP, картинки) API BITRIX_URL, BitrixClientService Mock (static files) или seed URLs Stage Prod
8 SMS (sms.ru, sms4b) API SMSRU_*, SMS4B_* Заглушка (noop) Заглушка Реальный провайдер
9 Почта API MAILER_DSN, SendMailService Mailpit / null://null Mailpit или sandbox SMTP Prod SMTP
10 Yandex SmartCaptcha API SMARTCAPTCHA_*, ServiceController Заглушка (always OK) Заглушка Реальный ключ
11 Calltouch API CT_*, CalltouchController Заглушка (noop, уже частично) Заглушка Реальный API
12 Bitrix24 BITRIX24_URL в .env Не используется в коде — игнор При появлении интеграции
13 JWT / AES / Lock Локально env + файлы ключей Свои ключи test Свои Prod secrets
14 Symfony Messenger Очередь MESSENGER_TRANSPORT_DSN doctrine:// или sync:// doctrine/redis prod queue
flowchart TB
  subgraph backend [backend pod]
    API[Symfony API]
    CMD[Console / CronJobs]
  end

  subgraph own_test [Поднимаем в test — свои инстансы]
    PG[(PostgreSQL main)]
    PGC[(PostgreSQL cabinet)]
    MY[(MySQL Bitrix)]
    RD[(Redis)]
  end

  subgraph stubs [Заглушки в test]
    MIS_M[Mock MIS / Widget API]
    SMS_M[Noop SMS]
    MAIL_M[Mailpit]
    CAP_M[Mock SmartCaptcha]
    CT_M[Noop Calltouch]
    BTX_M[Mock Bitrix HTTP images]
  end

  subgraph prod_only [Только prod / stage по решению]
    MIS_P[Реальная Инфоклиника]
    SMS_P[sms.ru / sms4b]
    CAP_P[Yandex SmartCaptcha]
    CT_P[Calltouch]
  end

  API --> PG
  API --> PGC
  API --> MY
  API --> RD
  API --> MIS_M
  API --> SMS_M
  API --> MAIL_M
  API --> CAP_M
  API --> CT_M
  API --> BTX_M
  CMD --> PG
  CMD --> MY
  CMD --> MIS_M

  MIS_P -.->|stage/prod| API
  SMS_P -.->|prod| API

1. Базы данных (с БД — новый инстанс)

1.1. PostgreSQL — основная (DATABASE_URL)

Назначение: все Doctrine-сущности backend (User, Specialist, News, Schedule, …).

Где используется:

  • config/packages/doctrine.yaml → connection default
  • все Repository, миграции apps/backend/migrations
  • кеш расписания (Schedule в таблице, не Redis)
  • Symfony Messenger при MESSENGER_TRANSPORT_DSN=doctrine://default

Test: Helm Bitnami PostgreSQL / CNPG instances: 1 в sova-data-test.

Stage: один PG на отдельном stage-сервере.

Prod: Patroni (2 db-VM) + witness etcd на отдельной VM (не prod-app). Подключение: DATABASE_URL → PgBouncer :6432 (transaction, API); migrate Job и Messenger → :5432 (session). §7.6 плана.

Stage/Prod: отдельные credentials; никогда не указывать prod URL в test.


1.2. PostgreSQL — cabinet (DATABASE_CABINET_URL)

Назначение: read-only доступ к legacy-таблицам cabinet (не Doctrine entities backend).

Где используется:

  • config/packages/doctrine.yaml → connection cabinet
  • UsrlogControllerSELECT из public.usrlog через doctrine.dbal.cabinet_connection

Test: отдельный PostgreSQL (можно второй database на том же chart), seed из local/postgres/init/03-cabinet-schema-and-seed.sql.

Если usrlog не нужен в test: connection можно оставить на пустую БД; эндпоинт /usrlog/list вернёт пустой список.


1.3. MySQL — Bitrix CMS (DATABASE_BITRIX_URL)

Назначение: чтение таблиц Bitrix (b_iblock_*, b_file) для синхронизации контента и отзывов.

Где используется:

Компонент Как
BitrixService прямые SQL через doctrine.dbal.mysql_connection
BitrixUpdateReviewsCommand getReviews() — отзывы по specialist.id
BitrixUpdateDoctorsCommand нормализация dcodes (Bitrix HTTP закомментирован)
*CrudService::syncFromView* INSERT … SELECT FROM public.view_*
Миграции views view_article, view_news, … с JOIN на b_* (через mysql_fdw на prod или упрощённые views в test)

Test: новый MySQL в K8s (Bitnami MySQL chart), init SQL из local/mysql-bitrix/init/ в корне репозитория. Дополнительно — создать упрощённые view_news, view_promo, … или наполнять таблицы news/promo через CRUD API без sync-команд.

Важно: на prod PG использует mysql_fdw (infrastructure/pgsql + extensions). В test можно:

  • Вариант A (рекомендуется): mysql_fdw в test-PG → test-MySQL (повторяет prod);
  • Вариант B: отключить sync-команды в test CronJobs, контент только через adminPanel CRUD;
  • Вариант C: materialized views только на PG без FDW (дублировать seed в PG).

1.4. Redis (REDIS_URL)

Назначение: Symfony cache, sessions (prod php.ini), потенциально Messenger.

Test: Bitnami Redis в sova-data-test, свой пароль в SealedSecret.


2. HTTP-интеграции

2.1. MIS / Инфоклиника (MIS_URLInfoclinicaClientService)

Prod URL (пример): https://widget.sovamed.ru

Эндпоинты клиента:

Метод Путь Сценарий
GET /api/reservation/intervals?{query} Расписание врача
GET /filials/list Список филиалов (через client)
POST /api/reservation/anonymous-reserve Анонимная запись

Где вызывается:

  • GetScheduleMessageHandlergetSchedule()
  • GetAnonymousReserveRequestMessageHandleranonymousReserve()
  • UploadDoctorsCommand, UploadDepartmentsCommand$client->request('GET', …) через InfoclinicaClientServiceInterface

Test — рекомендация: Mock-сервис в K8s

Развернуть WireMock или лёгкий Node/Go stub в namespace sova-mocks:

MIS_URL=http://mis-mock.sova-mocks.svc.cluster.local

Stub должен отдавать JSON в формате, который ждёт InfoclinicaClientService::normalizeSchedule() (см. schedule-cache.md).

Альтернативы:

Вариант Когда
WireMock + JSON fixtures Полный контроль, воспроизводимые тесты
Отдельный «test MIS» у вендора Если Infoclinica даёт sandbox (уточнить у владельца интеграции)
Read-only прокси на prod Не рекомендуется — риск записи/нагрузки

Stage: sandbox MIS или изолированный контур клиники.


2.2. Widget API (hardcoded https://widget.sovamed.ru)

Проблема: часть команд не читает MIS_URL, а хардкодит URL:

Команда Путь
UploadFilialsCommand GET /filials/list
UploadPriceCommand GET /pricelist/list
UploadPriceDepCommand GET /pricelist/departments

Test:

  1. Краткосрочно: тот же mock-сервис, что для MIS, + рефакторинг команд — вынести base_uri в env WIDGET_API_URL или переиспользовать MIS_URL.
  2. До рефакторинга: mock должен слушать на URL, который прописан в коде (или патч через /etc/hosts + Ingress) — технический долг.

2.3. Bitrix site HTTP (BITRIX_URLBitrixClientService)

Prod URL (пример): https://sovamed.ru

Назначение: скачивание файлов врачей с путей вида /upload/iblock/....

Где используется:

  • GetSpecialistPictureMessageHandlergetSpecialistImage($path)

Test:

Вариант Описание
Mock HTTP nginx/static с несколькими .jpg в /upload/iblock/
Отключить handler Если в test seed картинки уже локальные (specialist/xxx.jpg)
Прокси на stage Bitrix Только read, без записи

BITRIX_URL=http://bitrix-mock.sova-mocks.svc.cluster.local


2.4. SMS — sms.ru и sms4b (SMSRU_*, SMS4B_*)

Клиенты: SmsruClientService, Sms4bClientService (реализуют SmsClientServiceInterface).

Статус в коде: сервисы зарегистрированы в config/services.yaml, но ни один контроллер/команда их пока не inject'ит. Сущности Record / AlertSms есть, логика отправки SMS не подключена к HTTP-сценариям.

Test (по вашему решению): заглушка

# Symfony: test env — noop implementation
App\Service\Client\Interfaces\SmsClientServiceInterface:
  class: App\Service\Client\Stub\NoopSmsClientService

NoopSmsClientService пишет в log «SMS suppressed» и возвращает { "status": "ok", "stub": true }.

Prod: реальные SMSRU_URL / SMS4B_URL и токены из SealedSecret.

Stage: заглушка или отдельный «sandbox»-аккаунт провайдера с whitelist номеров.


2.5. Почта (MAILER_DSN, SendMailService)

Где используется:

  • ServiceController::sendmail — отправка по query-параметрам, защита MAILER_ACCESS_TOKEN
  • Symfony MailerInterfaceSendMailService

Prod .env: MAILER_DSN=null://null (письма фактически не уходят, но endpoint есть).

Test — рекомендация:

DSN Зачем
smtp://mailpit.sova-mocks:1025 UI на :8025, видно письма в браузере
null://null Если endpoint /service/sendmail не тестируете

Безопасность Mailpit Web UI:

Mailpit UI (:8025) не выставлять в интернет без защиты. В тестовых письмах могут быть ФИО, mock-пароли, токены сброса (иногда валидные и на stage).

# Ingress mailpit.test.sova.dev — только Basic Auth
metadata:
  annotations:
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: mailpit-basic-auth
    nginx.ingress.kubernetes.io/auth-realm: "Mailpit test only"

Альтернатива: UI только cluster-internal (kubectl port-forward), SMTP :1025 — из sova-test по Cluster DNS без Ingress.

Stage: Mailpit или корпоративный sandbox SMTP.

Prod: реальный SMTP / API (SendGrid, Yandex 360, …).


2.6. Yandex SmartCaptcha (SMARTCAPTCHA_URL, SMARTCAPTCHA_KEY)

Где используется:

  • ServiceController::smartCaptchaPOST /smart-captcha
  • AnonymousReserveRequestDto::$captcha — поле обязательно при записи (валидация на backend)

Test — заглушка:

Mock возвращает { "status": "ok", "message": "" } для любого token (как Yandex при успехе).

Или Symfony binding:

# when@test
App\Service\Client\Interfaces\SmartCaptchaClientServiceInterface:
  class: App\Service\Client\Stub\AlwaysValidSmartCaptchaClientService

Prod: реальный https://smartcaptcha.yandexcloud.net + prod key.

Связь с MIS mock: при anonymous-reserve captcha проверяется на backend до вызова MIS; mock captcha достаточен для E2E test.


2.7. Calltouch (CT_URL, CT_PARAMS)

Где используется:

  • CalltouchController::createLead — вызов CalltouchClientService::requestCreate закомментирован
  • Клиент готов: POST /lead-service/v1/api/request/create

Test — заглушка:

  • Оставить как сейчас (echo request) или
  • NoopCalltouchClientService возвращает { "data": { "leadId": "test-123" } }

Prod/Stage (маркетинг): реальные CT_URL + region tokens из CT_PARAMS.


2.8. Bitrix24 (BITRIX24_URL)

Статус: переменная есть в .env, использований в PHP-коде backend не найдено. Можно не задавать в test до появления интеграции.


3. Локальная инфраструктура (не внешние SaaS)

Переменная Назначение Test
JWT_* Lexik JWT auth Сгенерировать отдельную пару ключей для test
AES_SECRET_KEY AESCryptService (сущность Record) Случайный ключ 32 байта в Secret
LOCK_DSN Symfony Lock flock:// или postgresql://… advisory lock
MESSENGER_TRANSPORT_DSN Очередь doctrine://default (таблица messenger_messages)
CORS_ALLOW_ORIGIN adminPanel origins https://admin.test.sova.dev
API_BASE_URL Self-reference / XML feeds https://api.test.sova.dev
MAILER_ACCESS_TOKEN Защита /service/sendmail Случайный UUID в Secret

4. Эталон: что уже сделано локально (local/.env.local)

В apps/backend/.env.local для sova-local уже задан паттерн mock URL:

MIS_URL=http://mock-mis.local
BITRIX_URL=http://mock-bitrix.local
SMS4B_URL=http://mock-sms4b.local
MAILER_DSN=null://null
SMARTCAPTCHA_URL=http://mock-smartcaptcha.local

Для K8s test нужно не просто «мёртвые» hostname, а реально поднятые mock-поды + DNS внутри кластера (Service names).


5. Архитектура mock-слоя в Kubernetes (test)

flowchart LR
  subgraph sova_test [namespace sova-test]
    BE[backend]
  end

  subgraph sova_mocks [namespace sova-mocks]
    MIS[mis-mock :8080]
    BTX[bitrix-http-mock :8080]
    CAP[captcha-mock :8080]
    CT[calltouch-mock :8080]
    MP[mailpit :8025]
  end

  subgraph sova_data [namespace sova-data-test]
    PG[(PostgreSQL)]
    MY[(MySQL Bitrix)]
    RD[(Redis)]
  end

  BE --> PG
  BE --> MY
  BE --> RD
  BE --> MIS
  BE --> BTX
  BE --> CAP
  BE --> CT
  BE --> MP

5.1. Рекомендуемые Helm releases (test)

Release Chart / образ Назначение
mis-mock WireMock / custom расписание, запись, filials, pricelist
bitrix-http-mock nginx + static /upload/iblock/*.jpg
captcha-mock tiny HTTP server POST /validate → ok
calltouch-mock WireMock lead create
mailpit axllent/mailpit SMTP + Web UI (Basic Auth на Ingress)
postgresql-test bitnami/postgresql main + cabinet DBs
mysql-bitrix-test bitnami/mysql Bitrix tables seed
redis-test bitnami/redis cache

Namespace sova-mocks общий для всех контуров test (не prod).

5.2. Env backend test (фрагмент SealedSecret)

DATABASE_URL: postgresql://sova_test:***@postgresql-test.sova-data-test:5432/sova_backend_test
DATABASE_CABINET_URL: postgresql://sova_test:***@postgresql-test.sova-data-test:5432/sova_cabinet_test
DATABASE_BITRIX_URL: mysql://bitrix_test:***@mysql-bitrix-test.sova-data-test:3306/sova_bitrix_test
REDIS_URL: redis://:***@redis-test.sova-data-test:6379/0
MIS_URL: http://mis-mock.sova-mocks.svc.cluster.local:8080
BITRIX_URL: http://bitrix-http-mock.sova-mocks.svc.cluster.local:8080
SMARTCAPTCHA_URL: http://captcha-mock.sova-mocks.svc.cluster.local:8080
SMARTCAPTCHA_KEY: test-key-not-used-by-mock
SMSRU_URL: http://noop.invalid
SMS4B_URL: http://noop.invalid
CT_URL: http://calltouch-mock.sova-mocks.svc.cluster.local:8080
MAILER_DSN: smtp://mailpit.sova-mocks.svc.cluster.local:1025
MAILER_ACCESS_TOKEN: "<uuid>"
CORS_ALLOW_ORIGIN: https://admin.test.sova.dev

При внедрении NoopSmsClientService URL sms можно не резолвить.


6. CronJobs и внешние вызовы

CronJob (из scripts/cron.*) Внешние зависимости Test
upload:deps, upload:doctors MIS HTTP mock MIS или отключить
upload:filials, upload:price* widget HTTP (hardcoded) mock + рефакторинг URL
bitrix-update-reviews MySQL Bitrix test MySQL + seed
bitrix-update-doctors только PG OK
upload:news, upload:promo, … PG views → MySQL test MySQL + views или CRUD only
ClearScheduleCacheCommand только PG OK

Рекомендация для первого выката test: включить CronJobs только для очистки кеша расписания; sync-команды — после готовности mock MIS и Bitrix MySQL.

concurrencyPolicy в K8s:

Для всех sync CronJobs обязательно:

spec:
  concurrencyPolicy: Forbid      # не запускать вторую копию, пока первая не завершилась
  jobTemplate:
    spec:
      activeDeadlineSeconds: 3600

Без Forbid при зависшей БД bitrix-update-reviews через час стартует дубликат — параллельные job'ы начнут мешать друг другу.


7. Stage vs Prod — отличия от test

Сервис Test Stage Prod
PostgreSQL / Redis / Bitrix MySQL Изолированные test DB Изолированные stage DB Production DB
MIS Mock Sandbox MIS или mock Production MIS
SMS Noop Noop или sandbox Live
Mail Mailpit Sandbox SMTP Live
SmartCaptcha Mock Mock Live
Calltouch Noop Noop Live
Bitrix HTTP (images) Mock Stage Bitrix site Prod

8. Технический долг (сделать до/во время миграции)

  1. Hardcoded widget.sovamed.ru в UploadFilialsCommand, UploadPriceCommand, UploadPriceDepCommand → env WIDGET_API_URL или MIS_URL.
  2. Hardcoded https://api.sovamed.ru в XmlFeedGenerator* → env API_PUBLIC_URL.
  3. Реализовать или явно отключить SMS-клиенты (NoopSmsClientService + bind по env).
  4. Calltouch — раскомментировать с env-флагом CALLTOUCH_ENABLED=false в test.
  5. BITRIX24_URL — удалить из .env или документировать при появлении кода.
  6. Mock-сервисы — вынести JSON fixtures в репозиторий sova-mocks (рядом с sova-deploy).

9. Чек-лист готовности интеграций test-контура

  • PostgreSQL test: миграции backend применены
  • MySQL Bitrix test: seed + (опционально) mysql_fdw views
  • Redis test: ping из backend pod
  • MIS mock: GET /api/reservation/intervals возвращает валидный JSON
  • MIS mock: POST /api/reservation/anonymous-reserve → 200
  • Captcha mock: POST /validate → ok
  • Mailpit: письмо из /service/sendmail видно в UI (Basic Auth или port-forward)
  • SMS: noop binding, приложение стартует без sms.ru
  • Calltouch: lead endpoint не бьёт prod
  • CORS: admin.test.sova.dev → api.test.sova.dev
  • JWT keys: login работает
  • CronJobs sync: concurrencyPolicy: Forbid
  • CronJobs: только безопасные включены на первом выкате

10. Связанные файлы в репозитории

Путь Содержимое
apps/backend/.env prod-like переменные (образец)
apps/backend/.env.local local mock URLs
apps/backend/config/services.yaml DI клиентов
apps/backend/config/packages/doctrine.yaml 3 DB connection
apps/backend/src/Service/Client/* HTTP-клиенты
apps/backend/src/Service/Bitrix/BitrixService.php MySQL Bitrix
local/mysql-bitrix/init/ seed Bitrix tables
local/postgres/init/ seed PG backend + cabinet
monitoring/prometheus/prometheus.yml scrape Bitrix server 192.168.2.11 (prod-only)

Версия 1.0. При изменении интеграций обновлять матрицу в §0 и env-фрагмент в §5.2.