惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MongoDB | Blog
MongoDB | Blog
IT之家
IT之家
J
Java Code Geeks
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Recent Announcements
Recent Announcements
博客园 - 三生石上(FineUI控件)
博客园_首页
MyScale Blog
MyScale Blog
腾讯CDC
I
InfoQ
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
人人都是产品经理
人人都是产品经理
Vercel News
Vercel News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
量子位
爱范儿
爱范儿
U
Unit 42
aimingoo的专栏
aimingoo的专栏
B
Blog RSS Feed
云风的 BLOG
云风的 BLOG
M
MIT News - Artificial intelligence
A
About on SuperTechFans
T
The Blog of Author Tim Ferriss
Blog — PlanetScale
Blog — PlanetScale
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Engineering at Meta
Engineering at Meta
博客园 - 叶小钗
小众软件
小众软件
Jina AI
Jina AI
Hugging Face - Blog
Hugging Face - Blog
Google DeepMind News
Google DeepMind News
The Cloudflare Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
D
Docker
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园 - 【当耐特】
博客园 - Franky
H
Help Net Security
Stack Overflow Blog
Stack Overflow Blog
阮一峰的网络日志
阮一峰的网络日志
C
Check Point Blog
C
CERT Recently Published Vulnerability Notes
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Cisco Talos Blog
Cisco Talos Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
I
Intezer
Latest news
Latest news
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - 司徒正美
Microsoft Security Blog
Microsoft Security Blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
ID, token, UUID и slug: в чём разница и почему их нельзя мешать
Михаил Миронов · 2026-06-16 · via Все публикации подряд на Хабре

В каждом бэкенде наступает момент, когда рядом появляются id, uuid, slug, token, request_id и прочие похожие строки. Выглядят они одинаково, но отвечают за совершенно разные вещи:

curl https://api.example.com/users/123
curl https://api.example.com/projects/550e8400-e29b-41d4-a716-446655440000
curl https://example.com/articles/objectid-protiv-uuid
curl -X POST 'https://example.com/password/reset?token=...'

Ну да, тут id. Тут тоже id. Тут какой-то token, но он тоже строка. Тут slug, по нему же можно найти статью. Тут UUID, значит вроде безопасно. Тут JWT, значит пользователь авторизован.

А потом через полгода выясняется, что:

  • ссылку сброса пароля сделали через user_id;

  • UUID в URL начинают воспринимать как защиту от доступа;

  • slug начинают считать вечной айдишкой;

  • API-ключ хранится в базе открытым текстом;

  • JWT живёт как вечная сессия;

  • reset-token спокойно лежит в логах, APM и истории прокси.

Не потому что разработчики тупые. Обычно нет. Просто модель данных начала врать: одинаковый тип данных стали воспринимать как одинаковую ответственность.

Эта статья не про вечный спор “UUID или integer”. Это меньшая и довольно скучная часть проблемы. Настоящий вопрос другой: что именно вы сейчас держите в руках — идентификатор, публичную ссылку, секрет, токен доступа или человекочитаемый адрес?

Когда эти роли смешивают, получается архитектура, где UUID используют как безопасность, slug — как первичный ключ, reset-token — как user_id, а JWT — как вечную сессию. Система ещё работает, но уже начинает тихо врать сама себе, а значит создавать технический долг.

ID, token, UUID и slug — разные задачи

ID, token, UUID и slug — разные задачи

Сначала роль, потом формат

Самая частая ошибка начинается не с выбора UUID или integer.
Она начинается раньше — когда команда не отвечает на простой вопрос: что это значение должно делать?

Одна и та же строка может быть чем угодно:

123
550e8400-e29b-41d4-a716-446655440000
usr_01J2Y4K7YQ7V8K9W2V7R4D5H3A
id-token-uuid-slug
pYp9cBW9wI8o4ZmU3uY1L2zYqR8b3S...
req_01J2Y51A8ZY2J1X9V7E6D4C3B2

Но архитектурно это разные сущности.

У них разная стабильность, разный срок жизни, разная видимость снаружи, разные правила хранения, разная цена утечки, даже разные причины для изменения.

Поэтому вопрос “какой тип использовать?” вторичный.
Сначала надо понять:

  • это значение должно жить годами или умереть через 15 минут?

  • его можно показывать пользователю или оно должно оставаться секретом?

  • оно участвует в связях между таблицами или только красиво выглядит в URL?

  • оно должно пережить миграцию базы?

  • по нему человек будет искать сущность в саппорте?

  • его можно безопасно логировать?

И только потом выбирать формат: bigint, UUID, ObjectId, slug, случайный токен или что-то своё.

