12 KiB
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 и т.д. Пример:
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 altum:
- Issuer:
https://altum.nstart.cloud/auth/realms/altum - Алгоритм: RS256
- JWK Set discoverится через
/.well-known/openid-configuration.
Service account (m2m)
Для интеграции backend↔backend (например altum-backend → ordinis read-api) получить service account клиент в Keycloak:
- Запросить у Ordinis team создание клиента в realm
altum. - Назначить роль
ordinis-public(или выше, если нужен RESTRICTED scope). - Получить
client_id+client_secret.
Получение access token (client_credentials):
curl -X POST https://altum.nstart.cloud/auth/realms/altum/protocol/openid-connect/token \
-d "grant_type=client_credentials" \
-d "client_id=altum-backend" \
-d "client_secret=<SECRET>"
Ответ:
{ "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
Список доступных справочников.
curl -H "Authorization: Bearer $TOKEN" \
https://ordinis.k8s.265.nstart.cloud/api/v1/dictionaries
[
{ "name": "spacecraft", "displayName": "Космические аппараты", "scope": "PUBLIC", "schemaVersion": "1.0.0" },
{ "name": "mission", "displayName": "Миссии / программы", "scope": "PUBLIC", "schemaVersion": "1.0.0" },
...
]
GET /api/v1/{name}/records
⚠️ БЕЗ префикса
/dictionaries/! Read-api сидит на/api/v1/{name}/records./api/v1/dictionaries/...— это admin-write контроллер для CRUD, у него нет bulk-list эндпоинта, не пытайтесь его читать.
Активные записи на момент at (default: now). Параметры:
| параметр | тип | пример | примечание |
|---|---|---|---|
lang |
string | ru-RU, en-US |
flatten i18n полей до строки на этом языке. Короткие формы (ru, en) тоже принимаются. |
at |
ISO 8601 | 2026-05-04T12:00:00+03:00 |
bitemporal query — вернёт активные на момент |
as_scope |
string | PUBLIC,INTERNAL |
для anonymous + allowQueryScope=true (legacy) |
curl -H "Authorization: Bearer $TOKEN" \
"https://ordinis.k8s.265.nstart.cloud/api/v1/spacecraft/records?lang=ru-RU"
Без ?lang ответ возвращает локализованные поля как объект:
"name": { "ru-RU": "Ресурс-П № 3", "en-US": "Resurs-P No.3" }
С ?lang=ru-RU:
"name": "Ресурс-П № 3"
GET /api/v1/{name}/records/{businessKey}
Одна запись. Параметры те же.
GET /api/v1/{name}/records/{businessKey}/history
Все версии записи (включая закрытые). Используется UI-историей, для consumer'ов обычно не нужно.
5. Стратегии кэширования
A) Простой TTL (рекомендуется для v1)
// 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:
{
"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 (для аудит-кейсов)
Когда нужен «как было на момент заказа» — сохранить полную копию использованных справочников рядом с заказом:
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
- Не делайте FK на копию справочника. Храните
business_key VARCHAR— это стабильный идентификатор, валидный сквозь версии. - Кэшируйте per
(dictionary, lang). Inline в коде —staleTime/TTL. - Refresh JWT перед TTL. Не дожидайтесь 401 — большинство Keycloak SDK рефрешит автоматически.
- Stale cache на 5xx/timeout. Если Ordinis недоступен, отдавайте старые данные с warn'ом в лог. Не падайте каскадно.
- Используйте businessKey, не UUID. UUID — внутренний идентификатор записи и меняется при создании новой версии (через update). businessKey стабилен.
- Не пишите массовых 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.codemission.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:
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/ordinisissues. - Запрос новой роли в Keycloak / нового scope: zimin.an@nstart.space.
- Запрос нового справочника / схемы: создать issue в репо ordinis с label
dictionary-request.