- 13 конвенций по осям arch / lang / stack / common; репозитории берут оттуда копии в свой docs/conventions/ и коммитят их у себя - conv — синхронизация копий: add / status / diff / pull / push, локальные регионы исключены из сравнения, поэтому расхождение не даёт шума
87 lines
6.1 KiB
Markdown
87 lines
6.1 KiB
Markdown
---
|
||
status: рекомендуемая
|
||
---
|
||
|
||
# Идентификаторы сущностей
|
||
|
||
Как выбираются и как выглядят первичные ключи. Схема БД меняется тяжело,
|
||
поэтому конвенция применяется **к новым таблицам**; существующие живут как
|
||
есть и перечислены в отступлениях.
|
||
|
||
## Условие применимости
|
||
|
||
Вопрос задаётся **один раз на репозиторий**, а не по таблицам:
|
||
|
||
> Есть ли в приложении хотя бы одна сущность, которую адресуют извне — по
|
||
> id из URL, запроса API или callback-данных?
|
||
>
|
||
> - **Да** → весь репозиторий на сортируемый строковый id, который
|
||
> генерирует приложение (ULID), включая внутренние таблицы.
|
||
> - **Ни одной** → автоинкремент, и этого достаточно.
|
||
|
||
Критерий — именно **адресация**: снаружи по этому id возвращаются к
|
||
системе. Не «id виден в логе» — туда рано или поздно попадает любой
|
||
идентификатор, и по такому критерию вторая ветка была бы недостижима.
|
||
|
||
Почему квантор репозиторный, а не потабличный: внутренние сущности имеют
|
||
привычку становиться внешними, и тогда целочисленный id утекает в URL
|
||
задним числом. Плюс одна ментальная модель дешевле, чем спор при заведении
|
||
каждой таблицы.
|
||
|
||
**Что не является смешиванием.** Запрет касается двух видов
|
||
*сгенерированных суррогатных* ключей в одной базе. Естественные и составные
|
||
ключи у таблиц-деталей — третья категория, они допустимы всегда (см. ниже).
|
||
|
||
<!-- local:решение -->
|
||
<!-- /local -->
|
||
|
||
## Если ULID
|
||
|
||
- **PK — TEXT ULID** (26 символов Crockford base32), генерируется
|
||
**приложением** в момент создания записи, а не БД.
|
||
- Почему не UUID: UUIDv4 не сортируется по времени. UUIDv7 (RFC 9562)
|
||
сортируется, и против него остаются два довода — 36 символов против 26 и
|
||
дефисы: без них `grep` и двойной клик берут id целиком.
|
||
- Сортировка по времени создания даёт `ORDER BY id` = хронология с
|
||
точностью до миллисекунды. Внутри одной миллисекунды порядок произволен,
|
||
если генератор не монотонный, — на хронологию событий это не влияет.
|
||
- Глобальная уникальность across таблиц даёт побочный, но важный эффект:
|
||
голый `grep` по id находит все записи сущности независимо от имени поля.
|
||
- **Единая точка генерации и разбора.** Один модуль генерирует id и один
|
||
разбирает; самодельных генераторов по коду нет.
|
||
|
||
## Канонический вид и границы
|
||
|
||
- Генерим и храним id в **нижнем регистре**. Сравнение строк в БД обычно
|
||
побайтовое, поэтому регистр — не косметика, а корректность. Спецификация
|
||
ULID канонизирует верхний регистр, и библиотеки по умолчанию отдают
|
||
именно его — нижний обеспечивает единая точка генерации, поэтому звать
|
||
библиотеку мимо неё нельзя.
|
||
- Любой пришедший снаружи id **обязательно** проходит разбор до запроса к
|
||
БД: он валидирует формат и нормализует регистр.
|
||
- Синтаксически невалидный id, которым **адресуют ресурс**, трактуем как
|
||
несуществующую сущность (404), **без похода в БД**: это и дешевле, и
|
||
убирает целый класс запросов с мусором. Невалидный id, пришедший из
|
||
собственной формы или кнопки, — не «не найдено», а некорректный ввод: там
|
||
это признак устаревшего интерфейса или бага, и маскировать его под 404
|
||
значит терять диагностику.
|
||
|
||
## Естественные и составные ключи — для деталей
|
||
|
||
У таблиц-деталей и связей допустим естественный или составной ключ вместо
|
||
сгенерированного, когда он есть по природе данных. Отдельный id там —
|
||
мёртвый вес, который ещё и создаёт второй способ адресовать ту же строку.
|
||
|
||
Прочие генерируемые идентификаторы (батчи, задания, корреляционные ключи) —
|
||
через ту же единую точку: единый формат, сортируемость, корреляция в логах.
|
||
|
||
<!-- local:отступления -->
|
||
<!-- /local -->
|
||
|
||
## Связано
|
||
|
||
- `arch/time.md` — метки времени тоже генерирует приложение, а не схема.
|
||
|
||
<!-- local:связано -->
|
||
<!-- /local -->
|