ORKEN
Um ERP modular para diferentes ramos de operação.
ERP modular · SaaS multi-tenant
- Produção
- Status
- 5
- Módulos verticais
- Multi-tenant
- Isolamento
Visão geral
O que este sistema resolve
ORKEN é o sistema de gestão de quem opera sob pressão sem equipe de TI: ponto de venda, estoque, caixa, vendas, clientes e financeiro no núcleo, e módulos verticais que se encaixam no ramo do cliente — restaurante, obra, serviços, varejo.
A aposta do produto é que a mesma pizzaria, a mesma construtora e a mesma barbearia podem ser atendidas por um produto só. O que muda entre elas é módulo e preset, não código duplicado nem instalação separada.
O desafio
O que tornava o problema difícil
Estes são os pontos que mais influenciaram a arquitetura.
Verticais que não se parecem em nada
Restaurante tem comanda, mesa e cozinha. Obra tem etapa, orçamento e diário. Serviço tem agenda e profissional. Modelar isso como um produto só, sem transformar o núcleo num acúmulo de exceções, é o problema central do produto.
Isolamento entre empresas
Não pode existir caminho em que a consulta de uma empresa alcance a linha de outra. Um erro desses não é um bug que se corrige na próxima sprint — é um incidente de dado que já aconteceu quando alguém percebe.
O que o cliente pode acessar depende do que ele assina
A cobrança é por módulo. O acesso precisa ser verdade no servidor, não apenas um item escondido do menu — e precisa refletir a assinatura em tempo real, não no próximo login.
Estoque e caixa precisam explicar o saldo
Quando o dono pergunta por que o caixa fechou com essa diferença, a resposta tem que estar no histórico. Permitir edição de movimento destrói exatamente a informação que responde a pergunta.
Arquitetura
Como o sistema é montado
- Backend
.NET 8 · Clean Architecture
Domínio, aplicação, infraestrutura e API em projetos separados, com dependência apontando só para dentro.
- Aplicação
Núcleo + módulos
Núcleo por área de operação; verticais isolados em módulos próprios que estendem o núcleo.
- Dados
PostgreSQL · EF Core
Schema único, isolamento por linha imposto pelo ORM em toda entidade de negócio.
- Cache
Redis
Opcional por design: cai o cache, o sistema continua vendendo.
- Tempo real
SignalR
Telas operacionais que precisam refletir o estado sem o operador recarregar.
- Frontend
React · TypeScript · Vite
Organizado por módulo, com carregamento sob demanda e guardas de rota por módulo e perfil.
- Cobrança
Stripe
Assinatura por módulo, com webhook idempotente.
Decisões de engenharia
Decisões e trade-offs
Cada decisão registra a justificativa e o trade-off aceito.
Multi-tenancy por linha em schema único
Schema por empresa multiplica cada migração pelo número de clientes: um cliente com migração falhada fica numa versão diferente do resto, e o suporte passa a lidar com N variações do mesmo sistema. Com isolamento por linha, existe um schema, uma migração e uma versão.
Custo aceito · O isolamento passa a depender de disciplina — e é justamente por isso que ele foi movido para dentro do ORM e de um interceptor, em vez de ficar por conta de quem escreve a consulta.
Orken Service como módulo único com presets internos
Nove verticais de serviço tratados como nove produtos multiplicariam por nove o controle de acesso, a cobrança, as telas e os testes — e fragmentariam a evolução: uma melhoria feita para a clínica não chegaria ao pet shop. Com preset, o onboarding escolhe rótulos e capacidades sobre uma base única, e uma melhoria vale para os nove.
Custo aceito · A base de telas precisa absorver as diferenças de forma genérica. Vazar terminologia de um vertical para outro é um risco real e permanente, que exige cuidado a cada tela nova.
Movimentos de estoque e caixa são append-only
O saldo é sempre uma função da soma dos movimentos, nunca um número guardado. Correção é lançamento compensatório que referencia o original. Assim o histórico explica como o saldo atual se formou, e a auditoria é o próprio registro de movimentos — não um log paralelo que pode divergir.
Custo aceito · A operação humana precisa ser ensinada a estornar em vez de corrigir, e a interface tem a obrigação de tornar isso fácil. Também impede correções em massa por migração.
Migrações sempre aditivas
Coluna nova é anulável ou tem valor padrão; tabela nova não quebra o que já roda. Isso permite que a versão anterior e a nova coexistam durante o deploy, sem janela em que o sistema fica inconsistente.
Custo aceito · O schema acumula colunas que já não são mais o caminho principal, e a limpeza precisa ser um trabalho deliberado em vez de uma consequência da mudança.
O cache falha aberto
Se o Redis está indisponível, o sistema continua operando sem ele. Uma PME não pode parar de vender porque um componente de otimização caiu — e cache, por definição, é otimização.
Custo aceito · A exceção é a lista de tokens revogados, onde falhar aberto teria consequência de segurança. Essa área é tratada à parte, de forma explícita.
Erros esperados retornados como resultado explícito
Os serviços devolvem um tipo que representa sucesso ou falha, em vez de lançar. Isso obriga quem chama a tratar o caminho de erro — e mantém o rastreamento de exceção limpo, contendo apenas o que realmente não deveria ter acontecido.
Custo aceito · Mais verbosidade em cada camada, e uma fronteira explícita onde o resultado é traduzido em resposta HTTP.
Por dentro da engenharia
Os problemas em detalhe
Cada bloco abre com o problema, mostra a solução e explica por que ela foi escolhida. Expanda o que interessar.
Isolamento de tenant imposto pelo ORM
Problema
Uma condição de filtro esquecida numa consulta é o bug mais caro possível em um sistema multiempresa: mostra dado de um cliente para outro, passa por revisão de código sem chamar atenção e não quebra teste nenhum.
Solução
Cinco camadas. O token carrega a empresa; um middleware valida o vínculo entre usuário e empresa e resolve o contexto antes de qualquer consulta; um filtro global aplicado a toda entidade de negócio adiciona automaticamente a condição de empresa; um interceptor de salvamento injeta a empresa nos inserts e lança se alguém tentar gravar em nome de outra ou alterar a empresa de uma linha existente; e os testes de integração criam duas empresas em todo caso que envolva isolamento. Desligar o filtro é proibido no código de aplicação.
Por que foi feito assim
Fazer o isolamento depender de cada desenvolvedor lembrar não escala com o time nem com a superfície do produto. Movendo a regra para os pontos por onde toda consulta e todo salvamento passam obrigatoriamente, o caminho errado deixa de ser possível — e a violação vira exceção alta, não linha silenciosa no resultado.
Rate limit particionado por identidade
Problema
Um limitador de login configurado como um balde único e global significa que algumas tentativas erradas de uma pessoa consomem a cota de todo mundo. É negação de serviço acidental — e um vetor trivial para quem queira derrubar o login do sistema inteiro de propósito.
Solução
O limitador particiona por identidade do solicitante, de modo que o contador de um não interfere no do outro.
Por que foi feito assim
A partição por identidade contém tentativas abusivas sem consumir a cota de usuários legítimos.
Webhook de cobrança idempotente
Problema
Provedores de pagamento reentregam eventos — por retry, por timeout de resposta, por reprocessamento do lado deles. Processar o mesmo evento duas vezes libera módulo em duplicidade ou registra cobrança repetida.
Solução
O identificador de cada evento processado com sucesso é registrado numa tabela própria. Antes de processar, o sistema consulta; depois de processar, insere.
Por que foi feito assim
A reentrega faz parte do contrato de entrega ao menos uma vez. O receptor registra o identificador do evento para garantir idempotência.
Motor de interpretação com confirmação humana
Problema
Lançar movimento financeiro à mão é lento, e a fonte da informação costuma ser um comprovante ou um texto solto — não um formulário.
Solução
Um pipeline que separa duas coisas que costumam ser confundidas: extração ("o que está escrito no documento") e interpretação ("o que isso significa dentro desta operação"). A interpretação produz uma sugestão em que cada campo carrega a origem da sugestão, e nada vira lançamento sem confirmação. Correções do usuário realimentam um perfil de memória por empresa.
Por que foi feito assim
Separar extração de interpretação permite trocar o extrator sem tocar em regra de negócio, e testar a regra sem depender de um documento real. A confirmação humana impede que um erro de interpretação vire lançamento contábil antes da revisão.
Condição climática no diário de obra
Problema
O diário de obra registra a condição do tempo por turno, e é esse registro que sustenta pedido de prorrogação de prazo por chuva. Preenchido de memória na sexta-feira, não sustenta nada.
Solução
Integração com um serviço público de meteorologia, que preenche a condição do dia a partir da coordenada da obra no momento do lançamento.
Por que foi feito assim
É dado de terceiro, com data, e não depende da lembrança de ninguém — que é exatamente o que dá valor probatório ao registro. Não há modelo nem inferência envolvida: é uma consulta a uma fonte externa. A integração consulta uma fonte externa; não há modelo nem inferência envolvidos.
Qualidade
O que os testes protegem
Duas suítes com propósitos distintos. A unitária cobre regra de domínio isolada. A de integração sobe um PostgreSQL real em contêiner e exercita o caminho completo — porque o que este sistema mais precisa garantir, o isolamento entre empresas, só é verificável de verdade contra um banco de verdade.
- Que nenhuma consulta de uma empresa alcance a linha de outra — todo teste de isolamento cria duas
- Que o interceptor recuse escrita cruzada e alteração da empresa de uma linha existente
- Que o controle de acesso por módulo reflita a assinatura, e não o menu
- Que os movimentos de estoque e caixa permaneçam append-only
Stack
Stack do projeto
O sistema em números
- 584 (285 unitários · 299 integração)
- Testes automatizados
- 86
- Tabelas mapeadas
- 63
- Controllers de API
- 43
- Migrations
- 15
- Áreas do núcleo
conferido no repositório em 27/08/2026
Status
Produção
Backend
Dados
Frontend
Tempo real
Integrações
Infraestrutura
Testes