Luhmera
Sócio e arquiteto de software – atual
Visão geral
Plataforma SaaS multi-tenant para clínicas, com back-end em NestJS e PostgreSQL e front-end em React. Fui responsável pela arquitetura e pela implementação de ponta a ponta: isolamento entre clientes, processamento assíncrono confiável, auditoria, tempo real, segurança e testes.
- Isolamento entre clientes provado por testes de integração contra Postgres e Redis reais.
- Eventos entregues de forma confiável com outbox transacional e filas BullMQ.
- No back-end: 37 módulos, cerca de 315 endpoints HTTP, 62 migrations versionadas e cerca de 730 casos de teste, além de um front-end com mais de 100 rotas.
Arquitetura
Browser · React SPA
- HTTPS / WebSocket
API NestJS · N instâncias
Guards: auth · tenant · RBAC
Dados e armazenamento
PostgreSQL
Redis
Cache · Rate limit · Filas BullMQ · Adapter Socket.io
S3
URLs pré-assinadas
- Filas BullMQ
Processamento assíncrono
Workers
gravam o resultado no PostgreSQL
PDF · Puppeteer
Multi-tenant e isolamento
- Um tenant por clínica
- Cada clínica é um tenant, com os dados isolados por tenant_id e verificação em guards na entrada de cada requisição.
- Isolamento provado
- Uma suíte de testes de integração passa pela pilha HTTP real (guards, pipes e filtros) sobre Postgres e Redis reais e prova que uma clínica nunca lê nem altera dados de outra.
- Acesso revogado na hora
- Remover ou rebaixar um usuário vale já na requisição seguinte, sem esperar o token expirar.
- Administração da plataforma
- O time da plataforma tem uma área própria de administração dos tenants, com acesso controlado.
Eventos, outbox e filas
- Outbox transacional
- Outbox transacional: a mensagem é gravada na mesma transação da operação de negócio, então nada é publicado se a transação falhar.
- Relay acordado pelo Postgres
- Um relay leva as mensagens para o BullMQ, acordado por LISTEN/NOTIFY do Postgres e com polling de segurança a cada segundo.
- Várias instâncias, sem duplicar
- Lotes travados com FOR UPDATE SKIP LOCKED: várias instâncias podem rodar o relay ao mesmo tempo sem publicar a mesma mensagem duas vezes.
- Publicação idempotente
- Publicação idempotente (o id do job é o id da mensagem), ordem preservada e retentativa automática em caso de falha.
- Filas por domínio
- Filas BullMQ separadas por domínio (auditoria, documentos fiscais, notificações etc.), com workers que rodam em processos dedicados.
- Cinco workers
- Cinco workers BullMQ (auditoria, atendimentos, dicionário, documentos fiscais e notificações). Uma flag liga ou desliga workers e relay por processo, o que permite réplicas só de API.
- Sem emissão em duplicidade
- O worker de emissão de documentos fiscais usa o id do documento como chave de idempotência no provedor, então uma nova tentativa do job nunca emite duas vezes; o provedor é injetável, com uma versão falsa para testes.
Outbox
Cliente
- requisição
API
- BEGIN · COMMIT
PostgreSQL
grava a entidade e a mensagem outbox na mesma transação
- NOTIFY outbox (ou polling a cada 1 s)
Relay
SELECT ... FOR UPDATE SKIP LOCKED
- publica (jobId = id da mensagem)
BullMQ
- processa
Worker
grava o resultado no PostgreSQL
private async publishBatch(): Promise<number> {
return this.dataSource.transaction(async (manager) => {
const rows: PendingRow[] = await manager.query(
`SELECT id, queue, job_name, payload FROM infra.outbox
WHERE published_at IS NULL
ORDER BY seq
LIMIT $1
FOR UPDATE SKIP LOCKED`,
[OUTBOX_BATCH_SIZE],
);
const published: string[] = [];
for (const row of rows) {
try {
await withTimeout(
this.queues.publish(row.queue, row.job_name, row.payload, { jobId: row.id }),
PUBLISH_TIMEOUT_MS,
);
published.push(row.id);
} catch (error) {
await manager.query(
'UPDATE infra.outbox SET attempts = attempts + 1, last_error = $2 WHERE id = $1',
[row.id, (error as Error).message],
);
break;
}
}
if (published.length > 0) {
await manager.query('UPDATE infra.outbox SET published_at = now() WHERE id = ANY($1)', [
published,
]);
}
return published.length === rows.length ? rows.length : 0;
});
}Auditoria
- Gravada junto com a escrita
- Toda escrita (criar, alterar, excluir, restaurar) passa por um repositório base que grava o evento de auditoria no outbox, na mesma transação da operação: o evento existe se, e somente se, a escrita foi confirmada, e sobrevive a uma queda ou a uma falha do Redis antes de chegar à fila.
- O que cada evento guarda
- O evento guarda quem fez (usuário ou sistema), qual registro, a operação, o que mudou, quando a mudança aconteceu (não quando foi processada) e um id de correlação compartilhado por todas as escritas de uma mesma ação do usuário.
- Delta, não diff
- Na alteração, o delta é exatamente o que o chamador informou; na criação, o retrato inicial. Não há diff calculado nem leitura extra do registro.
- Falhas também deixam rastro
- Escritas que falham também deixam rastro: um evento com success=false e a mensagem do erro, gravado em uma conexão própria (a transação original já foi abortada pelo Postgres) e sem esconder o erro verdadeiro. Dá para desligar por chamada.
- Exportação auditada
- A exportação dos dados da clínica também é um ato auditado (operação EXPORT): o evento guarda o autor e as contagens, nunca os dados exportados.
- Worker ordenado e idempotente
- O worker de auditoria roda com concorrência 1, para a versão de cada registro (MAX + 1) seguir a ordem do commit, e o INSERT é idempotente por (id, occurred_at): uma reentrega da fila não duplica nada.
- Tabela append-only e particionada
- A tabela é append-only: sem exclusão lógica, nunca editada nem apagada. É particionada por mês em occurred_at (chave primária (id, occurred_at)), com partição default como rede de segurança e uma rotina idempotente, sob advisory lock, que garante o mês atual e os três seguintes. A migração converteu a tabela existente copiando os dados e tem reversão.
- Segredos mascarados duas vezes
- Segredos (senhas, hashes, tokens, certificados, credenciais) são mascarados em qualquer profundidade do JSON na gravação e mascarados de novo na leitura, para cobrir eventos antigos.
- Duas formas de leitura
- Leitura em duas formas: o histórico de um registro, que repete as mudanças em ordem para mostrar o valor anterior de cada campo, e a lista das últimas mudanças de um tipo, com paginação por cursor (occurred_at, id) e filtros de período (padrão: 90 dias no calendário da clínica, o que limita as partições lidas), operação e autor.
- Só o que pode ser auditado
- A consulta só devolve eventos bem-sucedidos da própria clínica e de 30 tipos de registro de uma lista explícita; qualquer outro tipo é recusado. Os nomes dos registros vêm do retrato de criação, mesmo os excluídos depois, e os ids de papel viram nomes de papel.
- Conteúdo protegido por permissão
- O conteúdo é protegido por permissão: quem tem audit.read vê quem fez o quê e quando, mas os campos dos registros sensíveis só aparecem para quem também pode ler o próprio registro. Um usuário e o vínculo dele com a clínica formam uma só linha do tempo.
- Painel Histórico no front
- No front, um botão Histórico (relógio) nas listas e nas linhas abre um painel com filtros de período e de operação, o valor anterior riscado ao lado do novo, um cadeado quando o conteúdo está oculto e o carregamento de mais itens. O botão só aparece para quem tem audit.read.
- Testes com Postgres real
- Testes de integração contra Postgres real cobrem a reconstrução do valor anterior, a ocultação de escritas com falha, o isolamento entre clínicas, a permissão por tipo de registro, o cursor, a linha do tempo do usuário, as partições e o worker pela fila; um teste unitário cobre o mascaramento.
Do escrita ao registro de auditoria
Requisição
um id de correlação para todas as escritas da ação
- COMMIT
Repositório base
grava o registro e o evento no outbox, na mesma transação
Escrita com falha (etapa planejada)
evento success=false em conexão própria, e o erro original segue
- LISTEN/NOTIFY, com polling de segurança
Relay do outbox
- jobId = id do evento
Fila audit
- INSERT ... ON CONFLICT DO NOTHING
Worker de auditoria
concorrência 1: versão = MAX + 1 na ordem do commit; reentrega é no-op
audit_events
append-only, particionada por mês em occurred_at
Do pedido ao painel de histórico
Pedido com permissão audit.read
Filtros na consulta
Clínica
Tipos permitidos
lista explícita; outro tipo é recusado
Período e cursor
só as partições do período são lidas
- eventos bem-sucedidos
Reconstrução e máscara
repete as mudanças em ordem para achar o valor anterior; segredos mascarados de novo
- JSON paginado
Painel Histórico
conteúdo escondido sem a permissão de leitura do registro
export async function writeAuditEvent(
manager: EntityManager,
event: AuditEventInput,
): Promise<void> {
await manager.query(
`INSERT INTO infra.audit_events
(id, tenant_id, entity, entity_id, operation, changes, actor_id, version, success, correlation_id, occurred_at)
SELECT $1::uuid, $2::uuid, $3::varchar, $4::varchar, $5::infra.audit_events_operation_enum, $6::jsonb,
$7::varchar, COALESCE(MAX(version), 0) + 1, $8::boolean, $9::varchar, $10::timestamptz
FROM infra.audit_events
WHERE entity = $3::varchar AND entity_id = $4::varchar
ON CONFLICT (id, occurred_at) DO NOTHING`,
[/* id, tenantId, entity, entityId, operation, changes, actorId, success, correlationId, occurredAt */],
);
}const SENSITIVE_KEY = /password|secret|token|hash|pfx|certificate|credential|credenciais/i;
export function redactSensitive(value: Record<string, unknown>): Record<string, unknown> {
const walk = (node: unknown): unknown => {
if (Array.isArray(node)) return node.map(walk);
if (node === null || typeof node !== 'object') return node;
return Object.fromEntries(
Object.entries(node as Record<string, unknown>).map(([key, child]) => [
key,
SENSITIVE_KEY.test(key) && child !== null && child !== undefined ? REDACTED : walk(child),
]),
);
};
return walk(value) as Record<string, unknown>;
}await this.dataSource.transaction(async (manager) => {
await manager.query(`SELECT pg_advisory_xact_lock(hashtext('audit_events_partitions'))`);
for (let offset = 0; offset <= AUDIT_PARTITION_MONTHS_AHEAD; offset++) {
const from = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + offset, 1));
const to = new Date(Date.UTC(from.getUTCFullYear(), from.getUTCMonth() + 1, 1));
const name = `audit_events_${from.toISOString().slice(0, 7).replace('-', '_')}`;
await manager.query(
`CREATE TABLE IF NOT EXISTS "infra"."${name}" PARTITION OF "infra"."audit_events" FOR VALUES FROM ('${from.toISOString()}') TO ('${to.toISOString()}')`,
);
}
});Capturas de tela de um ambiente de desenvolvimento, com dados fictícios.



