ADR-UI-012. API-клиент и модели

ADR Версия: 1.0 accepted

UI ходит к бэку по REST. Контракт описан OpenAPI; клиент к нему генерируется, а не пишется руками. Но UI не должен зависеть от API-моделей напрямую — иначе любая правка контракта расходится по компонентам и шаблонам. Фиксируем: где живёт и как регистрируется сгенерированный клиент, как устроены UI-модели, где и как выполняется маппинг API↔UI, и что весь трафик идёт через клиент.

⟨/⟩ Исходник

1. Контекст и постановка задачи

Фронт обращается к бэку по REST. Контракт описан OpenAPI-спекой, и клиент к нему генерируется (swagger-codegen, src-ui/swagger-codegen/generate.py), а не пишется вручную. Прямая зависимость UI от сгенерированных API-моделей опасна: переименование поля, смена формата даты, вложенные DTO — и правка расползается по компонентам и шаблонам.

Нужно зафиксировать:

Контракт — наш (бэк в этом же репозитории), поэтому эндпоинты описываются так, чтобы генератор выдавал рабочие типизированные методы, а не подгоняются под ограничения инструмента задним числом.

2. Драйверы решения

  1. Изоляция UI от мутаций контракта. Правка API — точечное изменение маппинга, не сквозная замена по шаблонам.
  2. Минимум boilerplate. Слой изоляции не должен плодить файлы ради самого себя — маппинг простой и локальный.
  3. Прозрачность. Понятно, где тип, где маппинг, где запрос.
  4. Единый контракт ответа. Все ручки возвращают одну обёртку (ADR-SRV-001) — фронт разворачивает её единообразно.
  5. Совместимость с per-page паттерном (ADR-UI-013): запрос+маппинг+эффекты — в сервисе страницы.

3. Рассмотренные варианты

4. Решение

Выбран Вариант C.

4.1. Сгенерированный клиент

Чеклист после перегенерации: просмотреть api-clients/icd/api/api.ts (список сервисов) и добавить в providers app.config.ts каждый api-сервис, используемый в per-page сервисах. Пропуск даёт NG0201: No provider for _XxxService при первом обращении.

4.2. UI-модели

UI-модель — это то, что удобно показывать и редактировать. Типичные отличия от API-модели: денормализация (плоские lookup-карты вместо вложенных ссылок), удобные форматы (enum-код → подпись, ISO-строка → отображаемая дата), поля «только для UI». Покрывать все поля API-модели она не обязана — только используемые страницей. Пример — справочники ввода (modules/input/models/input-reference.ts), наполняемые из @api/icd маппером.

4.3. Маппинг API ↔ UI

4.4. Обёртка ответа

Все ручки возвращают единый контракт IcdBaseRequestResult (ADR-SRV-001): payload / isSuccess / errors[]. Per-page service разворачивает payload, проверяет isSuccess, а при ошибке показывает errors[].errorMessage — тексты формирует бэк по-русски, UI их не переводит (ADR-UI-015).

4.5. Весь трафик — через слой api-clients

Per-page service инжектирует сгенерированный api-сервис и зовёт его методы; он не строит URL/HttpClient/FormData сам. Это касается и загрузки файлов: так как контракт наш, эндпоинт описывается корректным multipart/form-data (файлы — бинарные поля), и генератор выдаёт типизированный метод (напр. apiInputPacketPostForm(payload, files)), внутри которого сам собирает FormData. Корректность multipart-описания на стороне бэка обеспечивается отдельно (см. связанный backend-артефакт).

4.6. Загрузка данных

4.7. Чего не делаем

5. Положительные следствия

6. Отрицательные следствия и компромиссы

7. Проверка

8. Открытые вопросы / отложено

Связанные артефакты

Документы