Resumo rápido: este README explica todas as decisões técnicas do backend, por que optei por elas e como rodar o projeto localmente (com Docker). Também explica as adaptações feitas para simular um EJB sem exigir um servidor de aplicação (WildFly), o que facilita avaliação e execução do código pelo avaliador.
- Implementação do backend em Spring Boot (API REST) + um componente chamado
BeneficioEjbServiceque mantém o nome e responsabilidades de um EJB, mas é integradamente gerenciado pelo Spring para facilitar execução local e em CI/CD. - Banco de dados: PostgreSQL (esquema e seed providos em
src/main/resources/db/e executados no startup do container do Postgres). - Documentação: OpenAPI / Swagger via
springdoc. - Validações:
jakarta.validationnas requisições. - DTOs: feitos com
record(concisos e imutáveis). - Exceptions: exceções customizadas para regras de negócio e um
GlobalExceptionHandlerpara mapear para respostas HTTP apropriadas.
- O projeto segue uma arquitetura em camadas, com separação clara entre API (Controller), camada de aplicação (Service) e domínio.
- O domínio foi isolado em um módulo separado (
ejb-module), contendo entidades e regras de negócio, evitando acoplamento com Spring ou HTTP.
O exercício original usava anotações EJB (@Stateless) e um módulo ejb-module. Para não obrigar o avaliador a subir um servidor WildFly (e evitar configuração extra no CI), optei por:
- Manter o nome
BeneficioEjbService(para preservar a intenção do desafio e facilitar revisão do código). - Para simular o comportamento de um EJB sem exigir um servidor de aplicação como wildfly, foi criado o EjbServiceConfig, que expõe o BeneficioEjbService como um @Bean, injetando o EntityManager gerenciado pelo Spring.
- Regras de criação/alteração estão dentro da entidade de domínio (
Beneficio). - O domínio contém os métodos
debit/credit/create/updatepara garantir invariantes (ex.: não permitir valor negativo). - Decisão: domínio centralizado no módulo EJB-simulado. Não criei um Repository Spring separado para evitar duas fontes de controle do BD (um único lugar gerenciando persistência evita inconsistências).
-
Usei optimistic locking via campo
@Versionna entidadeBeneficio. Justificativa:- Menor impacto na performance (comparado a locks pesimistas).
- Em cenários de alta concorrência, otimistic lock lança
OptimisticLockExceptionna segunda commit que conflitar — comportamento esperado.
-
Tratamento: o
GlobalExceptionHandlercapturaOptimisticLockException(e a variante do SpringObjectOptimisticLockingFailureException) e retorna HTTP 409 Conflict com uma mensagem amigável:Registro foi atualizado por outra operação. Tente novamente.
- Requisições validadas com
@NotBlank,@Size,@NotNull,@Positive— erros coletados e retornados por umErrorResponse(record comstatus,List<String> errorsetimestamp). - Se o JSON enviado for inválido (erro de parsing), o
GlobalExceptionHandlertrata e retorna feedback apropriado em vez de 500 genérico.
- A coluna
nometemUNIQUEno schema. Se umINSERTviolar essa constraint, o Postgres lança umaConstraintViolationException. - Estratégia: preferi capturar a exceção no
GlobalExceptionHandler(detectarConstraintViolationException/DataIntegrityViolationException) e transformar em um 400 Bad Request ou mensagem clara explicando que onomejá existe. - Alternativa não utilizada: criar checagem de unicidade no serviço (SELECT + throw) — porém isso tem race conditions se usado sozinho; por isso a constraint no DB + tratamento da exceção é mais robusto.
-
Unit / Domain tests: JUnit 5 + Mockito para mocks.
-
Cobertura:
- Testes de domínio (ex.:
BeneficioTest) cobrem regras de negócio. - Testes de serviço/EJB (mockando
EntityManager) cobrem cenários de transferência, erro de saldo, exclusão, etc. - Não foram incluídos testes de integração com banco real por se tratar de um desafio focado em lógica de domínio e regras de negócio, mantendo os testes rápidos e determinísticos.
- Testes de domínio (ex.:
- Há
Dockerfileedocker-compose.ymlpara subir API + Postgres sem precisar commitar.jarno repositório. - Docker build utiliza multi-stage: primeiro
mvn -pl backend-module -am clean package -DskipTests, depois copia otarget/*.jarpara imagem leve (runtime) docker-compose.ymlexpõe portas e injeta variáveis diretamente (não uso.envpor decisão do desafio — as variáveis estão no compose para facilitar avaliação).
Comandos úteis:
# build + up (vai construir a api e subir postgres)
docker compose up --build
# build local do backend (se quiser só compilar)
mvn -pl backend-module -am clean packagedocker compose up --build- Acesse
http://localhost:3000/swagger-ui/index.html#/para ver endpoints. - Teste os endpoints de CRUD e
POST /beneficios/transferencias.
- Frontend desenvolvido em Angular 20.
- Utilização de Standalone Components, sem uso de NgModules.
- Interface construída com Angular Material, priorizando consistência visual, acessibilidade e boa UX.
- Comunicação com backend via HttpClient.
- Tratamento centralizado de erros retornados pela API.
- Aplicação alinhada às regras de negócio do backend, como soft delete e validações de transferência.
- Angular 20
- Angular Router
- Angular Forms (Reactive Forms)
- Angular Material
- RxJS
- TypeScript 5.9
Dependências principais:
@angular/core @angular/router @angular/forms @angular/material rxjs
O frontend segue uma organização clara por responsabilidade, separando core, models, services, páginas e componentes compartilhados.
Estrutura real do projeto:
src
├── app
│ ├── core
│ │ ├── interceptors
│ │ │ └── error.interceptor.ts
│ │ ├── models
│ │ │ └── beneficio.model.ts
│ │ └── services
│ │ └── beneficio.service.ts
│ │
│ ├── pages
│ │ ├── beneficio
│ │ │ ├── beneficio-list
│ │ │ │ ├── beneficio-list.component.ts
│ │ │ │ ├── beneficio-list.component.html
│ │ │ │ └── beneficio-list.component.scss
│ │ │ └── beneficio-form-dialog
│ │ │ ├── beneficio-form-dialog.component.ts
│ │ │ ├── beneficio-form-dialog.component.html
│ │ │ └── beneficio-form-dialog.component.scss
│ │ │
│ │ └── transferencia
│ │ ├── transferencia.component.ts
│ │ ├── transferencia.component.html
│ │ └── transferencia.component.scss
│ │
│ └── shared
│ └── components
│ └── navbar
│ ├── navbar.component.ts
│ ├── navbar.component.html
│ └── navbar.component.scss
│
└── environments
├── environment.ts
└── environment.prod.ts
- A URL base da API é centralizada no environment:
http://localhost:3000/api/v1/beneficios
Os modelos do frontend espelham os contratos do backend, garantindo tipagem forte e clareza.
Principais modelos:
- Beneficio
- BeneficioCreateRequest
- BeneficioUpdateRequest
- TransferRequest
- ApiErrorResponse
A tela principal exibe um grid com todos os benefícios cadastrados.
Características:
- Listagem de benefícios ativos e inativos
- Exibição de:
- Nome
- Descrição
- Saldo (formatado em Real Brasileiro - R$)
- Status (Ativo / Inativo)
- Benefícios inativos são exibidos com destaque visual
Ações disponíveis por item:
- Editar
- Abre um dialog de edição
- Permite alterar nome e descrição
- O saldo não pode ser alterado após a criação
- Inativar (Soft Delete)
- Não remove o registro do banco
- Apenas altera o status para inativo
- Transferir
- Disponível apenas para benefícios ativos
- Implementado com Angular Material Dialog
- Utiliza Reactive Forms
- Validações no frontend alinhadas com o backend
- UX moderna e clara
No modo edição:
- Nome e descrição são editáveis
- Saldo é exibido como somente leitura
- Status pode ser alterado entre ativo e inativo
- Rota dedicada:
/transferencia - Permite selecionar:
- Benefício de origem
- Benefício de destino
- Valor da transferência
Regras aplicadas:
- Benefícios inativos não podem ser selecionados
- Origem e destino não podem ser iguais
- Valor deve ser maior que zero
- Tratamento centralizado via HttpInterceptor
- O frontend espera erros no formato:
{
"status": 400,
"timestamp": "2024-01-01T12:00:00Z",
"errors": [
"Mensagem de erro"
]
}
- As mensagens são exibidas usando Angular Material Snackbar
- Caso o erro não esteja no formato esperado, é exibida uma mensagem genérica ao usuário
- Não exibir ações que o usuário não pode executar
- Evitar exclusões físicas (soft delete)
- Feedback visual claro para erros e ações bem-sucedidas
- Interface moderna sem overengineering
Pré-requisitos:
- Node.js compatível com Angular 20
- Angular CLI 20+
Comandos:
npm install npm start
Aplicação disponível em:
http://localhost:4200
O frontend foi desenvolvido com foco em:
- Clareza de código
- Alinhamento total com o backend
- Boa experiência do usuário
- Facilidade de avaliação técnica
A aplicação está pronta para uso real e evolução futura.