Комментарии 6
config.TENANT_ID - это не обязательно техдолг. Это может быть нормальное архитектурное решение, если система построена по модели «один instance приложения — один tenant».
Например у нас два адреса: tenant1.example.com, tenant2.example.com
При этом каждый поддомен может вести на отдельный instance приложения или отдельный контейнер, где TENANT_ID задан через конфиг или переменные окружения. В этом случае приложение само не «вычисляет» tenant из запроса, а берет из настроек (config.TENANT_ID) окружения приложения.
Посмотрите как работает django.contrib.sites
Другой подход - один общий instance приложения для многих tenants. При данном подходе вы динамически фильтруете tenant в рамках одного instance приложения.
Посмотрите как работает django-tenants
Поэтому нельзя называть захардкоженный config.TENANT_ID техдолгом. Это может быть плохим решением в одной архитектуре и совершенно нормальным решением в другой.
Перед публикацией таких материалов я бы рекомендовал отдавать их на техническое ревью нескольким LLM моделям, потому что сейчас выводы выглядят так, будто вы смешали все в одну кучу.
Суть тут в том, что есть две совершенно разные архитектуры, и я в статье недостаточно чётко это разделил.
Если у тебя модель «один tenant = отдельный instance приложения» — например отдельный контейнер, отдельный env, отдельный субдомен — то config.TENANT_ID вообще нормальная история. По сути приложение обслуживает только одного клиента и заранее знает, кто он. Это примерно как SITE_ID в Django — ничего криминального.
Проблема начинается в другой архитектуре — когда один instance приложения одновременно обслуживает много клиентов. То есть настоящий shared multi-tenant. Там tenant уже нельзя хранить как глобальную константу, потому что в один и тот же момент разные запросы принадлежат разным клиникам или компаниям.
И вот в этом кейсе у меня была ошибка: схема базы уже была multi-tenant, в таблицах был tenant_id, но часть кода всё ещё брала tenant из глобального config.TENANT_ID. Получалось странное состояние: архитектура вроде многотенантная, а runtime местами продолжает думать, что tenant всегда один и тот же.
Из-за этого и появились проблемы — данные могли создаваться или читаться не из контекста текущего пользователя, а из дефолтного tenant’а.
В итоге это исправили нормально: tenant теперь берётся не из глобального конфига, а из контекста текущей сессии/JWT пользователя. То есть каждый запрос сам знает, к какой клинике относится. Это уже ближе к тому, как работают django-tenants или middleware-based multi-tenancy.
Так что вы правы: сам по себе config.TENANT_ID — не техдолг. Всё зависит от архитектуры. Ошибкой он становится только тогда, когда приложение уже пытается быть shared multi-tenant системой.
После вашего уточнения становится понятно, что проблема была не в config.TENANT_ID, а в том, что в проекте одновременно жили две разные модели tenancy.
Часть системы уже была shared multi-tenant: таблицы с tenant_id, выборки через user.tenant_id, разные клиники в одной схеме. А часть write-path продолжала работать как single-tenant-приложение через глобальный config.TENANT_ID.
Но тогда не очень понятно, как именно эта ошибка связывает между собой разные архитектуры разделения данных: shared DB + tenant_id, schema-per-tenant, database-per-tenant и так далее. Баг был не в выбранной модели хранения данных, а в том, что приложение неконсистентно определяло текущего tenant’а. Это уровень runtime-контекста запроса, а не доказательство за или против конкретной схемы изоляции данных.
Это не «самая дорогая ошибка SaaS». Это базовый баг, который должен ловиться первым же E2E-тестом на двух tenant’ов.
Если система реально multi-tenant, минимальная проверка выглядит элементарно: tenant B создаёт врача — tenant B его видит, tenant A его не видит. Если запись уехала в tenant-1, тест сразу красный.
Поэтому статья драматизирует не ту проблему. config.TENANT_ID сам по себе не плохой и не хороший. Он нормален в архитектуре «один tenant — один instance» и ошибочен в shared multi-tenant runtime.
Настоящий вывод должен быть не «не используйте config.TENANT_ID», а «не смешивайте tenancy-модели, явно определяйте источник tenant context и покрывайте изоляцию tenant’ов базовыми тестами».
Справедливо, особенно про tenancy-mixing — это и есть настоящий корень. config.TENANT_ID живёт как наследие от первого деплоя в single-tenant режиме, а часть write-path так и не успела переехать на request-scoped context до того, как пришёл второй клиент. Заголовок драматизирующий, согласен — урок в статье звучит точнее в формулировке «не смешивайте источники tenant context и закрывайте изоляцию E2E-тестом на двух tenant’ов», а не «config.TENANT_ID — зло». Two-tenant isolation test добавили после инцидента; до — не было, и это главный провал, не сам код.
Текст выглядит так, будто LLM модель помогла собрать убедительную форму вокруг изначально ошибочной связки тезисов. Но LLM не заменяет понимание предметной области и техническое ревью. Если плохо разложить задачу на уровни, на выходе получается гладкий, но методологически слабый материал.
Читатель получает не аккуратный разбор архитектуры multi-tenant SaaS, а драматизированное описание базового бага, искусственно связанное с темами, к которым он почти не относится. Мне кажется, перед следующими материалами стоит глубже разобрать тему и отдельно проверить техническую логику текста, а не только его подачу.
Логичнее было бы сфокусироваться на маркетинговых статьях, где у вас уже возможно есть сильная экспертиза, и осторожнее подходить к техническим темам.
Возможно, когда LLM-модели станут лучше вы сможете вернуться к техническим статьям.

Три архитектурных решения для multi-tenant B2B SaaS, о которых я пожалел, что не узнал раньше