4.9 KiB
Prompt 03 — Migrations do db_empresa_<slug> (tenant) e criação dinâmica de database
Contexto
Prompts 01 e 02 concluídos: projeto no ar, db_master com schema completo. Leia 10-tracker-progresso.md antes de começar.
Este prompt cria: (a) o schema que roda dentro de cada db_empresa_<slug>, (b) o mecanismo que cria um novo database Postgres + roda essas migrations quando uma empresa nova é cadastrada, e (c) o cache de conexões por tenant.
Objetivo
Implementar TenantDataSourceProvider (cria/reaproveita conexão por empresa_id) e TenantProvisioner (cria o database físico + roda migrations quando uma empresa é criada), e as migrations do schema operacional (condomínios, contatos, avisos etc. da arquitetura v3.0).
Especificação
1. Migrations — src/main/resources/migrations/tenant/
Criar, na ordem, usando o DDL exato da seção 2 do documento de arquitetura v3.0:
001_condominios.sql002_contatos.sql003_contato_condominio.sql004_funcionario_condominio.sql005_espacos_comuns.sql006_assembleias.sql007_documentos.sql008_avisos.sql009_aviso_documentos.sql010_aviso_destinatarios.sql011_log_auditoria.sql012_indices.sql:
CREATE INDEX idx_contato_condominio_condominio ON contato_condominio(condominio_id);
CREATE INDEX idx_contato_condominio_contato ON contato_condominio(contato_id);
CREATE INDEX idx_funcionario_condominio_condominio ON funcionario_condominio(condominio_id);
CREATE INDEX idx_espacos_condominio ON espacos_comuns(condominio_id);
CREATE INDEX idx_assembleias_condominio ON assembleias(condominio_id, data_hora);
CREATE INDEX idx_documentos_condominio ON documentos(condominio_id);
CREATE INDEX idx_avisos_condominio ON avisos(condominio_id, data_publicacao);
CREATE INDEX idx_avisos_assembleia ON avisos(assembleia_id);
CREATE INDEX idx_aviso_destinatarios_aviso ON aviso_destinatarios(aviso_id);
Importante: confira o DDL contra o documento v3.0 antes de escrever — avisos.assembleia_id é opcional (nullable), aviso_destinatarios.destinatario_id NÃO tem FK (referencia contatos.id OU db_master.funcionarios.id dependendo de tipo_destinatario), funcionario_condominio.funcionario_id também não tem FK (cross-database).
2. TenantProvisioner
Classe responsável por, dado um nome e slug de empresa:
- Conectar no Postgres com um usuário administrativo (não o
appcomum) e rodarCREATE DATABASE db_empresa_<slug>(sanitizar o slug: só[a-z0-9_], gerar erro claro se inválido). - Abrir conexão no novo database recém-criado.
- Rodar
MigrationRunner.runMigrations(conn, "migrations/tenant")nele. - Retornar sucesso/erro de forma que o chamador (Prompt 05, ao cadastrar empresa) possa tratar — se falhar no meio, o ideal é logar claramente e deixar o registro em
empresas.statuscomo algo comoprovisionamento_pendente(pode adicionar esse status à coluna livremente, éVARCHAR).
3. TenantDataSourceProvider
- Um
Map<Long empresaId, Connection>(ou pool simples por tenant) em memória, populado sob demanda: na primeira requisição que precisa do tenant X, abre a conexão emdb_empresa_<database_name>(buscandodatabase_namena tabelaempresasdo master) e guarda no cache. - Método
getConnection(long empresaId)— se não estiver no cache, buscadatabase_nameno master, conecta, guarda, retorna. - Não precisa de invalidação de cache sofisticada nesta fase — documentar como ponto de atenção futuro (ex: quando uma empresa é cancelada/bloqueada, talvez fechar a conexão do cache; deixar um método
invalidar(empresaId)já pronto, mesmo que não seja chamado ainda).
4. DAOs básicos do tenant (sem endpoints ainda — vêm no Prompt 06)
Criar em com.seconectai.condocontab.tenant.dao, todos recebendo a Connection do tenant como parâmetro (não guardam estado):
CondominioDao(inserir,buscarPorId,listarTodos)ContatoDao(inserir,buscarPorId)ContatoCondominioDao(vincular,listarPorCondominio(condominioId),buscarSindicoAtual(condominioId)— query comWHERE tipo='sindico' AND status='ativo')
Critérios de aceite
- Rodar
TenantProvisioner.provisionar("Empresa Teste", "empresa-teste")cria de fato um novo databasedb_empresa_empresa_testeno Postgres, com as 11 tabelas do schema tenant. - Rodar de novo para outro slug cria outro database, isolado do primeiro (inserir um condomínio em um não aparece no outro).
TenantDataSourceProvider.getConnection(empresaId)retorna conexão válida e reaproveita a mesma conexão em chamadas subsequentes (validar com log ou teste).- Slug inválido (ex: com espaço ou maiúscula) é rejeitado com mensagem de erro clara, sem tentar criar o database.
ContatoCondominioDao.buscarSindicoAtualretornanull/vazio quando não há síndico ativo, sem lançar exceção.
Ao concluir
Atualizar 10-tracker-progresso.md: Prompt 03 concluído, data, resumo. Confirmar Prompts 01 e 02 seguem marcados.