- 13 конвенций по осям arch / lang / stack / common; репозитории берут оттуда копии в свой docs/conventions/ и коммитят их у себя - conv — синхронизация копий: add / status / diff / pull / push, локальные регионы исключены из сравнения, поэтому расхождение не даёт шума
6.1 KiB
status
| status |
|---|
| рекомендуемая |
Идентификаторы сущностей
Как выбираются и как выглядят первичные ключи. Схема БД меняется тяжело, поэтому конвенция применяется к новым таблицам; существующие живут как есть и перечислены в отступлениях.
Условие применимости
Вопрос задаётся один раз на репозиторий, а не по таблицам:
Есть ли в приложении хотя бы одна сущность, которую адресуют извне — по id из URL, запроса API или callback-данных?
- Да → весь репозиторий на сортируемый строковый id, который генерирует приложение (ULID), включая внутренние таблицы.
- Ни одной → автоинкремент, и этого достаточно.
Критерий — именно адресация: снаружи по этому id возвращаются к системе. Не «id виден в логе» — туда рано или поздно попадает любой идентификатор, и по такому критерию вторая ветка была бы недостижима.
Почему квантор репозиторный, а не потабличный: внутренние сущности имеют привычку становиться внешними, и тогда целочисленный id утекает в URL задним числом. Плюс одна ментальная модель дешевле, чем спор при заведении каждой таблицы.
Что не является смешиванием. Запрет касается двух видов сгенерированных суррогатных ключей в одной базе. Естественные и составные ключи у таблиц-деталей — третья категория, они допустимы всегда (см. ниже).
Если 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 там — мёртвый вес, который ещё и создаёт второй способ адресовать ту же строку.
Прочие генерируемые идентификаторы (батчи, задания, корреляционные ключи) — через ту же единую точку: единый формат, сортируемость, корреляция в логах.
Связано
arch/time.md— метки времени тоже генерирует приложение, а не схема.