заведён реестр префиксов, правила канона перенумерованы

- идентификатор правила теперь `<ПРЕФИКС>-<номер>` вместо `R<номер>`:
  префикс уникален по всему канону, поэтому ссылка больше не требует пути
  к файлу и не зависит от того, на какой оси файл лежит
- префикс выбирается под файл, а не выводится по формуле, и хранится в
  conventions/prefixes.toml вместе с выбывшими; номера сохранены один в
  один вместе с дырами
This commit is contained in:
av
2026-07-25 20:55:37 +03:00
parent 972627f641
commit dcdd92230b
13 changed files with 552 additions and 470 deletions
+26 -22
View File
@@ -1,3 +1,7 @@
---
prefix: KEYS
---
# Идентификаторы сущностей
Как выбираются и как выглядят первичные ключи сущностей. Форма записи —
@@ -14,7 +18,7 @@
## Правила
### R1. Первичный ключ новой сущности — ULID
### KEYS-1. Первичный ключ новой сущности — ULID
**ДОЛЖЕН.** Новая сущность получает сортируемый строковый идентификатор,
который порождает приложение, — во **всех** таблицах, включая те, что
@@ -31,12 +35,12 @@
Второй довод дешевле, но важнее в быту: одна ментальная модель избавляет от
спора при заведении каждой таблицы и делает идентификатор **глобальным**
уникальным across таблиц, а не только внутри своей. На этом держится
корреляция по логам (R7).
корреляция по логам (KEYS-7).
Правило про **сгенерированные суррогатные** ключи. Естественные и составные
ключи у таблиц-деталей (R6) — третья категория, они допустимы всегда.
ключи у таблиц-деталей (KEYS-6) — третья категория, они допустимы всегда.
### R2. Идентификатор генерирует приложение, а не база
### KEYS-2. Идентификатор генерирует приложение, а не база
**ДОЛЖЕН.** Значение ключа известно до вставки строки.
@@ -46,26 +50,26 @@
и достраивать связи вторым проходом, либо иметь два источника истины о
моменте создания.
### R3. Генерация и разбор идентификаторов — в единственной точке
### KEYS-3. Генерация и разбор идентификаторов — в единственной точке
**ДОЛЖЕН.** Один модуль порождает идентификаторы, он же их разбирает.
Самодельных генераторов и парсеров в коде нет.
**Почему.** Нормализация регистра (R4) и проверка формата обязаны
**Почему.** Нормализация регистра (KEYS-4) и проверка формата обязаны
применяться ко всем идентификаторам без исключения. Любая вторая точка
входа рано или поздно окажется той, где нормализацию забыли, — и дефект
проявится не там, где создан.
### R4. Канонический вид — нижний регистр
### KEYS-4. Канонический вид — нижний регистр
**ДОЛЖЕН.** Идентификаторы порождаются и хранятся в нижнем регистре.
**Почему.** Сравнение строк в базе обычно побайтовое, поэтому регистр — не
косметика, а корректность поиска. Спецификация ULID канонизирует **верхний**
регистр, и библиотеки по умолчанию отдают именно его: без единой точки (R3)
регистр, и библиотеки по умолчанию отдают именно его: без единой точки (KEYS-3)
разный регистр появится в базе сам собой.
### R5. Внешний идентификатор разбирается до обращения к базе
### KEYS-5. Внешний идентификатор разбирается до обращения к базе
**ДОЛЖЕН.** Значение, пришедшее снаружи, проходит разбор и нормализацию
раньше, чем по нему делается запрос. Реакция на неудачный разбор зависит от
@@ -73,28 +77,28 @@
| № | Откуда пришёл | Разбор не удался → |
|---|---|---|
| R5.1 | путь или query URL | «не найдено» без обращения к хранилищу |
| R5.2 | собственная форма, данные кнопки | «некорректный ввод» либо «элемент устарел» |
| KEYS-5.1 | путь или query URL | «не найдено» без обращения к хранилищу |
| KEYS-5.2 | собственная форма, данные кнопки | «некорректный ввод» либо «элемент устарел» |
**Почему.** Синтаксически невалидное значение не может соответствовать
записи, поэтому поход в базу за ним — заведомо холостой; отсекая его на
границе, мы дёшево снимаем целый класс мусорного трафика.
Разделение R5.1 и R5.2 нужно, потому что источники значат разное. Мусор в
URL — это чужая или протухшая ссылка, и «не найдено» описывает ситуацию
точно. Мусор из собственной формы — это баг интерфейса или устаревший
экран; ответ «не найдено» здесь скрывает дефект и лишает диагностики
единственный момент, когда он заметен.
Разделение KEYS-5.1 и KEYS-5.2 нужно, потому что источники значат разное.
Мусор в URL — это чужая или протухшая ссылка, и «не найдено» описывает
ситуацию точно. Мусор из собственной формы — это баг интерфейса или
устаревший экран; ответ «не найдено» здесь скрывает дефект и лишает
диагностики единственный момент, когда он заметен.
Таблица перечисляет **внешние** источники — те, откуда значение приходит
вместе с запросом, и правило говорит, что отдать в ответ. Идентификатор из
конфигурации, из собственной базы или из фикстуры сюда не относится: он
ничего не отдаёт наружу, а его невалидность означает, что сломано у нас.
Формат идентификатора в конфигурации проверяется на старте
(`arch/config.md` R18), невалидное значение в собственной базе — нарушенный
инвариант единой точки (R3).
(`CONF-18`), невалидное значение в собственной базе — нарушенный
инвариант единой точки (KEYS-3).
### R6. У таблиц-деталей допустим естественный или составной ключ
### KEYS-6. У таблиц-деталей допустим естественный или составной ключ
**ДОПУСКАЕТСЯ.** Когда ключ есть по природе данных, отдельный
сгенерированный идентификатор не заводится.
@@ -104,10 +108,10 @@ URL — это чужая или протухшая ссылка, и «не на
лишний вопрос «по какому из них искать» на каждом запросе. Дополнительной
информации он не несёт.
### R7. Прочие генерируемые идентификаторы — через ту же точку
### KEYS-7. Прочие генерируемые идентификаторы — через ту же точку
**ДОЛЖЕН.** Идентификаторы, не являющиеся первичными ключами (батч,
задание, корреляционный ключ), порождаются тем же модулем (R3) и в том же
задание, корреляционный ключ), порождаются тем же модулем (KEYS-3) и в том же
формате.
**Почему.** Единый формат делает работающим главный побочный эффект
@@ -118,7 +122,7 @@ URL — это чужая или протухшая ссылка, и «не на
## Почему ULID, а не UUID
R1 требует **сортируемый** строковый идентификатор. UUIDv4 не
KEYS-1 требует **сортируемый** строковый идентификатор. UUIDv4 не
сортируется по времени вовсе. UUIDv7 (RFC 9562) сортируется — и против него
остаются два довода: 36 символов против 26 и дефисы, из-за которых
идентификатор не берётся ни двойным кликом, ни `grep`-ом как одно слово.