930df5b888
Springdoc-openapi генерирует OpenAPI 3 spec из аннотаций контроллеров — интеграторам не нужно поддерживать markdown-ТЗ синхронно с кодом. - /swagger-ui.html (UI), /v3/api-docs (JSON), /v3/api-docs.yaml. - OpenApiConfig: title/description/contact/servers (staging+prod+local), bearerAuth security scheme (Keycloak JWT). - SecurityConfig: permitAll на swagger paths. Документация: - docs/integration/altum-imaging-order.md — ТЗ интеграции «Заказ съёмки». Согласованы: m2m service account через Keycloak, BFF в altum-backend (FastAPI), 4 справочника со схемами, cascading select через instrument.supported_*_codes, snapshot strategy для аудита. - docs/tech/api-integration.md — общий гайд для интеграторов: auth flow, endpoints, кэш-стратегии, bitemporal модель, current dictionaries, cross-refs, best practices. Скрипты codegen openapi-generator-cli работают off /v3/api-docs.
244 lines
11 KiB
Markdown
244 lines
11 KiB
Markdown
# Ordinis API — гайд для интеграторов
|
||
|
||
Документ для команд которые хотят читать/писать справочники Ordinis из своих
|
||
сервисов (Альтум, геопортал, BI и т.д.).
|
||
|
||
**Owner:** Ordinis team — zimin.an@nstart.space.
|
||
|
||
## 1. Где живёт сервис
|
||
|
||
| Среда | URL | Auth |
|
||
|---------|--------------------------------------------------|-------------------------------------------|
|
||
| staging | `https://ordinis.k8s.265.nstart.cloud` | пока выключена (`ORDINIS_AUTH_REQUIRED=false`) |
|
||
| prod | `https://ordinis.k8s.264.nstart.cloud` | будет JWT (после prod stand-up) |
|
||
| local | `http://localhost:8080` | выключена |
|
||
|
||
## 2. Swagger UI / OpenAPI spec
|
||
|
||
- **Swagger UI:** `https://ordinis.k8s.265.nstart.cloud/swagger-ui.html`
|
||
- **Spec JSON:** `https://ordinis.k8s.265.nstart.cloud/v3/api-docs`
|
||
- **Spec YAML:** `https://ordinis.k8s.265.nstart.cloud/v3/api-docs.yaml`
|
||
|
||
Из spec можно генерировать клиент для Python (openapi-generator-cli),
|
||
TypeScript, Go и т.д. Пример:
|
||
|
||
```bash
|
||
openapi-generator-cli generate \
|
||
-i https://ordinis.k8s.265.nstart.cloud/v3/api-docs \
|
||
-g python \
|
||
-o ./altum-backend/clients/ordinis
|
||
```
|
||
|
||
## 3. Авторизация
|
||
|
||
Ordinis принимает JWT от Keycloak realm `nstart`:
|
||
|
||
- Issuer: `https://auth.nstart.space/realms/nstart`
|
||
- Алгоритм: RS256
|
||
- JWK Set discoverится через `/.well-known/openid-configuration`.
|
||
|
||
### Service account (m2m)
|
||
|
||
Для интеграции backend↔backend (например altum-backend → ordinis read-api)
|
||
получить service account клиент в Keycloak:
|
||
|
||
1. Запросить у Ordinis team создание клиента в realm `nstart`.
|
||
2. Назначить роль `ordinis-public` (или выше, если нужен RESTRICTED scope).
|
||
3. Получить `client_id` + `client_secret`.
|
||
|
||
**Получение access token (client_credentials):**
|
||
|
||
```bash
|
||
curl -X POST https://auth.nstart.space/realms/nstart/protocol/openid-connect/token \
|
||
-d "grant_type=client_credentials" \
|
||
-d "client_id=altum-backend" \
|
||
-d "client_secret=<SECRET>"
|
||
```
|
||
|
||
Ответ:
|
||
|
||
```json
|
||
{ "access_token": "eyJhbGciOi...", "expires_in": 300, "token_type": "Bearer" }
|
||
```
|
||
|
||
Кэшировать токен с TTL = `expires_in - 30` (буфер на retries) в backend, не
|
||
держать в фронте.
|
||
|
||
### Маппинг ролей в DataScope
|
||
|
||
| Keycloak роль | Ordinis DataScope |
|
||
|-------------------------------------------------|-------------------|
|
||
| `ordinis-public` / `public` | PUBLIC |
|
||
| `ordinis-internal` / `internal` | PUBLIC + INTERNAL |
|
||
| `ordinis-restricted` / `restricted` | + RESTRICTED |
|
||
|
||
Любая роль с суффиксом `-PUBLIC|INTERNAL|RESTRICTED` тоже работает (см.
|
||
`ordinis-auth/.../ScopeContext.java`). Это сделано чтобы не плодить orgaroles
|
||
для каждого consumer'а.
|
||
|
||
## 4. Read-side endpoints
|
||
|
||
### `GET /api/v1/dictionaries`
|
||
|
||
Список доступных справочников.
|
||
|
||
```bash
|
||
curl -H "Authorization: Bearer $TOKEN" \
|
||
https://ordinis.k8s.265.nstart.cloud/api/v1/dictionaries
|
||
```
|
||
|
||
```json
|
||
[
|
||
{ "name": "spacecraft", "displayName": "Космические аппараты", "scope": "PUBLIC", "schemaVersion": "1.0.0" },
|
||
{ "name": "mission", "displayName": "Миссии / программы", "scope": "PUBLIC", "schemaVersion": "1.0.0" },
|
||
...
|
||
]
|
||
```
|
||
|
||
### `GET /api/v1/dictionaries/{name}/records`
|
||
|
||
Активные записи на момент `at` (default: `now`). Параметры:
|
||
|
||
| параметр | тип | пример | примечание |
|
||
|----------|-----------|------------------------------|------------------------------------------------|
|
||
| `lang` | string | `ru`, `en` | flatten i18n полей до строки на этом языке |
|
||
| `at` | ISO 8601 | `2026-05-04T12:00:00+03:00` | bitemporal query — вернёт активные на момент |
|
||
| `as_scope` | string | `PUBLIC,INTERNAL` | для anonymous + `allowQueryScope=true` (legacy) |
|
||
|
||
```bash
|
||
curl -H "Authorization: Bearer $TOKEN" \
|
||
"https://ordinis.k8s.265.nstart.cloud/api/v1/dictionaries/spacecraft/records?lang=ru"
|
||
```
|
||
|
||
### `GET /api/v1/dictionaries/{name}/records/{businessKey}`
|
||
|
||
Одна запись. Параметры те же.
|
||
|
||
### `GET /api/v1/dictionaries/{name}/records/{businessKey}/history`
|
||
|
||
Все версии записи (включая закрытые). Используется UI-историей, для consumer'ов
|
||
обычно не нужно.
|
||
|
||
## 5. Стратегии кэширования
|
||
|
||
### A) Простой TTL (рекомендуется для v1)
|
||
|
||
```ts
|
||
// TanStack Query
|
||
useQuery({
|
||
queryKey: ['ordinis', 'spacecraft', lang],
|
||
queryFn: () => fetch(`/api/reference/spacecraft?lang=${lang}`).then(r => r.json()),
|
||
staleTime: 5 * 60_000, // 5 минут — справочники меняются редко
|
||
retry: 3,
|
||
})
|
||
```
|
||
|
||
### B) Webhook / Kafka push (v2)
|
||
|
||
Когда нужны realtime инвалидации — подписаться на топик
|
||
`nsi.dictionary.changed` (планируется в Ordinis v2). Payload:
|
||
|
||
```json
|
||
{
|
||
"name": "spacecraft",
|
||
"businessKey": "RESURS-P-3",
|
||
"action": "updated", // created / updated / closed
|
||
"at": "2026-05-15T10:30:00+03:00"
|
||
}
|
||
```
|
||
|
||
Consumer вычищает соответствующий ключ из своего кэша и pull'ит свежую версию.
|
||
|
||
### C) Snapshot at order time (для аудит-кейсов)
|
||
|
||
Когда нужен «как было на момент заказа» — сохранить полную копию использованных
|
||
справочников рядом с заказом:
|
||
|
||
```sql
|
||
CREATE TABLE imaging_order (
|
||
id UUID PRIMARY KEY,
|
||
spacecraft_business_key VARCHAR(256),
|
||
instrument_business_key VARCHAR(256),
|
||
reference_snapshot JSONB, -- копии записей, не FK
|
||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
Альтернатива — запрашивать read-api с `?at=<order.created_at>` (bitemporal
|
||
поддерживает это). Snapshot надёжнее: read-only к prod, не зависит от наличия
|
||
старых версий после retention.
|
||
|
||
## 6. Best practices
|
||
|
||
1. **Не делайте FK на копию справочника**. Храните `business_key VARCHAR` —
|
||
это стабильный идентификатор, валидный сквозь версии.
|
||
2. **Кэшируйте per `(dictionary, lang)`**. Inline в коде — `staleTime`/TTL.
|
||
3. **Refresh JWT перед TTL**. Не дожидайтесь 401 — большинство Keycloak SDK
|
||
рефрешит автоматически.
|
||
4. **Stale cache на 5xx/timeout**. Если Ordinis недоступен, отдавайте старые
|
||
данные с warn'ом в лог. Не падайте каскадно.
|
||
5. **Используйте businessKey, не UUID**. UUID — внутренний идентификатор записи
|
||
и меняется при создании новой версии (через update). businessKey стабилен.
|
||
6. **Не пишите массовых update'ов**. Ordinis оптимизирован под редкие записи и
|
||
частые чтения. Bulk import через bundle (`POST /admin/bundle/import`) или
|
||
через ETL.
|
||
|
||
## 7. Bitemporal модель — что значит `validFrom` / `validTo`
|
||
|
||
Каждая запись имеет:
|
||
|
||
- `validFrom` — с какого момента запись активна (бизнес-время).
|
||
- `validTo` — до какого момента (или `9999-12-31` если бессрочно).
|
||
- `version` — порядковый номер версии (JPA optimistic lock).
|
||
|
||
При update создаётся **новая версия**: старая `validTo` ставится в `now`,
|
||
новая создаётся с `validFrom = now`. Запись по `businessKey` всегда активна на
|
||
любой момент времени (не считая периодов когда она закрыта).
|
||
|
||
Для consumer'ов это значит: `GET /records/{key}?at=X` всегда возвращает
|
||
актуальную версию (или 404 если `key` был закрыт на этот момент).
|
||
|
||
## 8. Текущие справочники в bundle `cuod` (anchor v1)
|
||
|
||
| Имя | Scope | Записей (на staging) | Назначение |
|
||
|--------------------|--------|----------------------|-----------------------------------------------|
|
||
| `spacecraft` | PUBLIC | 4 | Каталог КА |
|
||
| `satellite_type` | PUBLIC | 5 | Классификация типов |
|
||
| `ground_station` | PUBLIC | 3 | Наземные пункты |
|
||
| `mission` | PUBLIC | 9 | Программы (Ресурс-П, Канопус, Sentinel...) |
|
||
| `instrument` | PUBLIC | 10 | Бортовые сенсоры (Геотон, MODIS, C-SAR...) |
|
||
| `processing_level` | PUBLIC | 8 | Уровни L0-L4 |
|
||
| `data_format` | PUBLIC | 8 | GeoTIFF, NITF, SAFE и т.д. |
|
||
|
||
Все справочники имеют `i18n` поля (`name`, `description`) с RU + EN. Используйте
|
||
`?lang=ru` для русского flatten, `?lang=en` для английского.
|
||
|
||
## 9. Cross-references между справочниками
|
||
|
||
- `spacecraft.satellite_type_code` → `satellite_type.code`
|
||
- `mission.spacecraft_codes` → `spacecraft.businessKey[]`
|
||
- `instrument.spacecraft_codes` → `spacecraft.businessKey[]`
|
||
- `instrument.supported_format_codes` → `data_format.businessKey[]`
|
||
- `instrument.supported_level_codes` → `processing_level.businessKey[]`
|
||
|
||
Эти связи **не enforced** на уровне БД (это NoSQL-style references поверх
|
||
JSONB). Consumer должен сам подтягивать связанные записи параллельными
|
||
запросами либо JOIN'ить в своей БД через snapshot.
|
||
|
||
Cascading select для UI:
|
||
|
||
```ts
|
||
const instrument = ... // выбран MSI-S2
|
||
const formats = await fetch(`/dictionaries/data_format/records?lang=${lang}`)
|
||
.then(r => r.json())
|
||
const allowedFormats = formats.filter(f =>
|
||
instrument.data.supported_format_codes.includes(f.businessKey))
|
||
// allowedFormats: SAFE, GEOTIFF, COG, JPEG2000
|
||
```
|
||
|
||
## 10. Поддержка / эскалация
|
||
|
||
- Bug в API: `git@git.nstart.local:2-6/2-6-4/terravault/ordinis` issues.
|
||
- Запрос новой роли в Keycloak / нового scope: zimin.an@nstart.space.
|
||
- Запрос нового справочника / схемы: создать issue в репо ordinis с label `dictionary-request`.
|