Ir para o conteúdo
DedicatedPHP Contato

Configuração por cliente em um SaaS PHP sem criar variantes difíceis de gerenciar

Projete uma configuração por cliente governada em SaaS PHP para atender necessidades B2B sem multiplicar bifurcações nem condicionais.

Diagrama editorial de um SaaS PHP com configuração centralizada por cliente, permissões, capacidades e processos assíncronos

Uma solicitação comercial se torna perigosa quando deixa de ser uma decisão explícita de produto e passa a se materializar como um if ($tenantId === ...). No início, resolve uma urgência. Com o tempo, essa condição aparece em controladores, templates, processos de fila, exportações e APIs. O resultado não é uma configuração: são variantes implícitas do produto, difíceis de testar, explicar e remover.

A configuração por cliente em SaaS PHP deve permitir diferenças deliberadas e governadas, não preservar cada exceção histórica. A pergunta útil não é «podemos fazer isso para este cliente?», mas «essa variação representa uma dimensão estável do produto da qual outros clientes poderiam precisar, com regras e suporte sustentáveis?».

O sinal de alerta: uma exceção permanente no código

O sinal de alerta: uma exceção permanente no código — guía visual de DedicatedPHP

Há uma diferença entre adaptar uma experiência e manter uma ramificação oculta do produto. Convém intervir antes que uma solicitação específica crie qualquer um destes sinais:

  • O identificador do tenant, domínio ou cliente aparece na lógica de negócio.
  • Uma mesma regra é replicada na interface, API e worker assíncrono.
  • A equipe não consegue responder quais clientes têm uma exceção nem quem a aprovou.
  • Uma mudança de plano altera comportamentos funcionais sem uma definição central.
  • Remover uma adaptação exige procurar condicionais em vários repositórios ou serviços.

Uma exceção pode ser legítima durante uma descoberta ou uma migração, mas deve ter proprietário, data de revisão e uma saída: transformá-la em capacidade de produto, isolá-la como integração específica ou rejeitá-la. Deixá-la sem classificação transforma a dívida técnica em uma promessa comercial não documentada.

Não confunda configuração, permissões, capacidades e desenvolvimento específico

Esses mecanismos respondem a perguntas diferentes. Misturá-los produz designs opacos e regras contraditórias.

  • Configuração: define como uma funcionalidade já existente se comporta para um tenant. Por exemplo, o formato de uma numeração, o idioma padrão ou se um fluxo requer uma aprovação adicional.
  • Permissões: determinam o que uma identidade pode fazer dentro de um tenant. Um usuário pode ter permissão para aprovar pagamentos mesmo que a aprovação esteja configurada como obrigatória.
  • Capacidades: indicam se o tenant tem acesso a uma funcionalidade ou a um limite operacional. Podem depender de contrato, plano ou ativação controlada, mas não deveriam conter toda a lógica do domínio.
  • Desenvolvimento específico: cobre um comportamento que não se encaixa em uma dimensão reutilizável do produto, como uma integração com um sistema próprio do cliente ou uma transformação contratual singular.

Uma regra prática ajuda a decidir: se muda quem executa uma ação, use permissões; se muda se uma funcionalidade existe ou está disponível, use capacidades; se muda como uma funcionalidade disponível opera, use configuração. Se muda o modelo de negócio de forma exclusiva, não o disfarce de flag.

O que deve ser configurável e o que deve permanecer no núcleo

Uma opção merece entrar no catálogo de configuração quando tem semântica clara, um conjunto finito de valores, validações conhecidas e uma expectativa razoável de reutilização. Também precisa de uma experiência de suporte compreensível: alguém deve poder explicar o efeito de alterá-la sem inspecionar código.

Costumam ser bons candidatos os parâmetros de apresentação, políticas de notificação, limites, sequências de aprovação, preferências regionais e escolhas entre fluxos já suportados. Por outro lado, devem permanecer no núcleo as invariantes de segurança, a integridade de dados, o cálculo financeiro base e regras cuja alteração exigiria reinterpretar entidades ou contratos existentes.

Não transforme dados arbitrários em configuração apenas por flexibilidade. Um campo JSON sem schema pode ocultar dependências impossíveis de descobrir. Quando uma opção modifica uma regra crítica, defina tipos, valores permitidos, condições de uso e consequências sobre dados anteriores.

Construa um modelo de configuração governado

Uma chave isolada não basta. Cada definição do catálogo deve incluir metadados que permitam operar o produto com segurança:

  • Chave e descrição funcional: nomes estáveis, orientados ao domínio e não a detalhes de implementação.
  • Proprietário: equipe ou responsável que decide sua evolução e remoção.
  • Escopo: global, tenant, unidade organizacional, projeto ou usuário. Evite permitir todos os escopos por padrão.
  • Valor padrão: comportamento explícito quando não existe uma substituição.
  • Tipo e validação: booleano, enumeração, número com intervalo ou estrutura validada por schema.
  • Dependências: requisitos em relação a outras opções, capacidades ou estado de migração.
  • Sensibilidade: classificação de dados e regras de acesso para leitura e modificação.
  • Ciclo de vida: data de introdução, revisão, descontinuação e remoção prevista, quando aplicável.

