366 lines
13 KiB
Markdown
366 lines
13 KiB
Markdown
# Схема БД
|
||
|
||
## 1. Назначение документа
|
||
|
||
Этот документ фиксирует структуру хранения конфигураций, версий операций, загруженных артефактов и published runtime-view. Его цель - дать основу для SQL-миграций и для реализации `crank-registry`.
|
||
|
||
В документе предполагается реляционная модель, ориентированная на `PostgreSQL`. Канонической считается схема, совместимая с `PostgreSQL`.
|
||
|
||
## 2. Общие принципы хранения
|
||
|
||
### 2.1. Версионирование обязательно
|
||
|
||
Конфигурация operation не должна храниться только в одной "живой" записи. Каждое существенное изменение должно приводить к появлению новой версии конфигурации.
|
||
|
||
Для MVP в registry version snapshot хранит protocol-specific конфигурацию, схемы, mapping и execution settings. Поля identity и listing view (`name`, `display_name`, `protocol`) считаются стабильными и хранятся в `operations`.
|
||
|
||
### 2.2. Published и draft разделяются логически
|
||
|
||
- `draft` может меняться;
|
||
- `published` должна ссылаться на конкретную зафиксированную версию;
|
||
- runtime читает только опубликованные версии.
|
||
|
||
### 2.3. Артефакты и конфигурация не смешиваются
|
||
|
||
`.proto`, descriptor set, sample JSON и YAML import payload не должны храниться в той же структуре, что и runtime-ready configuration.
|
||
|
||
### 2.4. Секреты не хранятся внутри operation
|
||
|
||
В БД operation должны храниться только ссылки на auth profiles или secret references.
|
||
|
||
Для MVP рекомендуется отдельная таблица `auth_profiles`, где metadata и secret refs отделены от operation versions.
|
||
|
||
### 2.5. Тестовая изоляция
|
||
|
||
Integration tests для registry должны выполняться на реальной `PostgreSQL`, но без влияния на runtime-данные. Предпочтительный способ:
|
||
|
||
- отдельная test database;
|
||
- либо отдельная временная schema на время теста;
|
||
- обязательная очистка после завершения тестов.
|
||
|
||
## 3. Основные таблицы
|
||
|
||
Минимальный набор таблиц:
|
||
|
||
- `operations`
|
||
- `operation_versions`
|
||
- `published_operations`
|
||
- `operation_samples`
|
||
- `descriptors`
|
||
- `auth_profiles`
|
||
- `yaml_import_jobs`
|
||
|
||
Опционально позже:
|
||
|
||
- `operation_test_runs`
|
||
- `audit_log`
|
||
|
||
## 4. Таблица `operations`
|
||
|
||
Хранит стабильную сущность операции, не зависящую от конкретной версии.
|
||
|
||
### Поля
|
||
|
||
- `id` `text primary key`
|
||
- `name` `text not null unique`
|
||
- `display_name` `text not null`
|
||
- `protocol` `text not null`
|
||
- `status` `text not null`
|
||
- `current_draft_version` `integer not null default 1`
|
||
- `latest_published_version` `integer null`
|
||
- `created_at` `timestamptz not null`
|
||
- `updated_at` `timestamptz not null`
|
||
- `published_at` `timestamptz null`
|
||
|
||
### Назначение
|
||
|
||
- быстрый список операций;
|
||
- стабильный идентификатор для UI и MCP;
|
||
- привязка к актуальному draft и опубликованной версии.
|
||
|
||
## 5. Таблица `operation_versions`
|
||
|
||
Хранит полную сериализованную конфигурацию конкретной версии operation.
|
||
|
||
### Поля
|
||
|
||
- `operation_id` `text not null`
|
||
- `version` `integer not null`
|
||
- `status` `text not null`
|
||
- `target_json` `jsonb not null`
|
||
- `input_schema_json` `jsonb not null`
|
||
- `output_schema_json` `jsonb not null`
|
||
- `input_mapping_json` `jsonb not null`
|
||
- `output_mapping_json` `jsonb not null`
|
||
- `execution_config_json` `jsonb not null`
|
||
- `tool_description_json` `jsonb not null`
|
||
- `samples_json` `jsonb null`
|
||
- `generated_draft_json` `jsonb null`
|
||
- `config_export_json` `jsonb null`
|
||
- `change_note` `text null`
|
||
- `created_at` `timestamptz not null`
|
||
- `created_by` `text null`
|
||
|
||
### Ключи
|
||
|
||
- primary key: `(operation_id, version)`
|
||
- foreign key: `operation_id -> operations(id)`
|
||
- рекомендованный composite foreign key для связанных таблиц: `(operation_id, version)`
|
||
|
||
### Почему так
|
||
|
||
Для MVP выгоднее хранить version snapshot целиком, а не дробить по десятку связанных таблиц. Это:
|
||
|
||
- упрощает versioning;
|
||
- упрощает откат;
|
||
- упрощает YAML export;
|
||
- хорошо сочетается с JSON-oriented доменной моделью.
|
||
|
||
## 6. Таблица `published_operations`
|
||
|
||
Хранит явную published-привязку, которую читает runtime.
|
||
|
||
### Поля
|
||
|
||
- `operation_id` `text primary key`
|
||
- `version` `integer not null`
|
||
- `published_at` `timestamptz not null`
|
||
- `published_by` `text null`
|
||
|
||
### Назначение
|
||
|
||
- быстрый доступ к published runtime-view;
|
||
- отсутствие двусмысленности, какая именно версия сейчас активна;
|
||
- простой invalidation для runtime cache.
|
||
|
||
### Рекомендуемая целостность
|
||
|
||
- `operation_id -> operations(id)`
|
||
- `(operation_id, version) -> operation_versions(operation_id, version)`
|
||
|
||
## 7. Таблица `operation_samples`
|
||
|
||
Хранит метаданные и ссылки на sample artifacts.
|
||
|
||
### Поля
|
||
|
||
- `id` `text primary key`
|
||
- `operation_id` `text not null`
|
||
- `version` `integer not null`
|
||
- `sample_kind` `text not null`
|
||
- `storage_ref` `text not null`
|
||
- `content_type` `text not null`
|
||
- `file_name` `text null`
|
||
- `created_at` `timestamptz not null`
|
||
|
||
### Варианты `sample_kind`
|
||
|
||
- `input_json`
|
||
- `output_json`
|
||
- `yaml_import_source`
|
||
|
||
### Назначение
|
||
|
||
- не класть большие sample payload в основные version records;
|
||
- иметь возможность переиспользовать или пересобирать draft mapping;
|
||
- отслеживать, из каких sample-данных строился черновик.
|
||
|
||
### Рекомендуемая целостность
|
||
|
||
- `operation_id -> operations(id)`
|
||
- `(operation_id, version) -> operation_versions(operation_id, version)`
|
||
|
||
## 8. Таблица `descriptors`
|
||
|
||
Хранит gRPC schema artifacts.
|
||
|
||
### Поля
|
||
|
||
- `id` `text primary key`
|
||
- `operation_id` `text null`
|
||
- `version` `integer null`
|
||
- `descriptor_kind` `text not null`
|
||
- `storage_ref` `text not null`
|
||
- `source_name` `text null`
|
||
- `package_index_json` `jsonb null`
|
||
- `created_at` `timestamptz not null`
|
||
|
||
### Варианты `descriptor_kind`
|
||
|
||
- `proto_upload`
|
||
- `descriptor_set`
|
||
- `reflection_snapshot`
|
||
|
||
### Назначение
|
||
|
||
- связывать gRPC operation с конкретной схемой;
|
||
- не хранить binary descriptor внутри основной operation version;
|
||
- иметь отдельную точку для discovery metadata.
|
||
|
||
### Рекомендуемая целостность
|
||
|
||
- если descriptor привязан к version, то `(operation_id, version) -> operation_versions(operation_id, version)`
|
||
|
||
## 9. Таблица `yaml_import_jobs`
|
||
|
||
Для MVP можно импортировать YAML синхронно, но таблицу под журнал импорта лучше предусмотреть сразу.
|
||
|
||
### Поля
|
||
|
||
- `id` `text primary key`
|
||
- `source_sample_id` `text null`
|
||
- `status` `text not null`
|
||
- `format_version` `text not null`
|
||
- `mode` `text not null`
|
||
- `result_operation_id` `text null`
|
||
- `result_version` `integer null`
|
||
- `error_text` `text null`
|
||
- `created_at` `timestamptz not null`
|
||
- `finished_at` `timestamptz null`
|
||
|
||
### Назначение
|
||
|
||
- аудит импортов;
|
||
- разбор ошибок валидации;
|
||
- поддержка будущего async import pipeline.
|
||
|
||
## 10. Таблица `auth_profiles`
|
||
|
||
Хранит переиспользуемые профили аутентификации для внешних вызовов.
|
||
|
||
### Поля
|
||
|
||
- `id` `text primary key`
|
||
- `name` `text not null unique`
|
||
- `kind` `text not null`
|
||
- `config_json` `jsonb not null`
|
||
- `created_at` `timestamptz not null`
|
||
- `updated_at` `timestamptz not null`
|
||
|
||
### Варианты `kind`
|
||
|
||
- `bearer`
|
||
- `basic`
|
||
- `api_key_header`
|
||
- `api_key_query`
|
||
|
||
### Правило
|
||
|
||
`config_json` должен содержать только `secret_ref`, а не открытые секреты.
|
||
|
||
## 11. Предлагаемая SQL-форма
|
||
|
||
```sql
|
||
create table operations (
|
||
id text primary key,
|
||
name text not null unique,
|
||
display_name text not null,
|
||
protocol text not null,
|
||
status text not null,
|
||
current_draft_version integer not null default 1,
|
||
latest_published_version integer null,
|
||
created_at timestamptz not null,
|
||
updated_at timestamptz not null,
|
||
published_at timestamptz null
|
||
);
|
||
|
||
create table operation_versions (
|
||
operation_id text not null references operations(id),
|
||
version integer not null,
|
||
status text not null,
|
||
target_json jsonb not null,
|
||
input_schema_json jsonb not null,
|
||
output_schema_json jsonb not null,
|
||
input_mapping_json jsonb not null,
|
||
output_mapping_json jsonb not null,
|
||
execution_config_json jsonb not null,
|
||
tool_description_json jsonb not null,
|
||
samples_json jsonb null,
|
||
generated_draft_json jsonb null,
|
||
config_export_json jsonb null,
|
||
change_note text null,
|
||
created_at timestamptz not null,
|
||
created_by text null,
|
||
primary key (operation_id, version)
|
||
);
|
||
|
||
create table published_operations (
|
||
operation_id text primary key references operations(id),
|
||
version integer not null,
|
||
published_at timestamptz not null,
|
||
published_by text null,
|
||
foreign key (operation_id, version)
|
||
references operation_versions(operation_id, version)
|
||
);
|
||
|
||
create table auth_profiles (
|
||
id text primary key,
|
||
name text not null unique,
|
||
kind text not null,
|
||
config_json jsonb not null,
|
||
created_at timestamptz not null,
|
||
updated_at timestamptz not null
|
||
);
|
||
```
|
||
|
||
## 12. Индексы
|
||
|
||
Минимально нужны:
|
||
|
||
- index on `operations(protocol)`
|
||
- index on `operations(status)`
|
||
- index on `operation_versions(operation_id, created_at desc)`
|
||
- index on `published_operations(version)`
|
||
- index on `operation_samples(operation_id, version)`
|
||
- index on `descriptors(operation_id, version)`
|
||
- index on `auth_profiles(kind)`
|
||
|
||
## 13. Versioning flow
|
||
|
||
### Создание операции
|
||
|
||
1. Создается запись в `operations`.
|
||
2. Создается версия `1` в `operation_versions`.
|
||
3. `current_draft_version = 1`.
|
||
|
||
### Изменение draft
|
||
|
||
1. Читается текущий draft.
|
||
2. Создается новая версия `n + 1`.
|
||
3. В `operations.current_draft_version` пишется новая версия.
|
||
4. Published версия не меняется.
|
||
|
||
### Публикация
|
||
|
||
1. Берется текущий draft version.
|
||
2. В `published_operations` upsert-ится ссылка на эту версию.
|
||
3. В `operations.latest_published_version` пишется та же версия.
|
||
4. Runtime cache получает сигнал на reload.
|
||
|
||
### Импорт YAML
|
||
|
||
1. YAML валидируется.
|
||
2. Определяется create или update сценарий.
|
||
3. Создается новая запись в `operation_versions`.
|
||
4. При необходимости создается запись в `yaml_import_jobs`.
|
||
|
||
## 14. Что не должно храниться в БД в таком виде
|
||
|
||
- секреты в открытом виде;
|
||
- runtime cache;
|
||
- скомпилированные adapter clients;
|
||
- невалидированные черновики, не приводимые к доменной модели.
|
||
|
||
## 15. Практический итог
|
||
|
||
Для MVP рекомендован такой подход:
|
||
|
||
- `operations` - стабильная идентичность;
|
||
- `operation_versions` - полные version snapshots;
|
||
- `published_operations` - текущая активная версия;
|
||
- `operation_samples` и `descriptors` - внешние артефакты;
|
||
- `auth_profiles` - переиспользуемая внешняя аутентификация;
|
||
- `yaml_import_jobs` - журнал импортов.
|
||
|
||
Эта схема хорошо ложится на `sqlx`, не требует избыточной нормализации и соответствует JSON-oriented модели домена.
|