惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
Y
Y Combinator Blog
F
Fortinet All Blogs
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
Netflix TechBlog - Medium
T
Tailwind CSS Blog
aimingoo的专栏
aimingoo的专栏
博客园 - Franky
T
The Blog of Author Tim Ferriss
D
DataBreaches.Net
量子位
博客园 - 三生石上(FineUI控件)
I
InfoQ
Engineering at Meta
Engineering at Meta
WordPress大学
WordPress大学
阮一峰的网络日志
阮一峰的网络日志
爱范儿
爱范儿
D
Docker
美团技术团队
雷峰网
雷峰网
U
Unit 42
Stack Overflow Blog
Stack Overflow Blog
Recent Announcements
Recent Announcements
人人都是产品经理
人人都是产品经理

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Multi-tenant além do TenantId: problemas reais e aprendiz...
Lucas Vizza · 2026-05-26 · via DEV Community

Em muitos sistemas SaaS, o multi-tenant começa de um jeito aparentemente simples: adicionamos uma coluna TenantId nas tabelas e seguimos o desenvolvimento normalmente.

O problema é que o desafio nunca foi adicionar a coluna.
O verdadeiro desafio é garantir que ninguém esqueça dela.

No começo do projeto, isso normalmente parece simples. O time é pequeno, existem poucas queries e praticamente todo mundo conhece a arquitetura inteira do sistema. O isolamento entre tenants parece apenas uma regra de banco de dados.

Mas conforme o sistema cresce, novos problemas começam a aparecer:

  • background jobs;
  • filas;
  • cache compartilhado;
  • exportações;
  • relatórios;
  • integrações;
  • webhooks;
  • queries SQL manuais;
  • crescimento do time.

E nesse momento, o multi-tenant deixa de ser apenas uma preocupação de modelagem de dados.

Ele passa a ser uma preocupação de arquitetura.

O problema começa quando o sistema cresce

Em muitos projetos, o isolamento entre tenants depende diretamente da disciplina dos desenvolvedores.

O código normalmente começa assim:

var orders = await _dbContext.Orders
    .Where(x => x.TenantId == tenantId)
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

E honestamente: no início isso funciona.

O problema é que, conforme o sistema cresce, essa abordagem começa a depender demais da memória do desenvolvedor e do revisor.

Alguém inevitavelmente vai esquecer o filtro.

Principalmente em cenários como:

  • manutenção rápida em produção;
  • hotfix;
  • relatórios complexos;
  • queries SQL;
  • jobs assíncronos;
  • novos desenvolvedores entrando no projeto.

O maior erro em sistemas multi-tenant é transformar isolamento de dados em responsabilidade humana.

Em sistemas grandes, o problema não é "se" alguém vai esquecer o filtro.

O problema é "quando".

Nossa abordagem no .NET com EF Core

Depois de alguns problemas e aprendizados, começamos a tratar o multi-tenant como uma responsabilidade da arquitetura — e não apenas da query.

A ideia principal era simples:

O sistema precisava saber automaticamente qual é o tenant atual sem depender do endpoint ou da query.

Centralizando o contexto do tenant

O primeiro passo foi centralizar as informações do usuário autenticado.

Em vez de resolver TenantId manualmente em vários pontos da aplicação, criamos um contexto autenticado responsável por expor essas informações para toda a aplicação.

Algo parecido com:

public sealed class AuthenticatedUser
{
    public Guid TenantId { get; init; }
}

Enter fullscreen mode Exit fullscreen mode

Isso parece simples, mas muda bastante a arquitetura.

A partir desse momento, o sistema inteiro passa a conhecer o tenant atual de forma consistente.

Global Query Filters

Depois disso, começamos a usar Global Query Filters do Entity Framework Core.

A ideia era simples: impedir que o isolamento dependesse da memória do desenvolvedor.

builder.Entity<Order>()
    .HasQueryFilter(x =>
        x.TenantId == _authenticatedUser.TenantId);

Enter fullscreen mode Exit fullscreen mode

Isso resolveu boa parte dos problemas.

Além de reduzir drasticamente erros humanos, também ajudou a padronizar o comportamento da aplicação inteira.

Mas isso definitivamente não resolveu tudo.

E essa foi uma das partes mais importantes que aprendemos.

Onde as coisas ficaram mais perigosas

Os problemas mais complicados começaram a aparecer quando saímos do fluxo tradicional HTTP.