ID: стабильный идентификатор сущности

ID нужен, чтобы отличить одну сущность от другой.

{
  "id": 123,
  "email": "alice@example.com",
  "created_at": "2026-06-16T10:15:00Z"
}

Или так:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "email": "alice@example.com",
  "created_at": "2026-06-16T10:15:00Z"
}

Ключевой момент: ID — это про идентичность сущности, а не про способ доступа к ней.

Хороший ID обычно:

  • стабилен;

  • уникален в своём пространстве;

  • хорошо индексируется;

  • не меняется из-за переименования, смены email, заголовка или статуса;

  • может участвовать в связях между сущностями.

Например, пользователь может сменить email, но сам пользователь от этого не должен резко стать другой сущностью.
Статья может сменить заголовок, но комментарии, лайки, история публикации и права редакторов должны продолжать ссылаться на ту же статью.

Вот здесь ID и нужен: он держит идентичность, пока остальные атрибуты спокойно меняются.

Поэтому строить ID из изменяемых данных — почти всегда плохая идея.

{
  "id": "alice@example.com"
}

Сегодня это выглядит удобно. Завтра пользователь меняет email, и система начинает делать вид, что перед ней новый человек. Плохо? Очень.

email может быть логином. slug может быть адресом. username может быть публичным именем. Но всё это не обязано быть главным идентификатором сущности.

UUID — это не отдельный смысл, а формат ID

UUID часто обсуждают так, будто это самостоятельная архитектурная роль.

550e8400-e29b-41d4-a716-446655440000

Но UUID — это формат значения. Он не говорит, зачем это значение существует.

UUIDv4 обычно выбирают, когда нужен случайный идентификатор, который можно генерировать без центрального счётчика. UUIDv7 — когда хочется сохранить тот же формат, но получить отсортированные по времени значения, а как следствие и более производительную запись с точки зрения индексов.

Это полезные свойства формата, но они не отвечают на главный вопрос: что именно вы моделируете?

Проблемы начинаются, когда команда видит UUID и перестаёт думать о роли значения. UUID длинный и выглядит серьёзно, но это всё ещё просто формат.

UUID — это формат, а не роль.

UUID — это формат, а не роль.

Внутренний ID и публичный ID — разные контракты

Не каждый внутренний ID обязан торчать наружу.

Внутри базы может быть так:

{
  "id": 123456,
  "public_id": "usr_01J2Y4K7YQ7V8K9W2V7R4D5H3A",
  "email": "alice@example.com",
  "created_at": "2026-06-12T10:15:00Z"
}

А наружу можно отдавать так:

{
  "public_id": "usr_01J2Y4K7YQ7V8K9W2V7R4D5H3A",
  "email": "alice@example.com"
}

Это не обязательное правило на каждый проект. Для некоторых проектов отдельный public_id может быть лишней церемонией, которая ощутимого профита и не даст.
Но если API становится внешним контрактом, то разделение быстро начинает окупаться.

Внутренний ID оптимизирует хранение и связи.
Публичный ID стабилизирует внешний мир: API, события, интеграции, ссылки, тикеты поддержки, экспорт данных.

id можно поменять вместе с базой, способом шардинга или внутренней моделью хранения.
public_id лучше не менять без очень веской причины, потому что на него уже могли завязаться клиенты, интеграции, webhooks и чужие логи.

Префиксы вроде usr_, ord_, inv_ не делают значение безопаснее. Но они делают систему удобнее для людей.

usr_01J2Y4K7YQ7V8K9W2V7R4D5H3A
ord_01J2Y4NDZJ4ZMK8H8EVNQX3JWR
inv_01J2Y4P0A1TRR4E0VD7J4FPX5K

Когда такой ID прилетает в тикет поддержки, уже видно, что это за сущность. Не надо будет каждый раз гадать на кофейной гуще.

Главная идея простая: внутренний ID принадлежит вашей реализации, публичный ID принадлежит вашему контракту.
Иногда это оверхед, но далеко не всегда.

Внутренний ID и публичный ID — разные контракты

Внутренний ID и публичный ID — разные контракты

Slug — это адрес для людей, а не идентичность сущности

slug нужен, чтобы URL можно было прочитать глазами.

curl https://example.com/articles/id-token-uuid-slug

Он хорош для публикаций, профилей, категорий, документации, лендингов и всего, что человек может увидеть, скопировать и отправить в чат.

Но slug живёт в мире текста, а текст имеет свойство меняться.
Название статьи поменяли, SEO поправили, компания переименовала продукт, или автор решил, что старый заголовок был унылым.