Tempo real
- Namespaces e salas
- Atualizações em tempo real com Socket.io organizadas por namespaces e salas.
- Escala horizontal
- Com o adaptador Redis, um evento emitido em qualquer instância da API chega aos usuários conectados em todas as outras, o que permite escalar horizontalmente.
- Emissão desacoplada
- Qualquer serviço emite para uma sala sem depender do módulo do gateway.
Tempo real
Serviço na instância 1
Hub de tempo real
Redis adapter
Instância 1 · sockets
Instância 2 · sockets
Instância N · sockets
Segurança
- Identidade
- Autenticação com JWT, MFA por TOTP (app autenticador, com QR code) e senhas com Argon2.
- Segredos em repouso
- Segredos de MFA e credenciais de integrações criptografados no banco com AES-256-GCM.
- RBAC verificado por teste
- RBAC com permissões por recurso e ação, e um teste que falha se alguma rota da API ficar sem permissão declarada.
- Proteção da borda
- Rate limiting distribuído em Redis, Helmet e validação de entrada com Zod; configuração validada na inicialização.
- Força bruta em duas camadas
- Anti-força-bruta em duas camadas: um limite por par IP + conta e outro, mais alto, só por conta, para o dono legítimo não ser bloqueado por quem ataca de outro IP.
- Sessões em Redis
- Sessões em Redis guardam só o hash do refresh token e têm idade máxima absoluta: renovar não prolonga a sessão para sempre. O access token leva apenas ids; as permissões vêm de uma consulta à sessão viva.
- Token só no cabeçalho
- O token viaja só no cabeçalho Authorization, nunca em cookie, o que elimina a superfície de CSRF por desenho. CORS por ambiente e CSP via Helmet.
Capturas de tela de um ambiente de desenvolvimento, com dados fictícios.

