Зарисовка: а может к черту любовь ролевую модель?
Привет, Хабр! Писать статьи — дело приятное, но только если нет на плечах релиза. Релиз оказался марафоном на месяцы, где каждый день мы жили задачами и доработками. Мы делились на три фронта: кто-то закрывал критические баги («баг-фиксеры»), кто-то добивал бизнес-логику («бизнес-логеры»), а кто-то всерьез отрабатывал план «Б» — ставил свечи за успешный релиз («молитвенники за прод»). Играли мы на разных уровнях, но финальный босс у всех был один: система, которую мы героически толкали в ПРОД, как кота в переноску: и он не хочет, и нам страшно.
Зарисовка: релиз как он есть. Никто не хочет, но все идут
Но как бы там ни было, сегодня на ПРОДе живет большая система. Прям такая, что, если бы она была организмом, у нее были бы печень, почки и амбулаторная карта в Сфере Знания.
Пользователи — сотни сотрудников. Система — новая, кнопки — непонятные, интерфейс — как квартира после переезда: ты вроде дома, но даже чайник включить страшно.
И вот представьте: в этой «квартире» все двери распахнуты настежь. Любой может зайти куда угодно, нажать любую кнопку, открыть любой экран. Кнопки, которые лучше не трогать, экраны, куда и разработчик-то без инструктажа не сунется… Получился цифровой «чулан Моники» — хаос, который мы срочно должны были привести в порядок.
Зарисовка: «А я могу сюда зайти?» — «Можешь, но не надо».
Решение было очевидным: нужна ролевая модель.
По плану ролевую модель — разграничение видимости интерфейсов и данных на стороне БД — мы должны были выкатить через пару недель после запуска. Но в мире, где перечень техдолгов меняется быстрее, чем погода в Калининграде, пришлось действовать иначе. В итоге, бочком-бочком, мы затолкали ее в боевой релиз буквально на финишной прямой.
Зарисовка: релизный спринт в лицах.
В этой статье расскажу, как мы построили интерфейсную часть ролевой модели: с пермишенами, ролями, пользователями и — конечно — шутками, потому что без них системный анализ слишком серьезен, а жизнь — слишком коротка.
Сцена 1. Режим бога: включен по умолчанию
Зарисовка: когда кнопок больше, чем идей, что с ними делать.
Еще до релиза мы давали фокус-группе из представителей заказчика тестировать интерфейс. Каждый раз это выглядело как мем с Винсентом Вегой из «Криминального чтива» — он стоит с пальто, озирается и явно не понимает, куда попал. Пользователи заходили в систему и оказывались в «режиме бога»: доступно всё — от критичных кнопок до внутренних экранов, которые они в реальной жизни никогда бы не использовали. На встречах неизменно звучал один и тот же вопрос: «А что будет делать обычный пользователь, которому половина этих функций вообще не нужна?»
Ситуация изменилась только с выходом на ПРОД, когда вместе с основным релизом мы включили ролевую модель. Она сразу отрезала лишнее: теперь кто-то видит аккуратный набор нужных инструментов, кто-то — только одну кнопку, а у кого-то и вовсе пустой экран. Никакой лишней информации, никакого хаоса — каждый получает ровно то, что ему нужно.
Сцена 2. Доступ — это не демократия
Зарисовка: кнопок нет, но вы держитесь.
Представим экран «Функции системы». На нем есть список доступных функций, каждая из которых что-то делает (от выгрузки данных до запуска автоматических расчетов). После каждого запуска формируется отчет — лог выполнения, чтобы было понятно, чем функция занималась и чем все закончилось.
У нас есть три роли:
Оператор — видит все элементы интерфейса на этом экране и может запускать функции, просматривать их логи, повторно запускать при необходимости.
Аудитор — видит список функций и их логи, но никаких кнопок запуска у него нет: для его роли они просто отсутствуют на интерфейсе, как будто их никогда и не существовало.
Админ — отвечает за настройку системы в целом, а этот экран ему ни к чему, поэтому он его вообще не видит.
По итогу: оператор работает на экране «от и до», аудитор — только наблюдает, админ — даже не подозревает, что экран существует.
Так как же это реализовать? Все просто — через пермишены.
Сцена 3. Пермишены: кирпичики ролевой модели
Каждый элемент интерфейса у нас связан с permission (по-нашему — «доступ»). И не «в целом к экрану», а буквально к каждому действию. Например, для нашего экрана «Функции системы» это может выглядеть так:
сам экран с функциями = FunctionsScreenView;
кнопка запуска конкретной функции = FunctionRun;
просмотр логов выполнения = FunctionLogsView.
Чтобы все было предсказуемо, мы завели головной пермишен на каждый экран (FunctionsScreenView). Его нет — экран невидим. Даже если все дочерние пермишены у тебя есть — ничего не отрисуется. Тут бы Вилли Вонка сказал: «Смотри сколько хочешь — не получишь!»
Что такое permission?
Если просто: это маркер, который говорит системе, что конкретный пользователь имеет право выполнить определенное действие или увидеть конкретный элемент интерфейса. Пермишены — это кирпичики, из которых строится ролевая модель.
В реальности мы отдаем фронту не дерево сложной вложенности, а плоский список разрешений. Например, для наших трех ролей это может быть так:
{
"Operator": [
"FunctionsScreenView",
"FunctionRun",
"FunctionLogsView"
],
"Auditor": [
"FunctionsScreenView",
"FunctionLogsView"
],
"Admin": []
}
Фронт получает этот список и уже сам решает, что показать: кнопка запуска есть — отрисовываем, нет — даже пустого места не будет.
Сцена 4. Архитектурный backstage
Ролевая модель нашей системы живет в пяти таблицах. На первый взгляд они простые, но именно от их связей зависит, что увидит пользователь на экране и сможет ли он это запустить.
Таблица |
Назначение |
Источник данных |
users |
Список всех пользователей системы. |
Приходит из внешней системы |
roles |
Набор ролей с кодами и описаниями. |
Приходит из внешней системы |
permissions |
Полный перечень действий и экранов, доступных в системе. |
Формируем и поддерживаем вручную |
role_permission |
Связь между ролями и их доступами (permissions). |
Заполняем вручную |
user_role |
Соответствие пользователя его роли. |
Приходит из внешней системы |
Что здесь понимается под агрегатами
users и roles — агрегаты справочников внешних систем.
permissions — агрегат внутреннего справочника действий (сущности, которые мы сами придумываем и поддерживаем).
role_permission и user_role — агрегаты связей между справочниками.
ER-диаграмма

