docs: Diplodoc setup + полное руководство интеграции по API

Новая документация-as-code на платформе Diplodoc (Yandex open-source) с
YFM (Yandex Flavored Markdown). Build → static HTML, deployable куда угодно.

Что добавлено:
- package.json + .yfm + toc.yaml — Diplodoc setup, навигация для sidebar
- index.md — главная страница с tabs для разных audiences
- README.md — инструкция для contributors

Раздел integration/api/ — 11 страниц про интеграцию consumers:
- index — обзор + глоссарий
- auth — Keycloak service accounts, scope mapping
- read-endpoints — GET /dictionaries, list/by-key, spatial filters
- events — Kafka outbox топики, partition keys, idempotency, DLQ
- webhooks — HMAC, SSRF guard, retry policy, test ping endpoint
- bitemporal — validFrom/validTo + history, future-dated entries, bulk close/export
- x-references — FK syntax, validation errors, scope hiding, orphan scanner, v2 cascade preview
- caching — TTL / push / snapshot strategies, hybrid pattern
- errors — full error code reference (validation/auth/conflict/server), retry strategy
- best-practices — 33 numbered rules для consumers
- bundle-cuod — все 40 справочников с FK графом и сценариями

Обновлены:
- ops/README.md — link на новую integration/api/, убран inline <br>
- ops/slo.md — broken cross-repo links заменены на text references

Build:
  cd docs && pnpm install && pnpm build
  → ./_dist (20 HTML files, 7.1M)
  pnpm serve  # → http://localhost:8088

CI deploy job для GitLab Pages — в roadmap (см. docs/README.md).

