Справедливо, особенно про tenancy-mixing — это и есть настоящий корень. config.TENANT_ID живёт как наследие от первого деплоя в single-tenant режиме, а часть write-path так и не успела переехать на request-scoped context до того, как пришёл второй клиент. Заголовок драматизирующий, согласен — урок в статье звучит точнее в формулировке «не смешивайте источники tenant context и закрывайте изоляцию E2E-тестом на двух tenant’ов», а не «config.TENANT_ID — зло». Two-tenant isolation test добавили после инцидента; до — не было, и это главный провал, не сам код.
Суть тут в том, что есть две совершенно разные архитектуры, и я в статье недостаточно чётко это разделил.
Если у тебя модель «один 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 системой.
Справедливо, особенно про tenancy-mixing — это и есть настоящий корень. config.TENANT_ID живёт как наследие от первого деплоя в single-tenant режиме, а часть write-path так и не успела переехать на request-scoped context до того, как пришёл второй клиент. Заголовок драматизирующий, согласен — урок в статье звучит точнее в формулировке «не смешивайте источники tenant context и закрывайте изоляцию E2E-тестом на двух tenant’ов», а не «config.TENANT_ID — зло». Two-tenant isolation test добавили после инцидента; до — не было, и это главный провал, не сам код.
Суть тут в том, что есть две совершенно разные архитектуры, и я в статье недостаточно чётко это разделил.
Если у тебя модель «один 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 системой.