Skip to content

adszf/portfolio-api

Repository files navigation

Portfolio API

API REST para gerenciamento de portfólio de projetos. Projetos são criados e gerenciados localmente; membros são 100% externos, consultados e criados via API externa mockada (WireMock em testes).


O que a API faz

  • Gerencia o ciclo de vida completo de projetos (criação, atualização, transição de status, exclusão soft)
  • Associa e desassocia membros funcionários a projetos
  • Valida transições de status sequenciais com regras de negócio
  • Calcula classificação de risco dinamicamente (orçamento + prazo)
  • Delega operações de membros para API externa, com cache Redis e Circuit Breaker
  • Gera relatório do portfólio com totais por status, orçamento e membros alocados

Stack e Tecnologias

Tecnologia Uso
Java 21 Linguagem
Spring Boot 3.5 Framework principal
Spring Data JPA + Hibernate Persistência
PostgreSQL Banco de dados
Flyway Migrations versionadas por domínio
Spring Security (Basic Auth) Autenticação
Spring Cache + Redis Cache distribuído
Resilience4j Circuit Breaker na integração com API externa
Spring RestClient HTTP client para API externa de membros
MapStruct Mapeamento DTO ↔ Entidade
Lombok Redução de boilerplate
SpringDoc OpenAPI Documentação Swagger em /swagger-ui.html
Micrometer + Prometheus Métricas em /actuator/prometheus
Testcontainers PostgreSQL + Redis reais nos testes de integração
WireMock Mock da API externa de membros nos testes
JaCoCo Cobertura mínima de 70% em service e validation

Estrutura de Pastas

src/main/java/com/portfolio/api/
├── client/               # Integração com API externa de membros
│   ├── MembroExternalClient.java        # Interface do client
│   └── MembroRestClientImpl.java        # Implementação com CircuitBreaker
├── config/               # Configurações Spring
│   ├── CacheConfig.java                 # Redis + TTLs por cache
│   ├── JacksonConfig.java               # ObjectMapper (BigDecimal, datas, Redis)
│   ├── JpaAuditConfig.java              # Auditoria JPA (createdBy)
│   ├── OpenApiConfig.java               # Swagger / OpenAPI
│   ├── RestClientConfig.java            # Bean do RestClient para membros
│   └── SecurityConfig.java              # Basic Auth, rotas públicas
├── controller/           # Endpoints REST
│   ├── MembroController.java            # GET /membros, POST /membros
│   ├── ProjetoController.java           # CRUD + status + membros
│   └── RelatorioController.java         # GET /relatorios/portfolio
├── dto/
│   ├── request/                         # Payloads de entrada
│   └── response/                        # Payloads de saída
├── entity/               # Entidades JPA
│   ├── BaseEntity.java                  # id, createdAt, updatedAt, createdBy, active
│   ├── Projeto.java
│   └── ProjetoMembro.java               # Tabela de associação N:N
├── enums/
│   ├── StatusProjeto.java               # 8 status possíveis
│   └── ClassificacaoRisco.java          # BAIXO, MEDIO, ALTO
├── exception/            # Exceções de domínio + handler global (RFC 7807)
├── filter/
│   └── CorrelationIdFilter.java         # X-Correlation-ID para rastreio de logs
├── mapper/
│   └── ProjetoMapper.java               # MapStruct: ProjetoCreateRequest → Projeto
├── repository/
│   ├── ProjetoRepository.java
│   └── ProjetoMembroRepository.java
├── service/              # Interfaces de serviço
│   └── impl/                            # Implementações
├── specification/
│   └── ProjetoSpecification.java        # Filtros dinâmicos JPA Criteria
└── validation/
    ├── RiskClassificationCalculator.java # Cálculo de risco por orçamento e prazo
    └── StatusTransitionValidator.java    # Máquina de estados do projeto

src/main/resources/
├── application.yml                      # Config base
├── application-dev.yml                  # Config local (datasource, redis)
├── application-prod.yml                 # Config produção
├── application-test.yml                 # Config testes (Testcontainers)
└── db/migration/
    ├── projeto/       V201__create_projeto_table.sql
    └── projeto_membro/V301__create_projeto_membro_table.sql

Endpoints

Membros (/api/v1/membros)

Método Rota Descrição
POST /membros Cria membro na API externa
GET /membros Lista todos os membros (API externa + cache)
GET /membros/{externalId} Busca membro por ID externo

Projetos (/api/v1/projetos)

Método Rota Descrição
POST /projetos Cria projeto (status inicial: EM_ANALISE)
GET /projetos Lista paginada com filtros (nome, status, orçamento, datas)
GET /projetos/{id} Busca projeto por ID
PUT /projetos/{id} Atualiza projeto completo
PATCH /projetos/{id} Atualiza projeto parcial
PATCH /projetos/{id}/status Transição de status
DELETE /projetos/{id} Soft delete
POST /projetos/{id}/membros Associa membro ao projeto
DELETE /projetos/{id}/membros/{externalId} Desassocia membro

Relatórios (/api/v1/relatorios)

Método Rota Descrição
GET /relatorios/portfolio Relatório geral do portfólio