Если slug был просто адресом — ничего страшного. Можно хранить историю и делать редиректы.

{
  "article_id": 7421,
  "old_slug": "id-token-uuid-slug",
  "new_slug": "identifiers-tokens-and-slugs",
  "created_at": "2026-06-12T10:00:00Z"
}

Но если slug стал идентичностью сущности, начинается боль.

{
  "article_slug": "id-token-uuid-slug",
  "comment": "..."
}

Лучше держать связь через стабильный ID, а slug оставить там, где ему место: на границе с человеком.

Slug — для людей, ID — для системы

Slug — для людей, ID — для системы.

Token — это значение с миссией и сроком смерти

Токен отличается от ID не длиной и не тем, что выглядит более случайно.
Токен отличается назначением.

ID указывает на сущность.
token даёт возможность выполнить конкретное действие или подтвердить конкретный контекст.

Например, reset-token:

curl -X POST https://example.com/password-reset/consume \
  -d '{"token":"pYp9cBW9wI8o4ZmU3uY1L2zYqR8b3S..."}'

У такого значения должна быть история:

  • зачем оно создано;

  • для какой сущности;

  • когда истекает;

  • можно ли использовать его повторно;

  • отозвано ли оно;

  • как оно хранится.

Поэтому запись токена обычно выглядит не как ещё один ID, а как отдельная сущность:

{
  "token_hash": "3b7f9c7a7c1d...",
  "user_id": 123,
  "purpose": "password_reset",
  "expires_at": "2026-06-12T10:30:00Z",
  "used_at": null,
  "created_at": "2026-06-12T10:00:00Z"
}

Обратите внимание: в базе лежит token_hash, а не сырой токен.

Это не потому что так красивее, просто токен — это credential. Если он утёк, то его можно будет использовать в плохих целях.
Поэтому хранить его как обычное поле token рядом с user_id — плохая привычка. Это удар по безопасности.

Сырой токен показывается один раз: в письме, ссылке или ответе API. Потом система работает с его хэшем, без постоянного хранения, без отображений в логах.

Вот здесь и видна разница.

user_id должен жить долго и стабильно указывать на пользователя.
reset_token должен выполнить одну задачу и исчезнуть из активной жизни.

Если значение должно умереть, быть отозвано, иметь конкретное назначение или лимит использования — это уже пахнет токеном, а не идентификатором.

В памятке у OWASP в Forgot Password Cheat Sheet похожая логика: reset-token должен быть случайным, одноразовым и ограниченным по времени.

Но в рамках этой статьи важнее не security-чеклист, а сама модель: токен — это не айдишка подлиннее, а отдельный объект с жизненным циклом.

API-key — это секрет, а не имя клиента

API-key часто живёт рядом с client_id, и из-за этого их любят смешивать.

curl -H 'X-API-Key: 9f3c0e7d1a2b...' https://api.example.com/orders

Но client_id и api_key отвечают на разные вопросы.

client_id: кто этот клиент?
api_key: чем он доказывает, что имеет право использовать интеграцию?

Плохая модель:

{
  "client_id": "partner-crm",
  "api_key": "9f3c0e7d1a2b...",
  "enabled": true
}

Она выглядит компактно, но делает секрет уязвимым и не отвечает на вопросы о ротации, дате создания, дате отзыва и прочем.

Более здоровая модель позволяет уместить все эти вещи:

{
  "id": 981,
  "client_id": "partner-crm",
  "key_prefix": "tkn_7xQd",
  "key_hash": "f0c3a2...",
  "expires_at": null,
  "revoked_at": null,
  "created_at": "2026-06-12T10:00:00Z",
  "last_used_at": "2026-06-12T10:10:00Z"
}

С помощьюclient_id можно выяснять, какому клиенту принадлежит данный ключ.
А key_prefix можно показывать клиенту, чтобы он понимал, какой ключ он смотрит.

Полный api_key лучше не показывать повторно и не хранить открытым текстом. Это та же ошибка, о которой я писал в блоке про token.

Session ID и JWT: два разных способа нести контекст

Session ID — это указатель на серверное состояние.

curl -H 'Cookie: session_id=s%3A7xQd9z...' \  https://example.com/account

Сам по себе session ID обычно ничего не объясняет. Он просто помогает серверу найти сессию.

{
  "session_hash": "b24a...",
  "user_id": 123,
  "created_at": "2026-06-12T10:00:00Z",
  "expires_at": "2026-06-19T10:00:00Z",
  "revoked_at": null
}

В этом и смысл: session ID непрозрачен. Вся полезная информация живёт на сервере.

JWT устроен иначе.

curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' \
  https://api.example.com/orders

