API REST para gerenciamento de eventos corporativos (Summits, Feiras de Tecnologia e Congressos), desenvolvida com Spring Boot 3 e persistência em PostgreSQL gerenciada via Docker.
- Java 21 & Spring Boot 3.5+
- Spring Security & JWT (Autenticação e Autorização)
- PostgreSQL (Banco de dados relacional)
- Flyway (Migração e versionamento de banco)
- Docker & Docker Compose (Containerização do ambiente)
- MapStruct & Lombok (Produtividade e mapeamento de DTOs)
- JUnit 5, Mockito & MockMvc (Testes automatizados da camada web e de negócio)
- Auth0 JWT (Biblioteca para geração e validação de tokens JWT)
O projeto adota o padrão de empacotamento por camadas, isolando responsabilidades e facilitando testes:
config/: Configurações globais do ecossistema Spring.controller/: Camada de exposição dos endpoints HTTP (consome e retorna apenas DTOs).dto/: Objetos de transferência de dados e payloads.entity/: Entidades JPA representando o modelo relacional.enums/: Tipos enumerados de domínio (ex:RegistrationStatus).repository/: Interfaces de acesso a dados (Spring Data JPA).service/: Camada onde residem as regras de negócio, auditoria e validações.security/: Regras de filtros JWT e controle de acessos (RBAC).exception/: Tratamento global de erros (GlobalExceptionHandler).
A API utiliza autenticação stateless baseada em tokens JWT (JSON Web Tokens):
- Registro (
POST /api/v1/auth/register): Usuário cria conta com e-mail e senha. A senha é criptografada com BCrypt antes de persistir no banco. - Login (
POST /api/v1/auth/login): Usuário fornece credenciais. OAuthenticationManagervalida usandoDaoAuthenticationProvidereUserDetailsService. - Geração de Token: Após autenticação bem-sucedida, um token JWT é gerado com assinatura HMAC256 contendo:
subject: e-mail do usuárioissuer: identificação da aplicaçãoexpiration: tempo de validade (configurável, padrão 24h)
- Acesso a Endpoints Protegidos: O token é enviado no header
Authorization: Bearer <token>. OJwtAuthenticationFiltervalida e extrai as informações do usuário para cada requisição.
Problema: O endpoint de login estava lançando java.lang.StackOverflowError devido à ausência de uma implementação explícita do UserDetailsService. O Spring Security não conseguia carregar os usuários durante a autenticação.
Solução Implementada:
- Criação de
UserDetailsServiceImplque implementaUserDetailsServicee busca usuários diretamente noUserRepositorypelo e-mail. - Configuração explícita do
DaoAuthenticationProvidernoSecurityConfig, vinculando oUserDetailsServicee oPasswordEncoder(BCrypt). - Esta abordagem elimina dependências cíclicas e fornece um caminho claro para o Spring Security carregar e autenticar usuários.
O endpoint de login retorna um DTO contendo:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"type": "Bearer",
"userId": "uuid-do-usuario",
"email": "usuario@exemplo.com",
"role": "PARTICIPANT"
}Nota: Para entender o histórico e as justificativas das decisões técnicas tomadas neste projeto, acesse os Registros de Decisão Arquitetural (ADRs).
- UUIDv4 é utilizado para
User,Event,RegistrationeAddresspara mitigar problemas de enumeração de recursos expostos na URL. - Long (Sequencial) é restrito à tabela
Categorydevido ao baixo volume e natureza estática dos dados.
EVENT_ORGANIZER(Many-to-Many): Tabela intermediária criada para suportar o requisito de múltiplos organizadores responsáveis por um mesmo evento, permitindo transferência de propriedade ou co-organização.REGISTRATIONUnique Constraint: Chave de unicidade composta entreuser_id+event_id+status=CONFIRMEDaplicada a nível de banco para garantir que um participante não se inscreva duas vezes no mesmo evento.
Para mitigar condições de corrida (race conditions) quando múltiplos usuários disputam as últimas vagas de um evento simultaneamente, a arquitetura avaliou três abordagens de engenharia de software antes de definir a implementação ideal.
- Como funcionaria: O Hibernate controlaria uma coluna de versão na tabela
Event. Se duas requisições lessem a versão1e tentassem salvar, a primeira venceria e incrementaria para2. A segunda falharia lançando umaObjectOptimisticLockingFailureException. - Por que foi rejeitado: Sob picos de acesso massivos (abertura de lotes), essa abordagem geraria milhares de exceções na camada de aplicação, exigindo lógica complexa de retentativas (retry) e estressando o servidor desnecessariamente.
- Como funcionaria: A aplicação executaria um
SELECT ... FOR UPDATEao buscar o evento, travando a linha física no PostgreSQL. As demais requisições concorrentes ficariam em uma fila estrita aguardando a liberação. - Por que foi rejeitado: Embora seguro contra overbooking, reter conexões físicas do pool (
HikariPool) por muito tempo eleva drasticamente a latência do sistema. Sob alta carga, isso esgotaria o pool rapidamente, derrubando a API inteira. Além disso, abriria margem para Deadlocks caso regras futuras travassem múltiplos recursos em ordens distintas.
- Como funciona: Evita-se qualquer tipo de lock preemptivo na aplicação. A validação de capacidade e o incremento ocorrem diretamente na camada de banco de dados através de uma única instrução SQL atômica:
UPDATE event SET current_count = current_count + 1 WHERE id = :id AND current_count < capacity;
- Por que foi escolhida: O motor do PostgreSQL gerencia o isolamento dessa operação de forma extremamente performática.
- Fluxo Sênior Anti-Bug: Para impedir a inserção de registros órfãos ("dados fantasmas") na tabela de inscrições, o fluxo tradicional foi invertido:
- Primeiro, executa-se o
UPDATEcondicional acima. - O Spring Data JPA avalia o retorno do banco. Se houver
1linha afetada (vaga garantida), o fluxo prossegue e realiza oINSERTna tabelaREGISTRATION. - Se retornar
0linhas afetadas (evento lotado), uma exceção de negócio é lançada imediatamente, interrompendo a transação sem sequer tocar na tabela de inscrições.
- Primeiro, executa-se o
- Consistência de Memória: O repositório utiliza
@Modifying(clearAutomatically = true)para limpar o contexto de persistência do Hibernate imediatamente após a query nativa, eliminando qualquer risco de dados defasados em memória.
- Docker e Docker Compose instalados.
- Java 21 e Maven (se desejar rodar a aplicação localmente fora do container).
Navegue até a raiz do projeto e execute o comando:
docker compose up -dO Docker irá provisionar o banco de dados e aplicar o volume local de persistência (pgdata). O Spring Boot aplicará as migrations do Flyway automaticamente na inicialização.