Pular para o conteúdo principal

Aula 16: Integração Final — CritiqueHub Completo

Objetivos

  • Integrar todas as camadas do CritiqueHub em uma aplicação funcional end-to-end
  • Revisar os padrões arquiteturais aplicados ao longo do curso
  • Validar a aplicação contra um checklist de qualidade profissional
  • Refatorar pontos de melhoria identificados durante o desenvolvimento
  • Consolidar o aprendizado com uma visão holística do sistema

Contexto e Motivação

Ao longo de 15 aulas, construímos o CritiqueHub peça por peça: entidades JPA, repositórios, serviços CDI, endpoints REST, interfaces Jakarta Faces, testes automatizados, segurança e containerização. Agora é hora de enxergar o todo, conectar as pontas e garantir que a aplicação funcione como um sistema coeso.

Esta aula não introduz tecnologias novas — ela consolida, integra e refina. O foco está em arquitetura, qualidade e visão sistêmica, habilidades que diferenciam desenvolvedores juniores de profissionais experientes.

Conteúdo

Arquitetura Final do CritiqueHub

Visão em Camadas

O CritiqueHub segue uma arquitetura em camadas (Layered Architecture), onde cada camada tem uma responsabilidade clara e se comunica apenas com a camada imediatamente abaixo:

Mapa de Dependências

Cada componente do CritiqueHub participa de um fluxo claro. Por exemplo, o fluxo completo de "criar uma avaliação":

Inventário de Componentes

Entidades JPA

EntidadeAulaRelacionamentosPapel
Filme8, 91:N Avaliação, N:N Gênero, N:1 DiretorItem cultural principal
Avaliacao8, 9N:1 Filme, N:1 UsuárioCore do domínio
Genero9N:N FilmeClassificação
Diretor91:N FilmeMetadado de filme
Usuario141:N Avaliação, rolesIdentidade e segurança

Serviços CDI

ServiçoResponsabilidadeInjeções
FilmeServiceCRUD de filmes, busca, rankingFilmeRepository, MeterRegistry
AvaliacaoServiceCRUD de avaliações, validação de negócioAvaliacaoRepository, FilmeRepository
UsuarioServiceCadastro, perfilEntityManager

Endpoints REST

MétodoPathAuthDescrição
GET/filmesPúblicoListar filmes paginados
GET/filmes/{id}PúblicoDetalhe de um filme
GET/filmes/buscaPúblicoBusca avançada com filtros
GET/filmes/rankingPúblicoRanking por nota
POST/filmesADMINCriar filme
PUT/filmes/{id}ADMINAtualizar filme
DELETE/filmes/{id}ADMINRemover filme
POST/avaliacoesUSERCriar avaliação
GET/avaliacoes/filme/{id}PúblicoAvaliações de um filme
GET/perfilAutenticadoPerfil do usuário logado

Páginas Jakarta Faces

PáginaEscopo do BeanFuncionalidade
index.xhtmlPágina inicial
filmes.xhtml@ViewScopedLista de filmes com DataTable
filme-detalhe.xhtml@ViewScopedDetalhe + avaliações
filme-form.xhtml@ViewScopedFormulário de cadastro/edição
login.xhtmlAutenticação
perfil.xhtml@RequestScopedPerfil do usuário

Revisão de Padrões e Boas Práticas

Padrões Aplicados

PadrãoOnde AplicamosBenefício
RepositoryFilmeRepository, AvaliacaoRepositoryAbstrai acesso a dados
Service LayerFilmeService, AvaliacaoServiceCentraliza lógica de negócio
DTOFilmeDTO, AvaliacaoDTO, PaginaDTODesacopla representação de domínio
Dependency InjectionCDI em todas as camadasBaixo acoplamento, testabilidade
Interceptor@Auditavel , @TransactionalCross-cutting concerns
ObserverEventos CDIDesacoplamento de notificações

Checklist de Qualidade

Use esta lista para validar o CritiqueHub antes de considerar o projeto completo:

Arquitetura e Código:

  • Cada classe tem uma responsabilidade única (SRP)
  • Services não acessam EntityManager diretamente — usam Repositories
  • DTOs são usados para transferência entre camadas (nunca expõe entidades em endpoints)
  • Nenhuma lógica de negócio nos Managed Beans ou Resources
  • Injeção de dependência via CDI (sem new para serviços)