Согласно RFC 7519, JWT — это компактный способ передавать соглашения между сторонами.

{
  "sub": "usr_01J2Y4K7YQ7V8K9W2V7R4D5H3A",
  "iss": "https://auth.example.com",
  "aud": "api.example.com",
  "exp": 1781268000,
  "scope": "orders:read"
}

Здесь важно не устроить религиозную войну “JWT против сессий”. Это отдельная тема.
Для этой статьи достаточно другого вывода:

session_id — это идентификатор серверной сессии.
jwt — это токен с значениями.
sub внутри JWT — это идентификатор субъекта.

Это три разные роли. Даже если в коде они все проходят как string.

Если хочется глубже именно про внутрянку JWT, есть RFC 8725: JSON Web Token Best Current Practices. Но тащить всю эту тему сюда не стоит.

Request ID — это ID события, а не бизнес-сущности

request_id, trace_id, correlation_id нужны, чтобы связывать события в логах.

curl -H 'X-Request-ID: req_01J2Y51A8ZY2J1X9V7E6D4C3B2' \
  https://api.example.com/orders

В логах:

{
  "request_id": "req_01J2Y51A8ZY2J1X9V7E6D4C3B2",
  "method": "POST",
  "path": "/api/orders",
  "status": 201,
  "duration_ms": 84
}

Это значение помогает ответить на вопрос: “что происходило в рамках этого запроса?”

Оно не должно становиться ID заказа, пользователя, файла или операции оплаты. Это фундаментальный принцип.

Да, оно тоже уникальное, тоже строка и его удобно прокидывать между сервисами.
Но его роль — наблюдаемость, а не бизнес-модель.

Если по request_id внезапно начинают искать заказ в доменной логике, то что-то явно идет не так. Не катастрофа сама по себе, но хороший индикатор, что границы начали расползаться.

Название поля должно говорить о роли, а не о формате

Плохое имя прячет смысл.

{
  "uuid": "550e8400-e29b-41d4-a716-446655440000"
}

Что это? ID пользователя? ID заказа? ID события?
Формат есть, а роли нет. Непонятно.

Лучше:

{
  "user_id": "550e8400-e29b-41d4-a716-446655440000"
}

Или вообще public_user_id, если это внешний контракт.

То же самое с токенами. Плохо называть поле просто id или token, если сама модель не предполагает назначения. Важно сходу иметь ответ на вопрос: "К чему это поле относится и какую проблему решает?"
reset_token, reset_token_hash — намного лучше.

Чем меньше приходится гадать по названию поля, тем меньше шансов, что через год его начнут использовать не по назначению.

Сначала роль — потом формат

Сначала роль — потом формат.

Одна таблица, чтобы не мешать всё в кашу

Значение

Главный вопрос

Живёт долго?

Можно показывать?

Можно менять?

id

Какая это сущность?

Да

Зависит от границы

Почти никогда

public_id

Как сослаться на сущность снаружи?

Да

Да

Очень нежелательно

UUID

В каком формате записан ID?

Зависит от роли

Зависит от роли

Зависит от роли

slug

Как сделать адрес читаемым?

Может меняться

Да

Да, с историей и редиректами

token

Почему это действие можно выполнить?

Обычно нет

Только в момент выдачи

Скорее отозвать/перевыпустить

api_key

Чем интеграция доказывает доступ?

Иногда

Нет, кроме префикса

Через rotation

session_id

Где серверная сессия?

Нет

Только клиенту-владельцу

Отозвать/создать заново

request_id

Как найти запрос в логах?

Пока живут логи/трейсы

Да

Нет смысла менять

Удобная проверка на здравый смысл: если вы не можете заполнить такую таблицу для значения, которое добавляете в API, то вы пока не спроектировали модель. Вы просто выбрали строку.

Что почитать по матчасти

Не чтобы превратить статью в список памяток RFC, а чтобы было куда ткнуть, когда спор снова скатится в “мне кажется”.

Вывод

Проблема не в том, что кто-то выбрал UUID вместо integer.
Проблема начинается, когда формат значения начинают путать с его ролью.

Они могут быть строками. Могут быть UUID. Могут выглядеть одинаково в JSON и лететь через один и тот же HTTP.
Но в системе они живут по разным законам.

Сначала роль. Потом срок жизни. Потом границы видимости. Потом хранение. И только потом формат.

Иначе получается классическая магия: всё называется id, всё вроде работает, а потом через год никто не понимает, почему одно поле нельзя переименовать без миграции, другое нельзя логировать, третье нельзя менять, а четвёртое вообще оказалось секретом.

Михаил Миронов, Табрика co-founder.