Em PHP, centralize a resolução em um serviço de domínio, por exemplo TenantSettings, e forneça objetos tipados em vez de arrays sem contrato. A aplicação pode combinar o valor global, o valor do tenant e um valor mais específico por meio de uma precedência documentada. A ausência de valor deve sempre ser resolvida para o valor padrão, não para uma interpretação diferente em cada consumidor.

$policy = $tenantSettings->approvalPolicy($tenantId);
if ($policy->requiresSecondApproval()) {
    $workflow->requestSecondApproval($order);
}

O armazenamento pode ser relacional ou documental, mas o catálogo e a validação não deveriam depender da forma de persistência. Mantenha também um histórico imutável de mudanças: valor anterior e novo, ator, momento, motivo e canal de modificação. O histórico não substitui um registro de auditoria de ações de negócio, mas permite reconstruir qual configuração estava vigente.

Avalie a decisão no limite adequado

O problema dos condicionais dispersos não se resolve movendo todos eles para um controlador. Uma configuração que afeta uma regra de negócio deve ser avaliada no serviço ou na política de domínio que aplica essa regra. O controlador traduz a solicitação; o template apresenta o resultado; nenhum deles deveria decidir por conta própria uma política de tenant.

Para comportamentos complexos, use estratégias ou políticas registradas, em vez de cadeias de booleanos. Uma política de faturamento pode selecionar uma implementação entre modos suportados após validar que o tenant tem a capacidade necessária. Assim, a interface, a API e a fila invocam a mesma decisão.

Os templates podem receber uma view já preparada, incluindo indicadores de capacidade para mostrar ou ocultar ações. Ocultar um botão não é autorização. A API deve aplicar permissões, capacidade e configuração no servidor, mesmo que a interface não exponha a operação.

Capacidades e limites sem enrijecer os planos

Um plano comercial pode conceder capacidades, mas não deve se transformar em uma coleção de if ($plan === '...'). Modele uma capacidade estável, como advanced_approvals ou api_access, e resolva quais tenants a possuem por meio de uma fonte contratual ou administrativa. Depois, a lógica funcional consulta a capacidade, não o nome do plano.

Os limites exigem uma definição ainda mais precisa: o que é contado, em qual janela temporal, quando o bloqueio é aplicado e como tentativas e processos em fila se comportam. Um limite deve ser observável e consistente em todos os pontos de entrada. Se uma integração cria recursos fora da interface principal, não pode contornar o mesmo controle.

Altere configurações de forma segura e reversível

Modificar uma opção pode ter efeitos imediatos sobre trabalhos em andamento, registros existentes ou integrações. Antes de salvar, valide tipo, permissões administrativas, dependências e compatibilidade com o estado atual. Quando o impacto for relevante, ofereça uma prévia da mudança: qual fluxo será ativado, quais restrições ela viola e quais operações futuras afetará.

A ativação gradual é diferente de divulgar uma opção em toda a interface. Você pode habilitar uma capacidade para um conjunto controlado de tenants e observar seu comportamento antes de expô-la de forma geral. Defina também uma reversão: qual valor restaura o estado anterior, se há migrações de dados associadas e o que acontece com operações iniciadas sob a nova configuração.

Uma mudança reversível na interface pode não ser reversível nos dados. Trate ambas as dimensões separadamente antes de ativar uma nova política.

Mantenha consistência em filas, APIs e integrações

Os processos assíncronos introduzem uma decisão adicional: resolver a configuração ao executar o trabalho ou preservar um snapshot ao criá-lo. Para ações que devem respeitar a política vigente, resolva no momento da execução e inclua o tenant no contexto do trabalho. Para documentos, cálculos ou comunicações que devem reproduzir a decisão original, armazene uma versão ou snapshot explícito junto ao comando.

Não misture ambas as opções sem declará-lo. Uma nova tentativa pode mudar de resultado se consultar uma configuração atualizada. Defina idempotência, versão de configuração e comportamento esperado em novas tentativas. As integrações externas precisam de contratos equivalentes: validação prévia, tratamento de erros, limites e rastreabilidade por tenant, sem enviar segredos ou dados pessoais para o log de diagnóstico.

Auditoria e suporte: explique o comportamento observado

Auditoria e suporte: explique o comportamento observado — guía visual de DedicatedPHP

O suporte precisa responder por que um cliente vê um fluxo, não apenas qual valor uma chave tem. Registre um rastro de decisão com identificador do tenant, versão da definição, fonte do valor efetivo — padrão ou substituição —, capacidades relevantes e resultado da avaliação. Limite o acesso a essas informações e mascare valores sensíveis.

Complete essa rastreabilidade com métricas de uso por opção, erros de validação, mudanças com falha e opções sem consumo. Uma configuração sem uso pode estar obsoleta; uma usada por um único tenant durante muito tempo merece revisão de produto. O objetivo não é eliminar toda diferença, mas tornar cada diferença explícita, verificável, observável e removida quando deixar de agregar valor.

Deseja aplicar essas ideias ao seu projeto?Vamos discutir sua plataforma PHP.
Veja os serviços relacionados