Persistência:

  • Entidades mapeadas com relacionamentos bidirecionais quando necessário
  • Métodos auxiliares addAvaliacao()/removeAvaliacao() para consistência
  • FetchType.LAZY como padrão, EAGER apenas quando justificado
  • Fetch Join ou Entity Graph para evitar N+1 em listagens
  • Paginação implementada em todas as listagens

Segurança:

  • Senhas armazenadas com BCrypt (nunca texto plano)
  • Endpoints de escrita protegidos com @RolesAllowed
  • Endpoints de leitura pública anotados com @PermitAll
  • Proteção CSRF ativa em formulários
  • Sem concatenação de strings em queries (prevenção SQL Injection)

Testes:

  • Testes unitários para Services com Mockito
  • Testes de integração com @QuarkusTest para endpoints
  • Testes de segurança com @TestSecurity
  • Dados de teste isolados (não dependem de estado externo)

Observabilidade e Deploy:

  • Health checks (liveness + readiness) implementados
  • Métricas de negócio registradas (Micrometer)
  • Perfis configurados (dev, test, prod)
  • Dockerfile multi-stage funcional
  • Docker Compose com banco de dados

Refatoração Guiada

Nesta seção, aplicamos melhorias comuns que surgem ao revisitar o código com uma visão sistêmica.

1. Extrair Interface para Services

Facilita testes e permite múltiplas implementações:

// Interface
public interface FilmeService {
PaginaDTO<FilmeDTO> listar(int pagina, int tamanho);
FilmeDTO buscarPorId(Long id);
FilmeDTO criar(FilmeDTO dto);
void deletar(Long id);
}

// Implementação
@ApplicationScoped
public class FilmeServiceImpl implements FilmeService {

@Inject
FilmeRepository filmeRepository;

@Override
@Timed("critiquehub.filmes.listagem")
public PaginaDTO<FilmeDTO> listar(int pagina, int tamanho) {
PanacheQuery<Filme> query = filmeRepository.findAll(Sort.by("titulo"));
query.page(Page.of(pagina, tamanho));

List<FilmeDTO> dtos = query.list().stream()
.map(this::toDTO)
.toList();

return new PaginaDTO<>(dtos, pagina, tamanho, query.count(), query.pageCount());
}

private FilmeDTO toDTO(Filme filme) {
return new FilmeDTO(filme.id, filme.titulo, filme.anoLancamento, filme.sinopse);
}
}

2. Global Exception Handler

Centraliza tratamento de erros em endpoints REST:

@Provider
public class GlobalExceptionMapper implements ExceptionMapper<Exception> {

@Override
public Response toResponse(Exception exception) {
if (exception instanceof ConstraintViolationException cve) {
Map<String, String> erros = cve.getConstraintViolations().stream()
.collect(Collectors.toMap(
v -> v.getPropertyPath().toString(),
ConstraintViolation::getMessage
));
return Response.status(Status.BAD_REQUEST)
.entity(Map.of("erros", erros))
.build();
}

if (exception instanceof NotFoundException) {
return Response.status(Status.NOT_FOUND)
.entity(Map.of("mensagem", exception.getMessage()))
.build();
}

Log.errorf(exception, "Erro inesperado: %s", exception.getMessage());
return Response.status(Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("mensagem", "Erro interno do servidor"))
.build();
}
}

3. Mapper Centralizado

Evita duplicação de código de conversão entidade/DTO:

@ApplicationScoped
public class FilmeMapper {

public FilmeDTO toDTO(Filme filme) {
return new FilmeDTO(
filme.id,
filme.titulo,
filme.anoLancamento,
filme.sinopse,
filme.generos.stream().map(g -> g.nome).toList()
);
}

public Filme toEntity(FilmeDTO dto) {
Filme filme = new Filme();
filme.titulo = dto.titulo();
filme.anoLancamento = dto.anoLancamento();
filme.sinopse = dto.sinopse();
return filme;
}

public void atualizarEntity(Filme filme, FilmeDTO dto) {
filme.titulo = dto.titulo();
filme.anoLancamento = dto.anoLancamento();
filme.sinopse = dto.sinopse();
}
}