Principalmente em:

  • background jobs;
  • mensageria;
  • processamento assíncrono;
  • webhooks;
  • integrações.

Nesse tipo de fluxo, não existe mais usuário logado.

O request HTTP já terminou.
E junto com ele, o contexto autenticado também desapareceu.

E nesse momento, o isolamento entre tenants começa a ficar perigoso novamente.

Background jobs e mensageria

Em processamento assíncrono, não existe usuário logado para “salvar” o isolamento do sistema.

Foi aí que começamos a propagar explicitamente o TenantId nos eventos.

public record OrderCreatedEvent
{
    public Guid TenantId { get; init; }
}

Enter fullscreen mode Exit fullscreen mode

Isso permitiu reconstruir corretamente o contexto durante o processamento do job.

Em um dos cenários, percebemos um processamento executando sem o contexto correto do tenant após sair do fluxo HTTP tradicional. O problema foi relativamente simples de corrigir, mas deixou claro que o isolamento ainda dependia demais do request original.

Sem isso, alguns cenários começavam a ficar perigosos:

  • processamento cruzado;
  • consultas incorretas;
  • dados inconsistentes;
  • dificuldade de rastreabilidade.

Esse tipo de problema normalmente não aparece em projetos pequenos.

Mas aparece rapidamente quando o sistema começa a crescer.

Cache compartilhado

Outro ponto importante foi o cache.

Em sistemas multi-tenant, Redis compartilhado sem isolamento adequado pode virar um problema rapidamente.

Por exemplo:

orders:active

Enter fullscreen mode Exit fullscreen mode

Essa chave parece inofensiva.

Mas em um ambiente multi-tenant ela pode causar vazamento indireto de informações.

Depois disso, começamos a adotar prefixos por tenant:

tenant:{tenantId}:orders:active

Enter fullscreen mode Exit fullscreen mode

Parece detalhe.

Mas são exatamente esses detalhes que começam a fazer diferença conforme a arquitetura evolui.

O que aprendemos na prática

Uma das principais lições foi entender que multi-tenant não é apenas uma preocupação de banco de dados.

Ele impacta:

  • segurança;
  • filas;
  • cache;
  • observabilidade;
  • permissões;
  • processamento assíncrono;
  • debugging;
  • manutenção.

Também percebemos que boa parte dos problemas não apareciam durante o desenvolvimento inicial.

Eles apareciam meses depois.

Principalmente quando:

  • o sistema crescia;
  • o número de fluxos aumentava;
  • novos desenvolvedores entravam no projeto;
  • integrações começavam a surgir.

E honestamente: muitos desses problemas são difíceis de perceber até acontecerem em produção.

Trade-offs

Global Query Filters ajudaram muito no isolamento automático.

Mas também adicionaram alguns trade-offs importantes.

Por exemplo:

  • debugging mais complexo;
  • comportamento implícito nas queries;
  • necessidade de bypass controlado;
  • maior atenção em performance;
  • maior cuidado com jobs administrativos.

Em alguns momentos, também precisamos criar mecanismos explícitos para desabilitar filtros em cenários administrativos e fluxos internos.

E isso exige bastante cuidado.

Porque toda exceção de isolamento começa a virar um ponto potencial de risco.

O que eu faria diferente hoje

Se estivesse começando essa arquitetura hoje, provavelmente investiria mais cedo em observabilidade e testes automatizados voltados especificamente para isolamento entre tenants.

Também analisaria com mais atenção recursos mais recentes do EF Core, como Named Query Filters no EF 10, principalmente para cenários administrativos e fluxos internos que exigem bypass controlado.

Boa parte dos problemas mais difíceis em sistemas multi-tenant não aparece no CRUD inicial.

Eles aparecem quando o sistema começa a crescer.

Conclusão

Durante muito tempo, enxergamos multi-tenant apenas como uma estratégia de modelagem de banco de dados.

Hoje, vejo muito mais como uma preocupação de arquitetura.

O verdadeiro desafio nunca foi adicionar TenantId.

O desafio é garantir isolamento consistente quando o sistema começa a crescer.

Principalmente quando entram em cena:

  • filas;
  • jobs;
  • cache;
  • integrações;
  • múltiplos times;
  • manutenção contínua.

No final, o maior aprendizado foi perceber que isolamento entre tenants não pode depender apenas da memória do desenvolvedor.

A arquitetura precisa ajudar nisso.