Front-end
- SPA com cadeia de guards
- SPA em React com Vite e React Router, com cadeia de guards por rota: sessão, área da plataforma, clínica e permissão.
- Estado do servidor e global
- Dados do servidor com TanStack Query, com cache e política de retentativa configuradas; estado global leve com Jotai.
- Formulários padronizados
- Formulários com react-hook-form e Zod sobre um hook próprio, que padroniza carregamento, validação e envio.
- Design system e temas
- Design system com Radix/shadcn e Tailwind, com temas visuais configuráveis por clínica.
- Detalhes de UX
- Detalhes de UX: paleta de comandos, exclusão com desfazer, aviso de alterações não salvas e listas paginadas.
- Roteamento declarativo
- Roteamento declarativo: uma tabela de rotas, um executor de guards e um de layouts, com guards compostos (sessão, plataforma, clínica, permissão). Usa o roteador de dados do React Router para bloquear a navegação com alterações não salvas.
- Token fora do React
- O estado do token fica fora do React, em um store com subscribe lido por useSyncExternalStore, para o interceptor do axios não depender de hooks. Hooks próprios padronizam lista paginada, debounce, carga de formulário e a proteção contra perda de edição.
Guards do front
Rota
Sessão válida?
não: vai para o login
Área da plataforma?
Clínica selecionada?
Tem permissão?
- sim / não
Página
Acesso negado (etapa planejada)
Capturas de tela de um ambiente de desenvolvimento, com dados fictícios.