Teste End-to-End

Um teste de integração final que valida o fluxo completo:

@QuarkusTest
@TestMethodOrder(OrderAnnotation.class)
class CritiqueHubIntegrationTest {

static Long filmeId;

@Test
@Order(1)
@TestSecurity(user = "admin@critiquehub.com", roles = "ADMIN")
void deveCriarFilme() {
filmeId = given()
.contentType(ContentType.JSON)
.body("""
{
"titulo": "Interestelar",
"anoLancamento": 2014,
"sinopse": "Exploração espacial para salvar a humanidade"
}
""")
.when()
.post("/filmes")
.then()
.statusCode(201)
.body("titulo", is("Interestelar"))
.extract().jsonPath().getLong("id");
}

@Test
@Order(2)
void deveListarFilmesPublicamente() {
given()
.when()
.get("/filmes")
.then()
.statusCode(200)
.body("$.size()", greaterThan(0));
}

@Test
@Order(3)
@TestSecurity(user = "user@critiquehub.com", roles = "USER")
void deveCriarAvaliacao() {
given()
.contentType(ContentType.JSON)
.body("""
{
"filmeId": %d,
"nota": 5,
"comentario": "Obra-prima de Christopher Nolan"
}
""".formatted(filmeId))
.when()
.post("/avaliacoes")
.then()
.statusCode(201);
}

@Test
@Order(4)
void deveVerAvaliacoesDoFilme() {
given()
.when()
.get("/avaliacoes/filme/" + filmeId)
.then()
.statusCode(200)
.body("$.size()", is(1))
.body("[0].nota", is(5));
}

@Test
@Order(5)
@TestSecurity(user = "user@critiquehub.com", roles = "USER")
void usuarioNaoDeveDeletarFilme() {
given()
.when()
.delete("/filmes/" + filmeId)
.then()
.statusCode(403);
}

@Test
@Order(6)
void healthCheckDeveEstarUp() {
given()
.when()
.get("/q/health")
.then()
.statusCode(200)
.body("status", is("UP"));
}
}

Retrospectiva do Curso

Mapa de Especificações Jakarta EE Cobertas

EspecificaçãoVersãoAulasStatus
Jakarta CDI4.04, 5Fundamentos + Avançado
Jakarta REST (JAX-RS)3.13Completo
Jakarta Persistence (JPA)3.18, 9, 10Completo
Jakarta Faces (JSF)4.011, 12, 13Completo
Jakarta Bean Validation3.05Fundamentos
Jakarta Security3.014Fundamentos
Jakarta Interceptors2.15Fundamentos

Habilidades Desenvolvidas

Próximos Passos

O curso cobriu os fundamentos sólidos do desenvolvimento Jakarta EE com Quarkus. Para continuar evoluindo:

TemaTecnologiaRecurso
MensageriaJakarta Messaging + SmallRye ReactiveQuarkus Messaging Guide
Cache DistribuídoInfinispan / RedisQuarkus Cache Guide
GraphQLSmallRye GraphQLQuarkus GraphQL Guide
gRPCQuarkus gRPCQuarkus gRPC Guide
KubernetesQuarkus KubernetesQuarkus Kubernetes Guide
Compilação NativaGraalVM + MandrelQuarkus Native Guide

Exercícios Finais

  1. Complete o CritiqueHub com todas as entidades do domínio (Diretor, Ator, Lista, PerfilDetalhado) e seus relacionamentos
  2. Implemente um endpoint /filmes/{id}/completo que retorna filme com diretor, gêneros, avaliações e média de notas — otimizado com Entity Graph
  3. Crie um fluxo completo Jakarta Faces: login → lista de filmes → detalhe → avaliar → ver avaliação publicada
  4. Execute todos os testes (unitários + integração + segurança) e garanta 100% de sucesso
  5. Containerize a aplicação final e valide com docker compose up que todos os endpoints, health checks e métricas funcionam
  6. Documente os endpoints da API REST usando OpenAPI/Swagger (adicione quarkus-smallrye-openapi) e valide em /q/swagger-ui

Referências