Веб-версияОткрыть в Telegram

ПостНедавно я реализовал Multi Tenancy систему на FastAPI + SQLAlchemy и пересобрал её в небольшой демо проект.

22 июня 2026
P
Python Заметки
Недавно я реализовал Multi Tenancy систему на FastAPI + SQLAlchemy и пересобрал её в небольшой демо проект. Ключевые особенности: ▫️ Основной всего является сущность Компании. Юзеры могут подключаться к разным компаниям, но в один момент времени могут находиться только в одной (по условиям ТЗ). ▫️ Разделение данных через FK-ссылку всех ключевых бизнес-сущностей на ID компании. ▫️ ID компании задаётся при логине и хранится в JWT. При смене компании потребуется новый токен. ▫️ Контекст активируется через зависимость. ▫️ Фильтр применяется автоматически для любого запроса. Теперь подробней: 1️⃣ Логин Когда юзер логинится, он указывает ID компании. Этот ID зашивается в JWT. 2️⃣ Запрос Ключевое действие находится в функции get_current_user(...). Мы парсим JWT и достаем инфу о юзере и компании. Для текущей и дочерних корутин этого запроса объявляется контекст конкретной компании. Объявление контекста происходит через ContextVar (а вот и реальный пример их применения). Никакого указания ID проекта\магазина\сайта со стороны юзера и проброса его по всем функциям. Это локальное состояние конкретного запроса и его цепочки корутин. 3️⃣ Активация фильтра для SQL Реализация фильтра сделана штатными средствами SQLAlchemy - прослушивание ивента do_orm_execute и правка любого SQL запроса с with_loader_criteria. Фильтр реагирует только на модели с миксином CompanyMixin. Нужно только указать ID компании для филтра. Функция фильтрации add_tenant_filter(...) требует либо указать контекст, либо отключить фильтр. Если вдруг контекст не задан, то по умолчанию юзер просто получит ошибку BadRequestError. 4️⃣ Админка В get_current_user() указание контекста необязательно. Это сделано для того, чтобы мог залогиниться суперадмин. Ведь должен же кто-то управлять компаниями "сверху". В моем случае я просто не указываю контекст, но можно явно проверять, является ли юзер суперадмином. После логина админ тоже будет получать ошибки при обращении к юзерским сервисам, поэтому у него есть свои - админские сервисы через админские роуты. На админских роутах стоит депенденси с функцией bypass_company_filter(...). Это позволяет отключить фильтр и управлять любыми сущностями без ограничения. А так же депенденси пропускающий только админов. Так же есть один юзерский запрос с bypass_company_filter() - это получение всех доступных юзеру компаний чтобы можно было на UI выбирать куда переключиться. Если же админу нужно вызвать юзерский сервис с контекстом какой-либо компании, то есть функция set_company_context(...), которая временно включает фильтр и активирует ID указанной компании. Например получить все [имя сущности] юзера для конкретной компании. ▫️Плюсы такого подхода: - Контекст хранится в токене, нет лишнего запроса в БД - При утечке токена одной компании другие не пострадают - Фильтр автоматически применяется на любой запрос юзера - Все данные в одной БД. Можно связывать данные разных клиентов через FK и хранить общие сущности без проблем. ▫️Минусы: - Сломался один клиент - сломались все! - Нужно продумывать индексы с учётом особенностей multi-tenancy - Если не следить за чистотой кода и правильностью архитектуры, то можно серьезно накосячить. Правильные тесты - наше ВСЁ! - Общие миграции ▫️Что еще можно добавить - партиции таблиц с разделением по полю company_id - автоматическое добавление текущей company_id для создаваемых юзером объектов. Моя версия в фукнции add_company_id(), но не факт, что это лучший способ. ▫️Как запустить 1. клонируем проект 2. запускаем just run 3. готово! ▫️Как протестить 1. клонируем проект 2. запускаем just test 3. готово! - Почему just? - Патамушта! И главное: мой пример не является эталоном и инструкцией к применению, скорей это эксперимент. Буду рад замечаниям и указаниям на проблемы такого подхода. Другие статьи на почитать: ↗️один ↗️два ↗️три #tricks #systemdesign
5 · 707 ·

Рядом в ленте

PPython ЗаметкиВ Python 3.6 был полностью переработан стандартный dict. Вместо разреженной таблицы данные стали храниться в плотных массивах. Это дало буст к скорости и экономPPython ЗаметкиMulti Tenancy - это архитектурный подход в разработке, при котором один экземпляр приложения обслуживает множество разных клиентов так, что они имеют доступ тол
это сообщение
PPython ЗаметкиМодуль pwd может использоваться для считывания информации о пользователях из базы данных паролей Unix (обычно /etc/passwd ). Получить объект текущего юзера можнPPython ЗаметкиВ прошлый раз был пример с понижением привелегий процесса. Проблема в том, что обратно поднять привилегии нельзя. Чтобы не потерять уровень доступа текущего про

Открытая публичная лента из поискового индекса ChatCrawler — «Google по публичному Telegram»; обновляется по мере обхода площадки. Время — UTC.

Только публичный контент, официальный API Telegram. О проекте · Вопросы · Чего мы не делаем · Убрать страницу из выдачи · Каталог · Поиск · Как мы считаем