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?
Top comments (0)