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

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

10 сообщений · –
P
Недавно я реализовал 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 · 705 ·
  1. С
    Для вас изобрели мемкеш, редис и аналоги. Когда подрастёте (в количестве запросов в секунду, в количестве клиентов итп) посмотрите на монгу и другие носкуели. Не обязательно пользоваться скуелем, он не всегда хорош. В случае хранения сессий точно не годится. А вот носкуели прямо вот хороши. Им из куки строчку мусора как ключ, а в ответ словарик с данными сессии. У-удобно.
    1. P
      Python Заметки - Чат
      Тут в примере другие задачи решаются. Кеш это следующий уровень. Данный пример про разграничение доступа к данным.
      1. С
        Речь не о кешировании. Речь о том, что хранить сессии в браузере фу. memcached это бд типа ключ-значение и хранит данные в памяти, доступ через сокет или по IP. То есть разные процессы и разные Хосты туда могут ходить одновременно. Запись атомарная итп. Удобная штука. Скорость вах отличная.
        1. P
          Python Заметки - Чат
          Это просто еще один подход. Использовать тот или иной способ - решается под задачу. Если вернуться к посту, то тема просто другая. Но дополнение засчитано 👍
    1. P
      Python Заметки - Чат
      Спасибо, давно юзаю)

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

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