Существующий tech/api-integration.md оставлен в toc как legacy
(содержимое мигрировано + расширено в integration/api/).
This commit is contained in:
Zimin A.N.
2026-05-07 23:19:02 +03:00
parent 0c79821f8e
commit d5268b9395
20 changed files with 3450 additions and 5 deletions
+169
View File
@@ -0,0 +1,169 @@
# Ошибки и rate limits
## HTTP status coды
Ordinis следует стандартам REST с расширениями:
| Status | Используется для | Можно retry? |
|---|---|---|
| `200 OK` | GET / PUT успешен | — |
| `201 Created` | POST создан новый ресурс | — |
| `202 Accepted` | Async операция принята | — |
| `204 No Content` | DELETE / close успешен | — |
| `400 Bad Request` | Malformed JSON / missing required fields | Нет — fix payload |
| `401 Unauthorized` | Token expired / invalid / missing | Refresh token и retry |
| `403 Forbidden` | Token валиден, но scope не позволяет | Нет — request higher role |
| `404 Not Found` | Resource не существует / закрыт / out of scope | Зависит от context |
| `409 Conflict` | Optimistic lock fail / cascade block | Refetch + retry |
| `422 Unprocessable Entity` | Validation errors (schema / x-references) | Нет — fix data |
| `429 Too Many Requests` | Rate limit hit | Wait + retry с backoff |
| `500 Internal Server Error` | Server bug | Retry с exponential backoff |
| `502 Bad Gateway` | Upstream (config-server, Vault) недоступен | Retry |
| `503 Service Unavailable` | Postgres недоступен / restart | Retry с long backoff |
| `504 Gateway Timeout` | Запрос > timeout (typically 30s) | Retry |
## Error response format
Все 4xx/5xx с body имеют единый shape:
```json
{
"timestamp": "2026-05-07T15:23:45.123+03:00",
"status": 422,
"error": "Validation Error",
"errors": [
{
"path": "/data/satelliteTypeCode",
"code": "x_references_target_not_found",
"message": "Referenced record not found in 'satellite_type' for code='UNKNOWN'"
},
{
"path": "/data/launchDate",
"code": "schema_format_invalid",
"message": "Expected ISO 8601 date, got 'tomorrow'"
}
],
"traceId": "abc123-def456-789"
}
```
| Поле | Описание |
|---|---|
| `timestamp` | Server time когда ошибка возникла. |
| `status` | HTTP status code. |
| `error` | Категория (`Validation Error`, `Authorization Error` и т.д.). |
| `errors` | Array of detail errors. Может быть один или несколько. |
| `errors[].path` | JSON Pointer к полю с ошибкой (например `/data/launchDate`). |
| `errors[].code` | Machine-readable код для programmatic handling. |
| `errors[].message` | Human-readable описание (русский для admin UI, английский для consumers). |
| `traceId` | Tempo trace ID — приложи при эскалации в Ordinis team. |
## Известные error codes
### Validation (422)
| Code | Описание | Fix |
|---|---|---|
| `schema_required_missing` | Required field пропущен | Добавить поле |
| `schema_format_invalid` | Wrong format (date / email / etc) | Fix format |
| `schema_type_mismatch` | Wrong JSON type (string vs number) | Fix type |
| `x_references_dict_not_found` | Referenced dictionary не существует | Schema bug — escalate |
| `x_references_target_not_found` | Referenced record не активен / out of scope | Use existing key либо upgrade scope |
| `x_references_non_string` | v1 поддерживает только string refs | Fix schema |
| `x_references_malformed` | Bad x-references syntax | Fix schema |
| `x_localized_default_missing` | i18n field без `default_locale` | Add default locale |
| `geometry_invalid_wkt` | Невалидный WKT для geometry | Fix WKT |
| `geometry_invalid_lat_lon` | lat/lon вне диапазона | Use valid coords |
### Authorization (401/403)
| Code | Описание |
|---|---|
| `auth_token_missing` | No `Authorization` header |
| `auth_token_invalid` | Token не verified (signature, issuer) |
| `auth_token_expired` | Token expired |
| `auth_scope_insufficient` | Scope не покрывает запрашиваемый словарь |
### Conflict (409)
| Code | Описание | Fix |
|---|---|---|
| `optimistic_lock_failed` | Запись изменилась между read и write | Refetch + retry |
| `record_already_closed` | close() на уже закрытой записи | No-op либо ignore |
| `bulk_partial_failure` | Bulk операция: часть успешна, часть нет | Check `errors` array в response |
### Server (5xx)
| Code | Описание |
|---|---|
| `internal_unexpected` | Server bug — escalate с traceId |
| `db_unavailable` | Postgres недоступен |
| `vault_unavailable` | Vault sealed либо unreachable |
| `kafka_publish_failed` | Outbox не смог publish (но запись в БД OK — eventually consistent) |
## Rate limits
Сегодня (v1.2.1) Ordinis **не имеет explicit rate limits**. Но есть soft
limits через resource constraints:
| Resource | Limit |
|---|---|
| Postgres connections | 200 (shared между read-api + writer + projection-writer) |
| Read-api app pods | 2× в проде, 1× CPU + 512MB memory |
| Writer pods | 2× в проде |
| Concurrent JWT verifications | bounded heap (~5k/sec практический максимум) |
При превышении (типично DDoS-like patterns):
- HTTP `429 Too Many Requests` после ingress rate limit (ingress nginx default).
- HTTP `503 Service Unavailable` при exhausting connection pool.
- `5xx error rate spike` → alert `OrdinisReadApiErrorBudgetBurnFast`.
### Recommended client-side limits
Чтобы не triggers backend limits:
| Сценарий | Recommended rate |
|---|---|
| Cold load на startup | `≤ 100 req/sec` parallel, then cache |
| Steady state read | `≤ 50 req/sec` per consumer |
| Bulk import / migration | Talk to Ordinis team первым |
Внутренний `RateLimiter` (Caffeine `Bucket4j` — TBD) — в roadmap для v2 если
наблюдаются abuse patterns.
## Retry strategy
```python
import time, random
from typing import Callable
def retry_with_backoff(
fn: Callable, max_attempts: int = 5, base: float = 0.5
) -> any:
for attempt in range(max_attempts):
try:
return fn()
except (HTTPError, TimeoutError) as e:
if attempt == max_attempts - 1:
raise
if e.status_code in (400, 401, 403, 404, 422):
raise # don't retry permanent errors
sleep = base * (2 ** attempt) + random.uniform(0, 0.5)
time.sleep(sleep)
```
## Tracing
Каждый response имеет `X-Trace-Id` header и `traceId` field в error body.
Ordinis эмитит OpenTelemetry traces в Tempo (`tempo.monitoring:4317`).
При эскалации Ordinis team:
1. Скопируй `traceId` из response либо логов.
2. Создай issue с воспроизведением.
3. Ordinis team query'ит Tempo: `traceId="abc123-..."`.
4. Trace покажет full path: ingress → read-api → postgres → response.
## Дальше
- [Best practices](best-practices.md) — как писать robust integration code
- [Авторизация](auth.md) — как избежать 401/403