Почему у нас «один пользователь — одна роль»
Это не случайное ограничение, а осознанное архитектурное решение. Если разрешить несколько ролей для одного пользователя, появятся конфликтующие пермишены.
Пример:
Роль «Оператор» — видит экран и кнопку запуска.
Роль «Аудитор» — видит экран, но без кнопки запуска.
Если дать обе роли, что показать на фронте? Кнопку? Тогда зачем ограничение для аудитора?
Решение:
Нужны расширенные права — создаем новую роль с полным набором необходимых пермишенов.
Это исключает сложную приоритетную логику, которую потом было бы трудно поддерживать.
Главное правило ролевой модели
Если функционал не должен быть доступен пользователю, он не должен даже знать о его существовании.
Так мы убираем риск «зайти куда не надо» просто из любопытства.
SQL: получение карточки пользователя и его пермишенов
SELECT
u.id,
u.name,
u.email,
p.code AS permission_code
FROM users u
JOIN user_role ur ON ur.user_id = u.id
JOIN role_permission rp ON rp.role_id = ur.role_id
JOIN permissions p ON p.id = rp.permission_id
WHERE u.id = :userId
В результате мы получаем данные о пользователе и список всех его пермишенов.
Пример ответа API
GET /permissions/{userId}
Authorization: Bearer <токен>
Response:
{
"user": {
"id": 42,
"name": "Иван Иванов",
"email": "ivan@example.com",
"role": "OPERATOR"
},
"permissions": [
"FunctionsScreenView",
"FunctionRun",
"FunctionLogsView"
]
}
Как это используется на фронте:
По объекту user отрисовывается карточка пользователя.
По массиву permissions фронт решает, какие кнопки и экраны показать.
Сцена 5. JSON-меню для твоего интерфейса
Мы уже разобрали, как в базе хранятся пользователи, роли и пермишены, и даже написали SQL, который достает их в одном запросе.
Следующий шаг — упаковать эти данные в удобный формат и отправить на фронт через API.
Процесс работы API:
Аутентификация — пользователь логинится, API извлекает его userId из токена.
Определение роли — по таблице user_role находим роль пользователя.
Получение пермишенов — по связке role_permission → permissions собираем коды доступов.
-
Формирование ответа:
объект user с информацией о пользователе (чтобы фронт мог отрисовать карточку или подпись в шапке),
массив permissions, по которому фронт понимает, что можно показывать.
Отправка на фронт — ответ в JSON.
Пример запроса:
GET /permissions/{userId}
Authorization: Bearer <токен>
Пример ответа:
{
"user": {
"id": 42,
"name": "Иван Иванов",
"email": "ivan@example.com",
"role": "OPERATOR"
},
"permissions": [
"FunctionsScreenView",
"FunctionRun",
"FunctionLogsView"
]
}
Почему такой подход удобен:
Фронт получает готовое «меню возможностей» без лишней логики.
Любое изменение доступов в БД моментально отражается в интерфейсе, без деплоя фронта.
Нет «призрачных кнопок» — если в массиве нет пермишена, элемент просто не отрисовывается.
Выполняется главное правило ролевой модели: если чего-то нет в списке, пользователь даже не узнает, что такая функция существует.
Советы, проверенные болью и продакшеном
Всегда начинай с головного пермишена.
Это твой главный «входной билет» к экрану. Если его нет — экран не отрисуется, даже если у пользователя есть доступы к отдельным кнопкам внутри. Так ты защищаешь систему от случайных «обходов» и неожиданных путей доступа.Роль — не склад случайных пермишенов.
Пермишены должны соответствовать реальным действиям пользователя в системе. Не нужно добавлять «на всякий случай» — так только увеличивается зона риска и растет хаос. Четкая роль = предсказуемый интерфейс и поведение.Одно правило доступа — один пользователь.
Да, можно дать несколько ролей, но это рождает конфликты: одна разрешает, другая запрещает. Придется изобретать приоритеты, вес пермишенов и другие способы усложнить себе жизнь. Вместо этого создай расширенную роль с нужным набором прав.Отказ по умолчанию.
Нет пермишена — нет элемента. Даже пустого места. Если функционал не положен, пользователь не должен о нем знать. Любопытство — мощная сила, но не в продакшене.Пермишены должны быть осознанными.
Каждый код в permissions — это не случайная строчка в БД, а конкретное действие, за которое ты готов отвечать. Формируй этот список как часть архитектуры, а не как свалку «а давай еще вот это добавим».
Финал: от хаоса к прозрачности
Ролевая модель — это не только про безопасность, но и про удобство, когда она построена грамотно:
база данных хранит простые, но мощные связи,
API отдает фронту ровно то, что нужно,
интерфейс не перегружен лишними кнопками,
а пользователи не блуждают в режиме «угадай, что тут можно нажать».
Главное правило, которое стоит забить на внутренний баннер над рабочим местом:
Если доступ не положен — пользователь не должен даже знать, что функционал существует.
Так вы снизите количество случайных ошибок, упростите поддержку и, главное, убережете себя от ситуации, когда кто-то в пятницу вечером решает «просто посмотреть, что будет, если нажать».
Подсказка: ничего хорошего.
Спасибо, что дочитали статью до конца! Теперь моя очередь увидеть ваши истории: как вы строили ролевую модель и решали вопрос “видеть или не видеть”. Ну и парочку позитивных комментариев можно оставить — они, как пермишены, всегда повышают настроение.
OlegIct
в Oracle Database роль - сборник привилегий и других ролей. Ограничение роли назначить нельзя. Есть ощутимое на практике ограничение на число включённых ролей. Вероятно, неверно выбраны хабы: в СУБД всё не так, как кажется с middle tier