DEV Community

Cover image for 5 decisões importantes em um SaaS multi-tenant
Asllan Maciel
Asllan Maciel

Posted on

5 decisões importantes em um SaaS multi-tenant

Um SaaS multi-tenant pode começar pequeno sem começar frágil.

O erro mais comum não é escolher o framework errado. É tratar isolamento, billing, filas e observabilidade como detalhes que serão resolvidos “quando o produto crescer”. Quando esse momento chega, dados e responsabilidades já estão espalhados pela aplicação.

Ao organizar o Laravel SaaS Blueprint, reuni cinco decisões que considero importantes desde o primeiro release.

1. Resolver o tenant uma vez

Espalhar tenant_id manualmente por controllers e queries cria uma convenção frágil. Basta uma consulta esquecer o filtro para atravessar a fronteira entre clientes.

O ideal é resolver o tenant na entrada da requisição e carregar um contexto explícito para o restante do fluxo. Esse contexto pode vir de domínio, subdomínio, token, organização selecionada ou outro mecanismo — mas deve ser validado antes de acessar recursos protegidos.

Também não basta filtrar o banco. O tenant precisa participar das chaves de cache, caminhos de storage, jobs, logs, exports e métricas.

2. Escolher isolamento pelo risco

Não existe uma estratégia universal:

  • banco compartilhado com tenant_id é simples e econômico;
  • schema por tenant aumenta separação e complexidade operacional;
  • banco por tenant oferece isolamento forte, mas cobra em deploy, migração e observabilidade.

A decisão depende de risco, escala, regulamentação e capacidade da equipe. Para muitos MVPs, banco compartilhado funciona — desde que o isolamento seja centralizado e testado.

O teste que realmente importa tenta acessar dados de outro tenant e espera falhar.

3. Tratar billing como estado de negócio

Billing não é apenas “adicionar Stripe”. Uma assinatura percorre estados: avaliação, ativa, vencida, cancelada, em recuperação e possivelmente pausada.

Webhooks podem chegar repetidos ou fora de ordem. A aplicação precisa manter um estado interno coerente, registrar identificadores de entrega e processar eventos de forma idempotente.

O provedor de pagamento deve ser um adaptador. As regras do produto — limites, recursos disponíveis e períodos de tolerância — pertencem ao domínio da aplicação.

4. Projetar jobs e webhooks para repetição

Em sistemas distribuídos, “executar exatamente uma vez” raramente é uma garantia prática. A estratégia mais segura é aceitar que a mensagem pode reaparecer e tornar a operação idempotente.

Algumas técnicas:

  • chave única por evento externo;
  • registro de processamento;
  • transação envolvendo efeito e marcação;
  • estados explícitos;
  • retry com backoff;
  • dead-letter queue para falhas persistentes.

E todo job precisa carregar o contexto do tenant. Um worker sem contexto pode produzir vazamento mesmo quando a camada HTTP está correta.

5. Definir observabilidade antes do incidente

Logs sem tenant_id, request_id, job_id ou delivery_id têm pouco valor durante uma investigação.

No mínimo, eu quero conseguir responder:

  • qual tenant foi afetado;
  • qual operação falhou;
  • qual evento iniciou o fluxo;
  • quantas tentativas ocorreram;
  • onde o tempo foi gasto;
  • se houve impacto em outros tenants.

Isso não exige uma plataforma enorme no MVP. Exige campos consistentes, erros acionáveis e métricas básicas desde o início.

Simples não é o mesmo que improvisado

Você não precisa começar com Kubernetes, microserviços ou um banco por cliente. Precisa preservar fronteiras que serão caras de reconstruir:

  • contexto de tenant;
  • isolamento verificável;
  • estados de billing;
  • idempotência;
  • observabilidade.

Todo o resto pode crescer de forma incremental.

O blueprint é documentação comunitária e independente de fornecedor. Se você está desenhando ou migrando um SaaS em Laravel, contribuições e contrapontos são bem-vindos.

Qual dessas decisões mais causou retrabalho em um produto seu?


Enter fullscreen mode Exit fullscreen mode

Top comments (0)