d5-framework-infraestrutura
Possui classes para implementar, com Spring Data JPA, o repositório e o repositório de aplicação de um agregado:
Entidade JPA
IAgregadoJpa
Marca uma classe como a entidade JPA correspondente a um agregado A. Não acrescenta métodos: apenas documenta, pelo parâmetro de tipo, qual agregado a entidade representa.
package dev.sauloaraujo.d5.framework.infraestrutura;
import dev.sauloaraujo.d5.framework.dominio.IAgregado;
public interface IAgregadoJpa<A extends IAgregado<?>> {
}
No tutorial, UnidadeFederativaJpa implementa IAgregadoJpa<UnidadeFederativa>.
Repositório
AgregadoRepositorioAbstrato
Implementação base de IAgregadoRepositorio sobre um JpaRepository do Spring Data. É parametrizada pelo identificador I1 do agregado, pelo agregado A1, pelo identificador I2 e pela entidade A2 — uma IAgregadoJpa<A1> — usados pelo Spring Data. Recebe no construtor o JpaRepository<A2, I2> e o modelo da mensagem de erro para identificador inexistente — no formato de String.format, com um %s substituído pelo identificador, usado por buscarPorIdentificador ao lançar AgregadoInexistenteExcecao — e implementa incluir, buscarPorIdentificador, alterar, excluir e existePorIdentificador convertendo entre agregado e entidade através dos métodos abstratos paraJpa, paraAgregado e paraIdentificadorJpa, que a subclasse implementa.
package dev.sauloaraujo.d5.framework.infraestrutura;
import java.util.Objects;
import org.springframework.data.jpa.repository.JpaRepository;
import dev.sauloaraujo.d5.framework.dominio.AgregadoInexistenteExcecao;
import dev.sauloaraujo.d5.framework.dominio.IAgregado;
import dev.sauloaraujo.d5.framework.dominio.IIdentificador;
import dev.sauloaraujo.d5.framework.dominio.spi.IAgregadoRepositorio;
public abstract class AgregadoRepositorioAbstrato<I1 extends IIdentificador, A1 extends IAgregado<I1>, I2, A2 extends IAgregadoJpa<A1>>
implements IAgregadoRepositorio<I1, A1> {
private final JpaRepository<A2, I2> repositorioJpa;
private final String inexistenteModelo;
protected AgregadoRepositorioAbstrato(JpaRepository<A2, I2> repositorioJpa, String inexistenteModelo) {
this.repositorioJpa = Objects.requireNonNull(repositorioJpa);
this.inexistenteModelo = Objects.requireNonNull(inexistenteModelo);
}
@Override
public A1 incluir(A1 agregado) {
Objects.requireNonNull(agregado);
var entidadeJpa = repositorioJpa.save(paraJpa(agregado));
return paraAgregado(entidadeJpa);
}
protected abstract A2 paraJpa(A1 agregado);
protected abstract A1 paraAgregado(A2 entidadeJpa);
@Override
public A1 buscarPorIdentificador(I1 identificador) {
Objects.requireNonNull(identificador);
var identificadorJpa = paraIdentificadorJpa(identificador);
var entidadeJpa = repositorioJpa.findById(identificadorJpa).orElseThrow(() -> {
var mensagem = inexistenteModelo.formatted(identificador);
return new AgregadoInexistenteExcecao(mensagem);
});
return paraAgregado(entidadeJpa);
}
protected abstract I2 paraIdentificadorJpa(I1 identificador);
@Override
public void alterar(A1 agregado) {
Objects.requireNonNull(agregado);
repositorioJpa.save(paraJpa(agregado));
}
@Override
public void excluir(I1 identificador) {
Objects.requireNonNull(identificador);
repositorioJpa.deleteById(paraIdentificadorJpa(identificador));
}
@Override
public boolean existePorIdentificador(I1 identificador) {
Objects.requireNonNull(identificador);
return repositorioJpa.existsById(paraIdentificadorJpa(identificador));
}
}
No tutorial, UnidadeFederativaRepositorio estende AgregadoRepositorioAbstrato e implementa paraJpa, paraAgregado e paraIdentificadorJpa.
Repositório de aplicação
AgregadoAplicacaoRepositorioAbstrato
Implementação base de IAgregadoAplicacaoRepositorio sobre Spring Data JPA. É parametrizada pela projeção P, pela ordenação O e pela consulta C. Implementa pesquisar(C) convertendo a consulta em um Pageable — cuja ordenação é resolvida pelo método abstrato paraPropriedadeOrdenacao, que a subclasse implementa — e delegando ao método abstrato pesquisar(C, Pageable), que a subclasse também implementa consultando o Spring Data. Na ausência de ordenação na consulta, a página não é ordenada.
Também expõe paraLike, que converte um filtro textual opcional em um padrão para cláusulas like que encontra o filtro em qualquer posição: converte o filtro para minúsculas, escapa %, _ e \ com o caractere de escape \ e o envolve com %; na ausência de filtro, devolve null. Para que o padrão funcione, a consulta JPQL que o recebe deve, portanto, comparar com a coluna em minúsculas, declarar o caractere de escape e ignorar o filtro quando ele for null — por exemplo, :nome is null or lower(u.nome) like :nome escape '\'.
package dev.sauloaraujo.d5.framework.infraestrutura;
import java.util.Objects;
import java.util.Optional;
import org.jspecify.annotations.Nullable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.query.EscapeCharacter;
import dev.sauloaraujo.d5.framework.aplicacao.IConsulta;
import dev.sauloaraujo.d5.framework.aplicacao.IConsultaResultado;
import dev.sauloaraujo.d5.framework.aplicacao.IOrdenacao;
import dev.sauloaraujo.d5.framework.aplicacao.IProjecao;
import dev.sauloaraujo.d5.framework.aplicacao.impl.ConsultaResultado;
import dev.sauloaraujo.d5.framework.aplicacao.spi.IAgregadoAplicacaoRepositorio;
public abstract class AgregadoAplicacaoRepositorioAbstrato<P extends IProjecao<?>, O extends Enum<O> & IOrdenacao<P>, C extends IConsulta<O>>
implements IAgregadoAplicacaoRepositorio<P, C> {
@Override
public IConsultaResultado<P> pesquisar(C consulta) {
Objects.requireNonNull(consulta);
var pagina = pesquisar(consulta, paraPageable(consulta));
return new ConsultaResultado<>(pagina.getContent(), pagina.getTotalElements());
}
protected abstract Page<P> pesquisar(C consulta, Pageable pageable);
protected abstract String paraPropriedadeOrdenacao(O ordenacao);
protected @Nullable String paraLike(Optional<String> trechoOpcional) {
Objects.requireNonNull(trechoOpcional);
return trechoOpcional.map(trecho -> "%" + EscapeCharacter.DEFAULT.escape(trecho.toLowerCase()) + "%")
.orElse(null);
}
private Pageable paraPageable(C consulta) {
var indicePagina = consulta.getIndicePagina();
var tamanhoPagina = consulta.getTamanhoPagina();
var ordenacao = paraOrdenacao(consulta);
return PageRequest.of(indicePagina, tamanhoPagina, ordenacao);
}
private Sort paraOrdenacao(C consulta) {
return consulta.getOrdenacao().map(ordenacao -> {
var direcao = consulta.isDescendente() ? Sort.Direction.DESC : Sort.Direction.ASC;
return Sort.by(direcao, paraPropriedadeOrdenacao(ordenacao));
}).orElse(Sort.unsorted());
}
}
No tutorial, UnidadeFederativaAplicacaoRepositorio estende AgregadoAplicacaoRepositorioAbstrato, implementa pesquisar(UnidadeFederativaConsulta, Pageable) e paraPropriedadeOrdenacao, e usa paraLike para filtrar pela sigla e pelo nome. Já MunicipioAplicacaoRepositorio mapeia, em paraPropriedadeOrdenacao, a ordenação pelo nome da unidade federativa para uma propriedade qualificada pelo alias de uma junção da consulta.
Validação
ValidacaoConfiguracao
Fornece ao Hibernate, através de um HibernatePropertiesCustomizer, a ValidatorFactory que ele usa para validar as entidades JPA antes de gravá-las, configurada com a localidade pt-BR, para que as mensagens das violações fiquem em português independentemente da localidade padrão da JVM. Fecha a ValidatorFactory quando o contexto do Spring é encerrado. É registrada como autoconfiguração do módulo. Veja Validação das entidades JPA.
package dev.sauloaraujo.d5.framework.infraestrutura;
import java.util.Locale;
import org.hibernate.cfg.ValidationSettings;
import org.hibernate.validator.HibernateValidator;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.hibernate.autoconfigure.HibernatePropertiesCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import jakarta.validation.Validation;
import jakarta.validation.ValidatorFactory;
@Configuration
public class ValidacaoConfiguracao implements DisposableBean {
private final ValidatorFactory fabricaValidador = Validation.byProvider(HibernateValidator.class).configure()
.defaultLocale(Locale.of("pt", "BR")).buildValidatorFactory();
@Bean
HibernatePropertiesCustomizer validacaoEntidadesJpa() {
return propriedades -> propriedades.put(ValidationSettings.JAKARTA_VALIDATION_FACTORY, fabricaValidador);
}
@Override
public void destroy() {
fabricaValidador.close();
}
}
Segurança
SegurancaServico
Implementação de ISegurancaServico sobre o Spring Security: lê o Authentication do SecurityContextHolder, lançando NaoAutenticadoExcecao quando não há usuário autenticado — inclusive quando a autenticação é anônima.
package dev.sauloaraujo.d5.framework.infraestrutura;
import java.util.List;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import dev.sauloaraujo.d5.framework.dominio.spi.ISegurancaServico;
import dev.sauloaraujo.d5.framework.dominio.spi.NaoAutenticadoExcecao;
public class SegurancaServico implements ISegurancaServico {
@Override
public String getUsuario() {
return getAuthentication().getName();
}
private Authentication getAuthentication() {
var contexto = SecurityContextHolder.getContext();
var autenticacao = contexto.getAuthentication();
if (autenticacao == null || !autenticacao.isAuthenticated()
|| autenticacao instanceof AnonymousAuthenticationToken) {
throw new NaoAutenticadoExcecao("Usuário não autenticado");
}
return autenticacao;
}
@Override
public List<String> getAutorizacoes() {
return getAuthentication().getAuthorities().stream().map(GrantedAuthority::getAuthority).toList();
}
}
Diferente das implementações de agregado, que o registro de beans descobre por herança de interface, SegurancaServico é registrado diretamente como autoconfiguração do módulo — é a única implementação de ISegurancaServico esperada em uma aplicação D5.
Registro de beans
AgregadoRegistrador
Registra como beans, sem que precisem de anotações do Spring, as implementações de IAgregadoRepositorio, IAgregadoAplicacaoRepositorio, IAgregadoServico e IAgregadoAplicacaoServico. É registrado como autoconfiguração do módulo. Veja Registro de beans para os detalhes e o código.