идентификатор новой сущности — всегда ULID
- R1 больше не ветвится по признаку внешней адресуемости: заранее отличить внутренние сущности, которые станут внешними, невозможно - целочисленный ключ остаётся у существующих схем и идёт вместе с AUTOINCREMENT — переиспользованный rowid молча наводит протухшую ссылку на другую строку (db-schema R12) - таблица R5 ограничена внешними источниками, регион «решение» убран
This commit is contained in:
@@ -8,42 +8,35 @@
|
||||
Схема базы меняется тяжело: таблица не переезжает от того, что её
|
||||
потрогали. Поэтому правила распространяются на **новые таблицы**;
|
||||
существующие живут как есть и перечисляются в отступлениях, причём этот
|
||||
список постоянный, а не список задач на дочистку.
|
||||
список постоянный, а не список задач на дочистку. Целочисленные ключи
|
||||
существующих приложений — именно такой случай: они не мигрируют, и правила
|
||||
их работы описаны в конвенции схемы, а не здесь.
|
||||
|
||||
## Правила
|
||||
|
||||
### R1. Вид первичного ключа выбирается один раз на репозиторий
|
||||
### R1. Первичный ключ новой сущности — ULID
|
||||
|
||||
**ДОЛЖЕН.** Репозиторий отвечает на один вопрос и держит ответ для всех
|
||||
своих таблиц:
|
||||
**ДОЛЖЕН.** Новая сущность получает сортируемый строковый идентификатор,
|
||||
который порождает приложение, — во **всех** таблицах, включая те, что
|
||||
снаружи не адресуются.
|
||||
|
||||
> Есть ли в приложении хотя бы одна сущность, которую адресуют **извне** —
|
||||
> по идентификатору из URL, запроса API или callback-данных?
|
||||
**Почему.** Ветвления здесь нет намеренно, хотя напрашивается: «эту
|
||||
сущность снаружи не адресуют, ей хватит целого числа». Внутренние сущности
|
||||
имеют привычку становиться внешними — и тогда целочисленный идентификатор
|
||||
утекает в URL задним числом, а миграция ключа на живых данных стоит
|
||||
несопоставимо дороже, чем взять строковый сразу. Заранее отличить те, с
|
||||
кем это случится, не получается: если бы получалось, они бы уже назывались
|
||||
внешними.
|
||||
|
||||
| № | Ответ | Вид ключа |
|
||||
|---|---|---|
|
||||
| R1.1 | да, хотя бы одна | сортируемый строковый идентификатор, который генерирует приложение (ULID) — во **всех** таблицах, включая внутренние |
|
||||
| R1.2 | ни одной | автоинкремент |
|
||||
Второй довод дешевле, но важнее в быту: одна ментальная модель избавляет от
|
||||
спора при заведении каждой таблицы и делает идентификатор **глобальным** —
|
||||
уникальным across таблиц, а не только внутри своей. На этом держится
|
||||
корреляция по логам (R7).
|
||||
|
||||
**Почему.** Квантор репозиторный, а не потабличный, по двум причинам.
|
||||
Внутренние сущности имеют привычку становиться внешними — и тогда
|
||||
целочисленный идентификатор утекает в URL задним числом, а миграция ключа
|
||||
на живых данных стоит несопоставимо дороже, чем взять строковый сразу.
|
||||
Вторая причина дешевле, но важнее в быту: одна ментальная модель избавляет
|
||||
от спора при заведении каждой таблицы.
|
||||
Правило про **сгенерированные суррогатные** ключи. Естественные и составные
|
||||
ключи у таблиц-деталей (R6) — третья категория, они допустимы всегда.
|
||||
|
||||
Критерий — именно **адресация**: снаружи по этому идентификатору
|
||||
возвращаются к системе. Не «виден в логе»: туда рано или поздно попадает
|
||||
любой идентификатор, и по такому критерию ветка R1.2 была бы недостижима.
|
||||
|
||||
Запрет смешивания касается двух видов **сгенерированных суррогатных**
|
||||
ключей. Естественные и составные ключи у таблиц-деталей (R6) — третья
|
||||
категория, они допустимы при любом ответе.
|
||||
|
||||
<!-- local:решение -->
|
||||
<!-- /local -->
|
||||
|
||||
### R2. При выборе R1.1 идентификатор генерирует приложение, а не база
|
||||
### R2. Идентификатор генерирует приложение, а не база
|
||||
|
||||
**ДОЛЖЕН.** Значение ключа известно до вставки строки.
|
||||
|
||||
@@ -93,6 +86,14 @@ URL — это чужая или протухшая ссылка, и «не на
|
||||
экран; ответ «не найдено» здесь скрывает дефект и лишает диагностики
|
||||
единственный момент, когда он заметен.
|
||||
|
||||
Таблица перечисляет **внешние** источники — те, откуда значение приходит
|
||||
вместе с запросом, и правило говорит, что отдать в ответ. Идентификатор из
|
||||
конфигурации, из собственной базы или из фикстуры сюда не относится: он
|
||||
ничего не отдаёт наружу, а его невалидность означает, что сломано у нас.
|
||||
Формат идентификатора в конфигурации проверяется на старте
|
||||
(`arch/config.md` R18), невалидное значение в собственной базе — нарушенный
|
||||
инвариант единой точки (R3).
|
||||
|
||||
### R6. У таблиц-деталей допустим естественный или составной ключ
|
||||
|
||||
**ДОПУСКАЕТСЯ.** Когда ключ есть по природе данных, отдельный
|
||||
@@ -117,7 +118,7 @@ URL — это чужая или протухшая ссылка, и «не на
|
||||
|
||||
## Почему ULID, а не UUID
|
||||
|
||||
Ветка R1.1 требует **сортируемый** строковый идентификатор. UUIDv4 не
|
||||
R1 требует **сортируемый** строковый идентификатор. UUIDv4 не
|
||||
сортируется по времени вовсе. UUIDv7 (RFC 9562) сортируется — и против него
|
||||
остаются два довода: 36 символов против 26 и дефисы, из-за которых
|
||||
идентификатор не берётся ни двойным кликом, ни `grep`-ом как одно слово.
|
||||
|
||||
Reference in New Issue
Block a user