4.5 KiB
FASE 1 — Domínio compartilhado e camada de dados
Objetivo
Modelo de domínio em shared/, schema completo no Postgres, infraestrutura de confiabilidade (outbox, fila, idempotência) funcionando, e autenticação. Ainda sem endpoints de negócio.
Pré-requisitos
FASE 0 concluída. Ler docs/02-MODELO-DE-DADOS.md inteiro e docs/01-ARQUITETURA.md §4 e §5.
Tarefas
1. Domínio em shared/
state/VisitState.kt e VisitStateMachine.kt conforme 01-ARQUITETURA.md §4 e o diagrama de 03-FLUXOS-E-CONTRATOS.md §1. A máquina de estados é a peça mais importante deste módulo — teste toda transição válida e inválida.
model/ — enums e value objects: VisitKind, DeliveryRule, MemberRole, MediaKind, DegradedMode, FeatureKey, GeoPoint.
dto/ — DTOs @Serializable de todos os contratos de 03-FLUXOS-E-CONTRATOS.md §6.
validation/ — validação de nome de visitante, telefone E.164, formato de unidade, tamanho de mídia. Usada no cliente e no servidor.
2. Migrations Flyway
As dez migrations de 02-MODELO-DE-DADOS.md §11, exatamente na ordem indicada. Pontos que não podem ser esquecidos:
tenant_id uuid NOT NULLem toda tabela de negócioversion integer NOT NULL DEFAULT 0onde há escrita concorrente- Nenhum
ON DELETE CASCADEem dado auditável - Índice GIST em
gates.location - Índice parcial em
visitspara estados ativos V8revogaUPDATE/DELETEemaudit_logpara o usuário da aplicaçãoV9cria as policies de RLS e as deixa desativadas
3. jOOQ
Geração de código a partir do schema migrado (Testcontainers no build, não banco local). Repositórios para os agregados principais. Todo repositório recebe tenantId como parâmetro obrigatório — sem default, sem opcional. É o que torna a ativação futura do multi-tenant um interruptor.
4. Outbox transacional
interface OutboxPublisher {
fun enfileirar(evento: DomainEvent) // participa da transação corrente
}
Publisher que lê outbox_events pendentes com FOR UPDATE SKIP LOCKED, despacha, marca published_at, e em falha aplica backoff exponencial (next_attempt_at) e grava last_error. Após 10 tentativas, alerta.
Teste obrigatório: transação que falha após enfileirar() não pode deixar evento na tabela. É exatamente a garantia que o padrão existe para dar.
5. Fila de jobs e ShedLock
interface JobScheduler {
fun agendar(tipo: JobType, payload: JsonObject, quando: Instant): Long
fun cancelar(jobId: Long)
}
Poller @Scheduled(fixedDelay = 1000) + @SchedulerLock, consumindo com FOR UPDATE SKIP LOCKED. Handlers registrados por JobType.
Teste obrigatório com duas instâncias simultâneas: cada job executa exatamente uma vez.
6. Idempotência
Filtro/interceptor que lê Idempotency-Key, consulta idempotency_keys, e:
- chave nova → executa e grava a resposta
- chave repetida com mesmo
request_hash→ devolve a resposta original - chave repetida com corpo diferente →
422 IDEMPOTENCY_KEY_REUSE
7. Segurança base
Spring Security com três cadeias de filtro conforme 06-LGPD-E-SEGURANCA.md §6: /api/v1/visitor/** (JWT efêmero), /api/v1/app/** (OIDC), /api/v1/admin/** (OIDC + MFA).
Emissão e validação do JWT de visitante: 15 min, escopo de uma visita, atado a gateId e à sessão — não ao IP: IP de celular muda no meio da sessão (CGNAT, troca de torre) e derrubaria visitante legítimo.
Conexões ao banco: Flyway como portaria (dono), aplicação como portaria_app — sem essa separação, o revoke do audit_log e a RLS futura são decorativos (dono ignora ambos). Ver 02-MODELO-DE-DADOS.md §8.
8. Auditoria
Aspecto que grava em audit_log toda mutação de entidade auditável, com before/after em jsonb.
Critérios de aceite
./gradlew :shared:allTestsverde (JVM + iOS)- Cobertura de 100% das transições da máquina de estados, válidas e inválidas
flyway migratedo zero cria o schema completo- jOOQ gera código a partir do schema migrado
- Teste: rollback de transação não deixa evento no outbox
- Teste: duas instâncias do poller executam cada job exatamente uma vez
- Teste:
UPDATEemaudit_logfalha por permissão do banco - Teste: chave de idempotência repetida devolve a resposta original
- Teste: repositório sem
tenantIdnão compila
Não faça nesta fase
- Endpoints de negócio (FASE 2)
- Integração com LiveKit, FCM ou APNs
- Qualquer tela
- Ativar RLS