Qualidade e entrega
- Testes com contêineres reais
- Testes de integração com Testcontainers (PostgreSQL e Redis), Supertest e dados gerados com Faker; testes e2e do front com Cypress.
- Migrations sempre em sincronia
- Teste que garante que as migrations estão sempre em sincronia com as entidades.
- API documentada e monitorada
- Documentação da API em OpenAPI, publicada com Scalar; health checks de banco e Redis com Terminus.
- PDFs e arquivos
- Geração de PDFs no servidor com Puppeteer e arquivos no S3 com URLs pré-assinadas.
- Docker e AWS
- Docker e deploy na AWS com CloudFormation.
- OpenAPI cruzada com o código
- Um teste cruza a documentação OpenAPI com o código: cada rota precisa estar documentada, e a permissão declarada no documento tem de ser igual à do decorator do handler; rota sem permissão nem exceção explícita falha o teste.
- CI e entrega
- CI com lint, typecheck, build, testes de isolamento e de migrations e varredura de segredos (gitleaks fixado por hash), com cancelamento de execuções antigas e permissões mínimas. O deploy usa GitHub OIDC e SSM, sem credenciais estáticas.
- Git hooks versionados
- Git hooks versionados: no commit, lint só dos arquivos alterados e varredura de segredos; no push, typecheck do projeto e só os testes relacionados ao que mudou.
- Operação
- Operação: encerramento limpo (shutdown hooks), variáveis de ambiente validadas na subida, migrations e seed idempotentes antes de a API iniciar, e um comando para remover e restaurar uma clínica com confirmação explícita.
it('every endpoint declares a permission or opts out explicitly', () => {
expect(endpoints.filter((e) => e.access === 'MISSING').map(label)).toEqual([]);
});
it.each(endpoints.map((e) => [label(e), e]))('%s', (_name, endpoint) => {
const e = endpoint as EndpointInfo;
const operation = doc.paths[e.path]?.[e.method];
expect(operation).toBeDefined();
const expected = e.access === 'public' || e.access === 'authenticated' ? undefined : e.access;
expect(operation!['x-required-permission']).toBe(expected);
});Capturas de tela de um ambiente de desenvolvimento, com dados fictícios.
