Files
mdm-ordinis/docs/design/dictionary-marketplace.md
T
2026-05-12 15:10:22 +00:00

400 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Design: Dictionary Bundle Marketplace
**Author:** zimin.an
**Date:** 2026-05-12
**Status:** PROPOSED v1 — needs `/office-hours` для premise validation + `/plan-eng-review`
**Scope:** ДЗЗ domain, multi-tenant ready (corp private bundles + curated public ДЗЗ catalog)
**Sprint estimate:** **~1.5-2 спринта** (10-14 days CC), v1 internal-only (no public catalog UI)
**Blockers:** none, но рекомендация defer'a до v2.14.0 prod stable + 2 weeks dogfood
---
## TL;DR
Сейчас каждый customer install Ordinis строит свой `ordinis-cuod-bundle` Maven module from scratch. ЦУОД bundle = 40 dictionaries (КА, типы, ground stations, частотные диапазоны, операторы и т.д.) — это ~2-3 недели работы дублируется при onboarding нового customer'а. **Нет shared catalog**, нет «бери готовое».
**Предложение:** Bundle marketplace. Авторы публикуют bundle versions в реестр (corp Nexus → потенциально public ДЗЗ catalog). Admin в running instance видит каталог, install'нет нужный bundle (с optional sample data seed), получает 40 готовых справочников за минуту.
**Win:** Customer onboarding 2-3 недели → 1 день. Sales: «у нас готовая ДЗЗ-библиотека из 40+ справочников». Compounding value: каждый install обогащает catalog.
**Risk:** schema conflicts при install, malicious bundles, version drift breaking consumer integrations. Mitigations через signed manifests + dry-run preview + namespace prefix.
---
## Current state
Сейчас в Ordinis:
- `ordinis-cuod-bundle` Maven module → ЦУОД-specific dictionaries + sample data в `src/main/resources/bundles/cuod/`
- Build-time bundle: компилируется в jar, deploy'ится с app
- Single-bundle per installation (multi-bundle architectural future, не shipped)
- No runtime install / browse / discover
```
[ Customer A install ] [ Customer B install ]
↓ docker pull ordinis:vX ↓ docker pull ordinis:vX
+ ordinis-cuod-bundle + ordinis-customer-b-bundle
(build-time, requires Maven build) (нет — customer пишет свой Maven module)
```
Каждый новый customer = **new Maven module от руки**.
## Что хочется
```
[ Customer admin opens /catalog ]
Browse: ЦУОД ДЗЗ Bundle v1.4.0 [40 dicts]
Гидрометео ДЗЗ Bundle v0.8.0 [12 dicts]
Базовый справочник стран v2.0.0 [1 dict]
[ Install ] → dry-run preview → confirm
POST /api/v1/bundles/install { bundleId, version, options }
40 dictionaries created in DB через standard `DictionaryDefinitionService`
+ optional sample data seeded
+ bundle metadata recorded в `installed_bundles` table
Done in ~30 sec
```
---
## Architecture
```
Bundle Registry
(Nexus / GitLab Package Registry)
- immutable versioned artifacts
- signed bundles (corp PKI)
│ npm/maven-like resolve
┌──────────────────────────────────────────┐
│ ordinis-rest-api (NEW endpoints) │
│ BundleCatalogController │
│ GET /api/v1/bundles/available │
│ GET /api/v1/bundles/{id}/versions │
│ BundleInstallController │
│ POST /api/v1/bundles/install (dry-run +│
│ apply) │
│ GET /api/v1/bundles/installed │
│ POST /api/v1/bundles/{id}/uninstall │
└────────────────┬──────────────────────────┘
┌────────────────▼──────────────────────────┐
│ BundleResolver (NEW component) │
│ - fetch manifest.yaml from registry │
│ - verify signature (corp PKI ed25519) │
│ - parse dictionary defs + sample data │
│ - check conflicts with existing dicts │
└────────────────┬──────────────────────────┘
┌────────────────▼──────────────────────────┐
│ Standard DictionaryDefinitionService │
│ - create dict (per-dict CREATE event) │
│ - seed sample records (if opted in) │
│ - outbox → Kafka → downstream consumers │
└───────────────────────────────────────────┘
┌────────────────┴──────────────────────────┐
│ ordinis-admin-ui │
│ BundleCatalogPage.tsx (NEW route) │
│ BundleInstallModal.tsx (dry-run preview) │
│ InstalledBundlesTab (manage existing) │
└───────────────────────────────────────────┘
```
### Bundle manifest format
```yaml
# bundle.yaml — published artifact metadata
apiVersion: ordinis.io/v1
kind: Bundle
metadata:
id: ru.cuod.dzz-ground-segment
name: ЦУОД ДЗЗ — наземный сегмент
version: 1.4.0
description: |
40 справочников для управления наземным сегментом ДЗЗ:
КА, типы КА, наземные станции, антенны, частотные диапазоны,
операторы, форматы данных, уровни обработки.
domain: dzz
scope: PUBLIC # or INTERNAL / RESTRICTED
author: ЦУОД team
license: proprietary
signature: ed25519:base64...
dependencies:
# Reference other bundles (e.g. shared "countries" dict)
- id: ru.shared.countries
version: ^2.0.0
- id: ru.shared.iso-units
version: ^1.0.0
dictionaries:
- name: spacecraft
schemaVersion: 1.0.0
scope: PUBLIC
schemaJson:
$schema: http://json-schema.org/draft-07/schema#
type: object
properties:
code: {type: string, x-unique: true}
type: {type: string, x-references: "satellite_type.code"}
# ...
sampleData:
- businessKey: ISS
data: {code: ISS, type: OPERATIONAL, ...}
# ... 39 more dictionaries
# Migration hints for upgrades
migrations:
- from: 1.3.x
to: 1.4.0
summary: добавлено поле x-frequencies в antenna
breaking: false
sql: null # null = no DB migration, schema add only
```
### Install flow (dry-run + apply)
```
1. Admin clicks Install в catalog
2. Frontend: POST /api/v1/bundles/install
{ bundleId, version, dryRun: true, options: { seedSampleData: true } }
3. Backend BundleResolver:
- Download manifest.yaml from Nexus
- Verify ed25519 signature against trusted authors registry
- Parse 40 dictionary defs
- For each: check conflicts (dict name уже exists? schema compatible?)
- Compute diff: новые dicts, modifications, conflicts
- Return: { willCreate: [...], willUpdate: [...], conflicts: [...], warnings: [...] }
4. Frontend shows preview modal:
- 40 dicts to create
- 0 conflicts
- 312 sample records to seed
- [Cancel] [Install for real]
5. POST /api/v1/bundles/install { ..., dryRun: false }
6. Backend opens transaction:
- For each dict: DictionaryDefinitionService.create() (existing logic)
- For sample data: bulk insert via existing DictionaryRecordService
- INSERT INTO installed_bundles (id, version, installed_at, installed_by, manifest_sha256)
- Commit
7. Standard outbox → Kafka → downstream consumers получают NewDictionaryCreated events
(40 events за raz, batched в outbox)
8. Frontend: success toast + redirect к catalog (теперь installed_bundles tab показывает entry)
```
### Conflict resolution strategies
При install bundle X v1.4.0, если dict `spacecraft` уже существует:
| Strategy | Behavior |
|---|---|
| **`abort`** (default) | Return 409, admin вручную resolve |
| **`namespace`** | Create as `dzz_ground_segment__spacecraft` (prefix bundle id) |
| **`merge`** | Skip dict if schema совместима (existing has all bundle fields); fail if incompatible |
| **`override`** | Replace existing schema (DANGEROUS — only с explicit confirmation + admin role RESTRICTED) |
v1: только `abort` + `namespace`. `merge` / `override` — v2 после dogfooding.
---
## Components (new)
| Component | Module | LOC est |
|---|---|---|
| `BundleResolver` (fetch + verify + parse) | ordinis-app or new ordinis-bundles module | ~300 |
| `BundleCatalogController` (read endpoints) | ordinis-rest-api | ~150 |
| `BundleInstallController` (dry-run + apply) | ordinis-rest-api | ~200 |
| `BundleSignatureVerifier` (ed25519) | ordinis-bundles | ~100 |
| `InstalledBundle` JPA entity + repo | ordinis-domain | ~80 |
| Migration 00XX: `installed_bundles` table | ordinis-migrations | ~30 |
| `BundleCatalogPage.tsx` (UI) | ordinis-admin-ui | ~250 |
| `BundleInstallModal.tsx` (dry-run preview) | ordinis-admin-ui | ~200 |
| `InstalledBundlesTab.tsx` | ordinis-admin-ui | ~150 |
| Bundle CLI publisher (corp Maven plugin / scripts) | new ordinis-bundle-publisher | ~250 |
Total: ~1700 LOC. Plus ~600 LOC tests.
---
## Registry choice
### Option A: Reuse corp Nexus (RECOMMENDED для v1)
- Pros: уже есть в инфре (corp packages), self-hosted, signed artifacts
- Cons: Nexus ориентирован на Maven/npm, нужна custom REST query layer
- Path: `nexus.corp/repository/ordinis-bundles/{bundle-id}/{version}/manifest.yaml + sampledata.tar.gz`
### Option B: GitLab Package Registry
- Pros: уже используем GitLab, native CI publish from bundle source repos
- Cons: less flexible queries, GitLab CE может иметь limits
### Option C: Custom registry service (NEW)
- Pros: tailored to bundles (search, semver, dependencies)
- Cons: yet another service to ops, +2 weeks effort
**Recommendation:** A (Nexus) для v1. C если customer growth >10 bundles published.
---
## Multi-tenancy
Каждый customer install ordinis имеет свой namespace в registry:
- `nexus.corp/ordinis-bundles/private/{customer-id}/` — corp private bundles
- `nexus.corp/ordinis-bundles/public/dzz/` — curated public ДЗЗ catalog
Customer A не видит customer B's private bundles. Public catalog visible всем authenticated installs.
Trust model:
- Private bundles: signed customer's own key
- Public ДЗЗ catalog: signed ЦУОД editorial team
- Admin UI shows badge: «✅ Verified ЦУОД» / «🏢 Private (your org)» / «⚠️ Unverified»
---
## Non-goals (v1)
- ❌ Public internet-facing marketplace UI (browser) — only corp Nexus discoverable through admin UI
- ❌ Paid bundles / billing
- ❌ Bundle rating / reviews / comments
- ❌ Automatic bundle updates (admin manually triggers upgrade)
- ❌ Bundle composition (compose multiple bundles into super-bundle)
- ❌ Bundle export from running instance back to registry (one-way: registry → install)
- ❌ Migration scripts execution (DB schema changes) — v1 только additive (new dicts, new fields), no DB migration
---
## Risks
1. **Malicious bundle uploaded в Nexus** — bundle с `x-id-source` указывающим на JNDI или другой attack vector. Mitigation:
- All bundles require ed25519 signature от trusted author
- Schema validator strict mode (no eval, no JNDI, allowlist `x-*` annotations)
- Sandboxed sampleData parse (no executable code, JSON only)
2. **Bundle version conflict** — bundle X v1.4 deps require shared/countries v2.x, but другой bundle Y deps require shared/countries v1.x. Mitigation:
- Resolver detect conflict at dry-run, return 409 conflict с explanation
- Recommend admin: deinstall Y or wait until Y updates
3. **Breaking schema change в new version** — bundle v2.0 removes field `mass_kg`, existing records have it. Mitigation:
- `migrations` block в manifest documents breaking changes
- Upgrade endpoint requires `acknowledgeBreaking: true` flag
- Audit log records upgrade event with migration summary
4. **Registry down при install** — admin clicks install, Nexus 503. Mitigation:
- User-friendly error «Catalog временно недоступен, повторите через минуту»
- Local cache: recently viewed manifests cached 1h
- Circuit breaker на Resolver level
5. **Schema-level FK references to non-existing dicts** — bundle dict A references dict B, B installed позже. Mitigation:
- Resolver topologically sort install order
- Atomic transaction (all-or-nothing)
- Cross-bundle FK: explicit dependency declaration в manifest, resolver enforces
6. **Sample data сбивает existing records** — install bundle с sample data, customer уже имеет records с теми же businessKeys. Mitigation:
- Default: `seedSampleData: false`
- If true: dry-run shows count of records, admin confirms
- Skip records existing с тем же businessKey (idempotent)
---
## Test plan
| # | Test | Type |
|---|---|---|
| 1 | Happy path: install bundle с 5 dicts → все 5 created, sample data seeded | integration |
| 2 | Dry-run mode returns preview without DB changes | integration |
| 3 | Conflict detection: install bundle с existing dict name → 409 | integration |
| 4 | Namespace strategy: prefix всех dict names с bundle id | integration |
| 5 | Signature verification: tampered manifest → 422 | integration |
| 6 | Signature verification: unknown author → 422 with «author not trusted» | integration |
| 7 | Dependency resolution: install bundle X depending on Y → 409 если Y absent | integration |
| 8 | Topological install order: bundle с inter-dict FK → outer FK created first | integration |
| 9 | Atomic transaction: failure mid-install rolls back ALL changes | integration |
| 10 | Outbox events: install 5 dicts → 5 NewDictionaryCreated events in outbox | integration |
| 11 | Registry timeout: Nexus 503 → user-friendly 502 + retry | integration |
| 12 | Circuit breaker: 10 fails → 5min cool-down | integration |
| 13 | Bundle uninstall: deletes dicts + records + emits delete events | integration |
| 14 | Uninstall blocked если dicts have records authored locally (non-sample) | integration |
| 15 | Frontend BundleCatalogPage shows verified badge | RTL |
| 16 | Frontend BundleInstallModal — preview correctly counts new/updated/conflicts | RTL |
| 17 | Multi-tenancy: customer A не видит customer B private bundles | integration |
---
## Effort
| Step | Effort (CC) | Notes |
|---|---|---|
| 1. `installed_bundles` table migration | 1h | Standard Liquibase |
| 2. `InstalledBundle` JPA entity + repo | 1h | Standard JPA |
| 3. `BundleResolver` (fetch + parse) | 6h | HTTP client + YAML + JSON parse |
| 4. `BundleSignatureVerifier` (ed25519) | 4h | BouncyCastle JCA |
| 5. `BundleCatalogController` (read endpoints) | 3h | Standard REST |
| 6. `BundleInstallController` (dry-run + apply) | 8h | Transactional, conflict detection, atomic |
| 7. Topological install order + dep resolution | 4h | Kahn's algorithm на dict deps |
| 8. Frontend `BundleCatalogPage.tsx` | 6h | Standard list + search + filter |
| 9. Frontend `BundleInstallModal.tsx` (dry-run preview) | 6h | Preview UI, diff visualization |
| 10. Frontend `InstalledBundlesTab.tsx` | 4h | List + uninstall action |
| 11. Bundle publisher CLI (Maven plugin) | 8h | Sign + upload to Nexus |
| 12. i18n keys (~25 strings ru/en) | 1h | Standard |
| 13. Tests (17 cases) | 16h | testcontainers + RTL |
| 14. Docs (admin guide + publisher guide + ops runbook) | 6h | docs/user-guide/marketplace.md |
| **Total** | **~74h (~10 рабочих дней)** | within 1.5 sprints |
---
## Open questions
1. **Где будут жить public ДЗЗ catalog editors?** Кто signs verified bundles от имени community? Suggestion: ЦУОД core team initially, expand by invitation later.
2. **Bundle CI/CD pipeline для авторов?** Authors write bundle.yaml + sample data → CI signs + uploads to Nexus? Yes — provide Maven plugin (`ordinis-bundle-publisher`) which integrates с GitLab CI. ~1 day extra effort.
3. **Versioning policy?** SemVer (major.minor.patch). Breaking schema changes = major bump. Recommend documenting в `docs/user-guide/bundle-authoring.md`.
4. **Sample data ownership после install?** Records seeded из bundle — owned by «system» user, audit log helps trace. Customer-edited records → owned by customer.
5. **Cross-bundle FK?** Bundle X dict X-A references bundle Y dict Y-B. Allowed? Suggestion: yes, declare explicitly в `dependencies`, resolver enforces install order.
6. **Bundle update flow?** v1: deinstall + install new version (loses customer-edited records). v2: incremental upgrade с migration. Add to backlog.
7. **Integration with AI schema assist?** AI suggestion → save as bundle → publish? Это **product-market-fit gold**: customer создаёт schema с AI, publish'нет в catalog, monetize'нет / share'нет. v2 idea, defer.
---
## Combined value with `ai-schema-assist.md`
Эти два дизайна имеют **massive synergy**:
| Без AI + без marketplace | + AI | + Marketplace | + Оба |
|---|---|---|---|
| Customer пишет 40 schemas вручную ~3 недели | Customer пишет 40 schemas с AI ~3 дня | Customer берёт готовый bundle ~30 сек | Customer описывает домен («гидрометеостанции») → AI генерит bundle → publishes → другие installer'ят |
**Compounding value loop:** AI lowers bar to create bundles → marketplace enables sharing → more bundles → more demand → more AI usage → more bundles.
---
## Recommendation
**Defer until после v2.14.0 prod stable + 2 weeks dogfood, + AI design `/office-hours`'d first** (поскольку AI affects bundle authoring flow).
**Sequencing:**
1. v2.14.0 prod stable + dogfood ~2 weeks (current)
2. `ai-schema-assist.md``/office-hours``/plan-eng-review` → implement (5-7 days)
3. Use AI assist для production, gather feedback ~1 month
4. `dictionary-marketplace.md``/office-hours``/plan-eng-review` → implement (10-14 days)
5. Public catalog launch — Q3-Q4 2026
**Это потенциально strongest product differentiator** для МДМ-продукта на рынке — combine AI + Marketplace + ДЗЗ domain expertise = **product nobody else has** в RU/CIS МДМ-сегменте.
---
## See also
- Companion: `ai-schema-assist.md` — AI-assisted authoring (sequencing prerequisite)
- Roadmap mention: `docs-internal/status/2026-05-05-state.md` § "v2 (governance / data quality / federation)" — Federation MDM-кластеров (related but not same)