Fluxo de Status do Projeto

EM_ANALISE
    └─► ANALISE_REALIZADA
            └─► ANALISE_APROVADA
                    └─► INICIADO  ← requer ao menos 1 membro associado
                            └─► PLANEJADO
                                    └─► EM_ANDAMENTO
                                                └─► ENCERRADO (seta dataRealTermino automaticamente)

Qualquer status (exceto ENCERRADO e CANCELADO) → CANCELADO

Regras de Negócio

Classificação de Risco (calculada dinamicamente)

Risco Orçamento Prazo
BAIXO ≤ R$100.000 ≤ 3 meses
MEDIO R$100.001 – R$500.000 3 a 6 meses
ALTO > R$500.000 > 6 meses

Condições são OR — basta uma para elevar o risco. ALTO tem prioridade sobre MÉDIO.

Gerente Responsável

  • Apenas membros com atribuicao = "gerente" podem ser definidos como gerente responsável (na criação e atualização do projeto)
  • Retorna HTTP 422 caso a atribuição seja diferente de "gerente"

Associação de Membros

  • Apenas membros com atribuicao = "funcionário" podem ser associados a projetos
  • Máximo de 10 membros por projeto
  • Máximo de 3 projetos ativos simultâneos por membro (excluídos ENCERRADO e CANCELADO)
  • Desassociação mantém histórico (soft delete via active = false)
  • Re-associação após desassociação é permitida
  • Associação e desassociação são bloqueadas em projetos com status ENCERRADO ou CANCELADO (HTTP 422)

Cache Redis

Cache TTL Invalidação
membros 15 min create no MembroService
membros-lista 15 min create no MembroService
projetos 5 min update, updateStatus, associarMembro, desassociarMembro, delete
relatorio-portfolio 5 min updateStatus, delete

Circuit Breaker (Resilience4j) — membro-api

Protege todas as chamadas à API externa de membros (create, findAll, findById).

Parâmetro Valor
Janela de avaliação 10 chamadas
Threshold de falha 50%
Threshold de chamadas lentas 80% (acima de 3s)
Tempo em estado OPEN 30s
Chamadas permitidas em HALF-OPEN 3
Transição automática OPEN → HALF-OPEN habilitada

Estados: CLOSEDOPEN (após falhas) → HALF-OPEN (tentativa) → CLOSED ou OPEN

O estado do circuit breaker é exposto em /actuator/health.


Autenticação

Basic Auth. Credenciais padrão (desenvolvimento):

  • Usuário: admin
  • Senha: admin123

Rotas públicas (sem autenticação): /actuator/health, /actuator/info, /swagger-ui/**, /v3/api-docs/**


Variáveis de Ambiente

Variável Padrão Descrição
APP_EXTERNAL_MEMBRO_URL http://localhost:8081 URL da API externa de membros
SPRING_DATASOURCE_URL via application-dev.yml URL do PostgreSQL
SPRING_DATA_REDIS_HOST via application-dev.yml Host do Redis

Como Rodar Localmente

# Subir infraestrutura
docker compose up -d

# Rodar a aplicação
./mvnw spring-boot:run

# Rodar testes (unitários + integração)
./mvnw verify

Swagger disponível em: http://localhost:8080/swagger-ui.html


API Externa de Membros — JSON Server

A pasta json-server/ contém um servidor REST fake baseado em json-server, usado para simular a API externa de membros em desenvolvimento e testes manuais.

Por que foi usado

A arquitetura da API define que membros são um domínio totalmente externo — não há tabela local de membros. Em produção, existiria um serviço real. Para desenvolvimento local e testes manuais via Postman, o JSON Server oferece uma API REST funcional sem precisar subir outro serviço real, permitindo:

  • Criar membros via POST /membros com UUID gerado automaticamente
  • Listar e buscar membros via GET /membros e GET /membros/{id}
  • Persistir os dados em db.json entre reinicializações
  • Rodar com um único comando Node, sem configuração de banco

Em testes automatizados (integração), o mock é feito via WireMock — mais controlado e sem dependência de processo externo.

Como rodar

cd json-server
npm install       # apenas na primeira vez
node server.js

Servidor disponível em: http://localhost:8081

Endpoints disponíveis

Método Rota Descrição
POST /membros Cria membro (gera id UUID e createdAt automaticamente)
GET /membros Lista todos os membros
GET /membros/{id} Busca membro por ID

Estrutura do db.json

{
  "membros": [
    {
      "id": "035ea832-5aba-4a5c-b302-a874eeb6b7fb",
      "nome": "adson",
      "atribuicao": "funcionário",
      "createdAt": "2026-04-01T20:54:53.534Z"
    }
  ]
}

O campo id retornado pelo JSON Server é mapeado para externalId no MembroResponse via @JsonAlias("id").


Rastreabilidade

Toda requisição recebe um X-Correlation-ID no header de resposta. Se o cliente enviar o header na requisição, o mesmo valor é propagado. O ID aparece em todos os logs da thread via MDC — útil para correlacionar logs de uma requisição específica.


About

API REST para gerenciamento de portfólio de projetos

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages