Lição 2: município
Nesta lição, você vai acrescentar ao subdomínio Geografia o agregado município, que referencia a unidade federativa criada na lição 1. Ao final, o projeto corresponde ao diretório exemplo2 do repositório de D5.
O município segue a mesma estrutura da unidade federativa — objetos de valor, agregado, repositório, serviço, projeção, consulta, ordenação, entidade JPA e controlador —, por isso esta lição explica apenas o que muda:
- o identificador do município contém o identificador da unidade federativa;
- o município referencia a unidade federativa pelo identificador, e não por um objeto;
- a entidade JPA do município, por sua vez, referencia a da unidade federativa por uma associação
@ManyToOne; - o serviço valida a existência de outro agregado e oferece uma ação além das quatro operações básicas: extinguir;
- a projeção traz o nome da unidade federativa, obtido por junção na consulta JPQL;
- a unidade federativa passa a ser referenciada e, por isso, não pode mais ser excluída enquanto houver municípios nela.
Município
Um município é identificado por um código IBGE de sete dígitos e possui um nome único na unidade federativa a que pertence. Um município pode ser extinto, o que o mantém cadastrado, mas impede que seja alterado. Seguindo as convenções, o pacote do agregado é com.example.exemplo.dominio.geografia.municipio. Crie o package-info.java desse pacote, anotado com @NullMarked:
@org.jspecify.annotations.NullMarked
package com.example.exemplo.dominio.geografia.municipio;
Domínio
Código IBGE
Os dois primeiros dígitos do código IBGE de um município são o código IBGE da unidade federativa a que ele pertence. Por isso, além de validar o formato com ObjetoValorRegexAbstrato e identificar o município com IIdentificador, o objeto de valor expõe getUnidadeFederativa, que deriva o código IBGE da unidade federativa desses dois dígitos.
package com.example.exemplo.dominio.geografia.municipio;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaCodigoIbge;
import dev.sauloaraujo.d5.framework.dominio.IIdentificador;
import dev.sauloaraujo.d5.framework.dominio.impl.ObjetoValorRegexAbstrato;
public final class MunicipioCodigoIbge extends ObjetoValorRegexAbstrato implements IIdentificador {
public static final int TAMANHO = 7;
private static final String FORMATO = "\\d{" + TAMANHO + "}";
private static final String MENSAGEM = "O código IBGE do município deve conter " + TAMANHO + " dígitos";
public MunicipioCodigoIbge(String valor) {
super(valor, FORMATO, MENSAGEM);
}
public UnidadeFederativaCodigoIbge getUnidadeFederativa() {
return new UnidadeFederativaCodigoIbge(toString().substring(0, UnidadeFederativaCodigoIbge.TAMANHO));
}
}
Agregado
O município referencia a unidade federativa apenas pelo seu identificador, derivado do código IBGE, e não por um objeto UnidadeFederativa: cada agregado é carregado, validado e persistido isoladamente, e um agregado só conhece os demais pelos seus identificadores.
O indicador de extinção é recebido no construtor como Optional<Boolean> — um município incluído sem esse indicador não está extinto. Um município só se torna extinto através de extinguir, que devolve um novo município, em vez de alterar o atual, e, validando o estado do município, rejeita a extinção de um município já extinto.
package com.example.exemplo.dominio.geografia.municipio;
import java.util.Objects;
import java.util.Optional;
import org.apache.commons.lang3.Validate;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaCodigoIbge;
import dev.sauloaraujo.d5.framework.dominio.IAgregado;
public final class Municipio implements IAgregado<MunicipioCodigoIbge> {
public static final String EXTINTO_MODELO = "O município %s está extinto";
private final MunicipioCodigoIbge identificador;
private final String nome;
private final boolean extinto;
public Municipio(MunicipioCodigoIbge identificador, String nome, Optional<Boolean> extinto) {
this.identificador = Objects.requireNonNull(identificador);
this.nome = Validate.notBlank(nome, "Nome do município em branco");
this.extinto = Objects.requireNonNull(extinto).orElse(false);
}
public Municipio extinguir() {
Validate.validState(!extinto, EXTINTO_MODELO, identificador);
return new Municipio(identificador, nome, Optional.of(true));
}
@Override
public Optional<MunicipioCodigoIbge> getIdentificador() {
return Optional.of(identificador);
}
public String getNome() {
return nome;
}
public UnidadeFederativaCodigoIbge getUnidadeFederativa() {
return identificador.getUnidadeFederativa();
}
public boolean isExtinto() {
return extinto;
}
}
Repositório
O nome de um município é único apenas na unidade federativa a que ele pertence. Por isso, existePorNome recebe, além do nome e do identificador a ignorar, a unidade federativa em que a verificação é feita. Crie o pacote spi e seu package-info.java, anotado com @NullMarked:
@org.jspecify.annotations.NullMarked
package com.example.exemplo.dominio.geografia.municipio.spi;
package com.example.exemplo.dominio.geografia.municipio.spi;
import java.util.Optional;
import com.example.exemplo.dominio.geografia.municipio.Municipio;
import com.example.exemplo.dominio.geografia.municipio.MunicipioCodigoIbge;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaCodigoIbge;
import dev.sauloaraujo.d5.framework.dominio.spi.IAgregadoRepositorio;
public interface IMunicipioRepositorio extends IAgregadoRepositorio<MunicipioCodigoIbge, Municipio> {
boolean existePorNome(String nome, UnidadeFederativaCodigoIbge unidadeFederativa,
Optional<MunicipioCodigoIbge> identificadorIgnorado);
}
Autorizações
AgregadoAutorizacoes representa apenas as quatro operações básicas. Como o município oferece também a ação de extinguir, suas autorizações estendem essa classe, acrescentando a autorização extinguir.
package com.example.exemplo.dominio.geografia.municipio;
import dev.sauloaraujo.d5.framework.dominio.impl.AgregadoAutorizacoes;
public final class MunicipioAutorizacoes extends AgregadoAutorizacoes<Municipio> {
private final boolean extinguir;
public MunicipioAutorizacoes(boolean incluir, boolean buscar, boolean alterar, boolean excluir, boolean extinguir) {
super(incluir, buscar, alterar, excluir);
this.extinguir = extinguir;
}
public boolean getExtinguir() {
return extinguir;
}
}
Serviço
Como na unidade federativa, o serviço estende AgregadoServicoAbstrato, agora parametrizado por MunicipioAutorizacoes, e libera a busca a qualquer usuário e as demais operações — inclusive extinguir — somente a administradores. As autorizações dependem apenas do usuário, e não do estado do município: um administrador continua autorizado a alterar e a extinguir um município extinto, e é o serviço que rejeita essas operações, validando o estado do município. Autorização e estado são coisas distintas — a primeira diz quem pode executar uma operação; o segundo, se a operação é possível —, e por isso a rejeição de uma operação sobre um município extinto responde 409, e não 403. Além de IMunicipioRepositorio, ele recebe IUnidadeFederativaRepositorio, para verificar a existência da unidade federativa do município. Em validarInclusao e validarAlteracao, o serviço valida o argumento, rejeitando um município recebido já extinto, que só pode se tornar extinto pela ação de extinguir. Em validarInclusaoAlteracao, o serviço:
- usa
validarExistencia, com o modelo de mensagem deUnidadeFederativaServico, para exigir que a unidade federativa exista — caso contrário, lançaAgregadoInexistenteExcecao, e a requisição responde 404, como quando o próprio município não existe; - usa
validarInexistenciaeexistePorNomepara exigir que o nome seja único na unidade federativa.
Em validarAlteracao, depois de validar o argumento, o serviço também rejeita a alteração de um município extinto, validando o estado do município existente. A regra sobre o município recebido não fica em validarInclusaoAlteracao por causa da ordem das validações de AgregadoServicoAbstrato: validarInclusao é chamado antes da verificação de inexistência do identificador, e validarAlteracao, depois da verificação de existência do município, mas ambos antes de validarInclusaoAlteracao. Assim, a inclusão de um município extinto é rejeitada como extinção indevida mesmo que seu código já exista, e a alteração de um município extinto é rejeitada porque ele está extinto mesmo que o nome recebido já seja usado na unidade federativa. Já a alteração de um município inexistente responde 404, ainda que ele seja recebido extinto. Por fim, extinguir valida a autorização extinguir com validarAutorizacao e a existência do município com validarExistencia — ambos herdados de AgregadoServicoAbstrato — e persiste o município devolvido por Municipio.extinguir.
package com.example.exemplo.dominio.geografia.municipio;
import java.util.Objects;
import java.util.Optional;
import org.apache.commons.lang3.Validate;
import com.example.exemplo.dominio.Autorizacao;
import com.example.exemplo.dominio.geografia.municipio.spi.IMunicipioRepositorio;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaServico;
import com.example.exemplo.dominio.geografia.unidadefederativa.spi.IUnidadeFederativaRepositorio;
import dev.sauloaraujo.d5.framework.dominio.impl.AgregadoServicoAbstrato;
import dev.sauloaraujo.d5.framework.dominio.spi.ISegurancaServico;
public final class MunicipioServico
extends AgregadoServicoAbstrato<MunicipioCodigoIbge, Municipio, MunicipioAutorizacoes> {
public static final String IDENTIFICADOR_INEXISTENTE_MODELO = "Não existe município com o identificador %s";
public static final String IDENTIFICADOR_EXISTENTE_MODELO = "Já existe um município com o identificador %s";
public static final String NOME_EXISTENTE_MODELO = "Já existe um município com o nome %s na unidade federativa";
public static final String EXTINCAO_INDEVIDA = "O município só pode ser extinto pela ação de extinção";
private final IMunicipioRepositorio repositorio;
private final IUnidadeFederativaRepositorio unidadeFederativaRepositorio;
public MunicipioServico(ISegurancaServico segurancaServico, IMunicipioRepositorio repositorio,
IUnidadeFederativaRepositorio unidadeFederativaRepositorio) {
super(segurancaServico, repositorio, IDENTIFICADOR_EXISTENTE_MODELO, IDENTIFICADOR_INEXISTENTE_MODELO);
this.repositorio = Objects.requireNonNull(repositorio);
this.unidadeFederativaRepositorio = Objects.requireNonNull(unidadeFederativaRepositorio);
}
@Override
public MunicipioAutorizacoes criarAutorizacoes(Optional<MunicipioCodigoIbge> identificador) {
Objects.requireNonNull(identificador);
var administrador = getSegurancaServico().possuiAutorizacao(Autorizacao.ADMINISTRADOR.name());
return new MunicipioAutorizacoes(administrador, true, administrador, administrador, administrador);
}
@Override
protected void validarInclusao(Municipio agregado) {
Objects.requireNonNull(agregado);
Validate.isTrue(!agregado.isExtinto(), EXTINCAO_INDEVIDA);
}
@Override
protected void validarInclusaoAlteracao(Municipio agregado) {
Objects.requireNonNull(agregado);
validarExistencia(unidadeFederativaRepositorio::existePorIdentificador, agregado.getUnidadeFederativa(),
UnidadeFederativaServico.IDENTIFICADOR_INEXISTENTE_MODELO);
validarInexistencia(
nome -> repositorio.existePorNome(nome, agregado.getUnidadeFederativa(), agregado.getIdentificador()),
agregado.getNome(), NOME_EXISTENTE_MODELO);
}
@Override
protected void validarAlteracao(Municipio agregado) {
Objects.requireNonNull(agregado);
Validate.isTrue(!agregado.isExtinto(), EXTINCAO_INDEVIDA);
var identificador = agregado.getIdentificador().orElseThrow();
var municipio = repositorio.buscarPorIdentificador(identificador);
Validate.validState(!municipio.isExtinto(), Municipio.EXTINTO_MODELO, identificador);
}
public void extinguir(MunicipioCodigoIbge identificador) {
Objects.requireNonNull(identificador);
validarAutorizacao(Optional.of(identificador), MunicipioAutorizacoes::getExtinguir);
validarExistencia(identificador);
var municipio = repositorio.buscarPorIdentificador(identificador);
repositorio.alterar(municipio.extinguir());
}
}
Testes
Escreva os cenários cobrindo, além da inclusão, da busca, da alteração e da exclusão, a ação de extinguir e as regras de existência da unidade federativa, de unicidade do nome na unidade federativa e de imutabilidade de um município extinto:
# language: pt
Funcionalidade: Gerenciar municípios
Para manter o cadastro de municípios da aplicação
Como usuário do sistema
Quero incluir, buscar, alterar, excluir e extinguir municípios,
respeitando as regras de unidade federativa, unicidade de nome e de autorização
Contexto:
Dado que não existe nenhum município cadastrado
E que existe a unidade federativa de código IBGE "35"
Cenário: Incluir um município
Dado que sou um administrador do cadastro de municípios
Quando eu incluo o município de código IBGE "3550308", nome "São Paulo"
Então o município é incluído com sucesso
E o município de código IBGE "3550308" passa a existir
Cenário: Não é possível incluir um município sem autorização de administrador
Dado que sou um usuário sem autorização para administrar municípios
Quando eu tento incluir o município de código IBGE "3550308", nome "São Paulo"
Então a inclusão do município é rejeitada por falta de autorização
Cenário: Não é possível incluir um município com código IBGE já existente
Dado que sou um administrador do cadastro de municípios
E que existe um município com código IBGE "3550308", nome "São Paulo"
Quando eu tento incluir o município de código IBGE "3550308", nome "Santos"
Então a inclusão é rejeitada porque já existe um município com o identificador "3550308"
Cenário: Não é possível incluir um município com nome já existente na mesma unidade federativa
Dado que sou um administrador do cadastro de municípios
E que existe um município com código IBGE "3550308", nome "São Paulo"
Quando eu tento incluir o município de código IBGE "3548500", nome "São Paulo"
Então a inclusão é rejeitada porque já existe um município com o nome "São Paulo" na unidade federativa
Cenário: Não é possível incluir um município com nome já existente na mesma unidade federativa, ainda que com outras maiúsculas e minúsculas
Dado que sou um administrador do cadastro de municípios
E que existe um município com código IBGE "3550308", nome "São Paulo"
Quando eu tento incluir o município de código IBGE "3548500", nome "SÃO PAULO"
Então a inclusão é rejeitada porque já existe um município com o nome "SÃO PAULO" na unidade federativa
Cenário: Não é possível incluir um município em uma unidade federativa inexistente
Dado que sou um administrador do cadastro de municípios
Quando eu tento incluir o município de código IBGE "3304557", nome "Rio de Janeiro"
Então a inclusão é rejeitada porque não existe unidade federativa com o identificador "33"
Cenário: Não é possível incluir um município já extinto
Dado que sou um administrador do cadastro de municípios
Quando eu tento incluir o município extinto de código IBGE "3550308" e nome "São Paulo"
Então a inclusão é rejeitada porque o município só pode ser extinto pela ação de extinção
Cenário: Não é possível incluir um município já extinto, ainda que com código IBGE já existente
Dado que sou um administrador do cadastro de municípios
E que existe um município com código IBGE "3550308", nome "São Paulo"
Quando eu tento incluir o município extinto de código IBGE "3550308" e nome "São Paulo"
Então a inclusão é rejeitada porque o município só pode ser extinto pela ação de extinção
Cenário: Buscar um município existente
Dado que existe um município com código IBGE "3550308", nome "São Paulo"
E que sou um usuário sem autorização para administrar municípios
Quando eu busco o município de código IBGE "3550308"
Então o município encontrado tem nome "São Paulo"
Cenário: Não é possível buscar um município inexistente
Dado que sou um usuário sem autorização para administrar municípios
Quando eu tento buscar o município de código IBGE "3550308"
Então a busca é rejeitada porque não existe município com o identificador "3550308"
Cenário: Alterar um município
Dado que sou um administrador do cadastro de municípios
E que existe um município com código IBGE "3550308", nome "São Paulo"
Quando eu altero o município de código IBGE "3550308" para nome "Cidade de São Paulo"
Então o município é alterado com sucesso
E o município de código IBGE "3550308" passa a ter nome "Cidade de São Paulo"
Cenário: Alterar um município mantendo seu próprio nome
Dado que sou um administrador do cadastro de municípios
E que existe um município com código IBGE "3550308", nome "São Paulo"
Quando eu altero o município de código IBGE "3550308" para nome "São Paulo"
Então o município é alterado com sucesso
Cenário: Não é possível alterar um município sem autorização de administrador
Dado que sou um usuário sem autorização para administrar municípios
E que existe um município com código IBGE "3550308", nome "São Paulo"
Quando eu tento alterar o município de código IBGE "3550308" para nome "Cidade de São Paulo"
Então a alteração do município é rejeitada por falta de autorização
Cenário: Não é possível alterar um município inexistente
Dado que sou um administrador do cadastro de municípios
Quando eu tento alterar o município de código IBGE "3550308" para nome "São Paulo"
Então a alteração é rejeitada porque não existe município com o identificador "3550308"
Cenário: Não é possível alterar um município extinto
Dado que sou um administrador do cadastro de municípios
E que existe um município extinto com código IBGE "3550308", nome "São Paulo"
Quando eu tento alterar o município de código IBGE "3550308" para nome "Cidade de São Paulo"
Então a alteração é rejeitada porque o município "3550308" está extinto
Cenário: Não é possível alterar um município extinto, ainda que para o nome de outro município
Dado que sou um administrador do cadastro de municípios
E que existe um município extinto com código IBGE "3550308", nome "São Paulo"
E que existe um município com código IBGE "3548500", nome "Santos"
Quando eu tento alterar o município de código IBGE "3550308" para nome "Santos"
Então a alteração é rejeitada porque o município "3550308" está extinto
Cenário: Não é possível alterar um município extinto informando-o como extinto
Dado que sou um administrador do cadastro de municípios
E que existe um município extinto com código IBGE "3550308", nome "São Paulo"
Quando eu tento alterar o município de código IBGE "3550308" para extinto com nome "Cidade de São Paulo"
Então a alteração é rejeitada porque o município só pode ser extinto pela ação de extinção
Cenário: Não é possível extinguir um município por meio da alteração
Dado que sou um administrador do cadastro de municípios
E que existe um município com código IBGE "3550308", nome "São Paulo"
Quando eu tento alterar o município de código IBGE "3550308" para extinto com nome "São Paulo"
Então a alteração é rejeitada porque o município só pode ser extinto pela ação de extinção
Cenário: Excluir um município
Dado que sou um administrador do cadastro de municípios
E que existe um município com código IBGE "3550308", nome "São Paulo"
Quando eu excluo o município de código IBGE "3550308"
Então o município é excluído com sucesso
E o município de código IBGE "3550308" deixa de existir
Cenário: Não é possível excluir um município sem autorização de administrador
Dado que sou um usuário sem autorização para administrar municípios
E que existe um município com código IBGE "3550308", nome "São Paulo"
Quando eu tento excluir o município de código IBGE "3550308"
Então a exclusão do município é rejeitada por falta de autorização
Cenário: Não é possível excluir um município inexistente
Dado que sou um administrador do cadastro de municípios
Quando eu tento excluir o município de código IBGE "3550308"
Então a exclusão é rejeitada porque não existe município com o identificador "3550308"
Cenário: Extinguir um município
Dado que sou um administrador do cadastro de municípios
E que existe um município com código IBGE "3550308", nome "São Paulo"
Quando eu extingo o município de código IBGE "3550308"
Então o município é extinguido com sucesso
E o município de código IBGE "3550308" passa a estar extinto
Cenário: Não é possível extinguir um município sem autorização de administrador
Dado que sou um usuário sem autorização para administrar municípios
E que existe um município com código IBGE "3550308", nome "São Paulo"
Quando eu tento extinguir o município de código IBGE "3550308"
Então a extinção do município é rejeitada por falta de autorização
Cenário: Não é possível extinguir um município inexistente
Dado que sou um administrador do cadastro de municípios
Quando eu tento extinguir o município de código IBGE "3550308"
Então a extinção é rejeitada porque não existe município com o identificador "3550308"
Cenário: Não é possível extinguir um município já extinto
Dado que sou um administrador do cadastro de municípios
E que existe um município extinto com código IBGE "3550308", nome "São Paulo"
Quando eu tento extinguir o município de código IBGE "3550308"
Então a extinção é rejeitada porque o município "3550308" está extinto
As step definitions dublam, além da segurança e do repositório de municípios, o repositório de unidades federativas, que simula a existência das unidades federativas através de um conjunto de identificadores. A suíte do Cucumber do subdomínio, criada na lição 1, já seleciona os novos cenários.
Os passos, porém, são compartilhados por todos os cenários do subdomínio: a suíte procura as step definitions em todas as classes do pacote com.example.exemplo.dominio.geografia e de seus subpacotes, e duas expressões iguais, ainda que em classes diferentes, interrompem os cenários que as usam com uma DuplicateStepDefinitionException. Por isso, os passos do município têm textos próprios — por exemplo, que sou um administrador do cadastro de municípios, e não que sou um usuário administrador, já definido nas step definitions da unidade federativa.
package com.example.exemplo.dominio.geografia.municipio;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import com.example.exemplo.dominio.Autorizacao;
import com.example.exemplo.dominio.geografia.municipio.spi.IMunicipioRepositorio;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaCodigoIbge;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaServico;
import com.example.exemplo.dominio.geografia.unidadefederativa.spi.IUnidadeFederativaRepositorio;
import dev.sauloaraujo.d5.framework.dominio.AgregadoExistenteExcecao;
import dev.sauloaraujo.d5.framework.dominio.AgregadoInexistenteExcecao;
import dev.sauloaraujo.d5.framework.dominio.spi.ISegurancaServico;
import dev.sauloaraujo.d5.framework.dominio.spi.NaoAutorizadoExcecao;
import io.cucumber.java.pt.Dado;
import io.cucumber.java.pt.Entao;
import io.cucumber.java.pt.Quando;
public final class MunicipioStepDefinitions {
private final Map<String, Municipio> municipios = new HashMap<>();
private final Set<String> unidadesFederativas = new HashSet<>();
private final ISegurancaServico segurancaServico = mock(ISegurancaServico.class);
private final IMunicipioRepositorio repositorio = mock(IMunicipioRepositorio.class);
private final IUnidadeFederativaRepositorio unidadeFederativaRepositorio = mock(
IUnidadeFederativaRepositorio.class);
private final MunicipioServico servico = new MunicipioServico(segurancaServico, repositorio,
unidadeFederativaRepositorio);
private Municipio resultadoInclusao;
private Municipio resultadoBusca;
private RuntimeException excecaoCapturada;
public MunicipioStepDefinitions() {
when(unidadeFederativaRepositorio.existePorIdentificador(any()))
.thenAnswer(chamada -> unidadesFederativas.contains(chamada.getArgument(0).toString()));
when(repositorio.existePorIdentificador(any()))
.thenAnswer(chamada -> municipios.containsKey(chave(chamada.getArgument(0))));
when(repositorio.buscarPorIdentificador(any())).thenAnswer(chamada -> {
MunicipioCodigoIbge codigoIbge = chamada.getArgument(0);
var municipio = municipios.get(chave(codigoIbge));
if (municipio == null) {
throw new AgregadoInexistenteExcecao(
String.format(MunicipioServico.IDENTIFICADOR_INEXISTENTE_MODELO, codigoIbge));
}
return municipio;
});
when(repositorio.existePorNome(any(), any(), any())).thenAnswer(chamada -> {
String nome = chamada.getArgument(0);
UnidadeFederativaCodigoIbge unidadeFederativa = chamada.getArgument(1);
Optional<MunicipioCodigoIbge> identificadorIgnorado = chamada.getArgument(2);
return municipios.values().stream()
.anyMatch(candidato -> candidato.getNome().equalsIgnoreCase(nome)
&& candidato.getUnidadeFederativa().equals(unidadeFederativa)
&& !foiIgnorado(candidato, identificadorIgnorado));
});
when(repositorio.incluir(any())).thenAnswer(chamada -> {
Municipio municipio = chamada.getArgument(0);
municipios.put(chave(municipio.getIdentificador().orElseThrow()), municipio);
return municipio;
});
doAnswer(chamada -> {
Municipio municipio = chamada.getArgument(0);
municipios.put(chave(municipio.getIdentificador().orElseThrow()), municipio);
return null;
}).when(repositorio).alterar(any());
doAnswer(chamada -> {
MunicipioCodigoIbge codigoIbge = chamada.getArgument(0);
municipios.remove(chave(codigoIbge));
return null;
}).when(repositorio).excluir(any());
}
private static String chave(MunicipioCodigoIbge codigoIbge) {
return codigoIbge.toString();
}
private static boolean foiIgnorado(Municipio candidato, Optional<MunicipioCodigoIbge> identificadorIgnorado) {
return identificadorIgnorado.isPresent() && candidato.getIdentificador().equals(identificadorIgnorado);
}
private static Municipio criar(String codigoIbge, String nome) {
return new Municipio(new MunicipioCodigoIbge(codigoIbge), nome, Optional.empty());
}
@Dado("que não existe nenhum município cadastrado")
public void que_nao_existe_nenhum_municipio_cadastrado() {
municipios.clear();
unidadesFederativas.clear();
}
@Dado("que existe a unidade federativa de código IBGE {string}")
public void que_existe_a_unidade_federativa(String codigoIbge) {
unidadesFederativas.add(codigoIbge);
}
@Dado("que sou um administrador do cadastro de municípios")
public void que_sou_um_administrador_do_cadastro_de_municipios() {
when(segurancaServico.possuiAutorizacao(Autorizacao.ADMINISTRADOR.name())).thenReturn(true);
}
@Dado("que sou um usuário sem autorização para administrar municípios")
public void que_sou_um_usuario_sem_autorizacao_para_administrar_municipios() {
when(segurancaServico.possuiAutorizacao(Autorizacao.ADMINISTRADOR.name())).thenReturn(false);
}
@Dado("que existe um município com código IBGE {string}, nome {string}")
public void que_existe_um_municipio(String codigoIbge, String nome) {
var municipio = criar(codigoIbge, nome);
municipios.put(chave(municipio.getIdentificador().orElseThrow()), municipio);
}
@Dado("que existe um município extinto com código IBGE {string}, nome {string}")
public void que_existe_um_municipio_extinto(String codigoIbge, String nome) {
var municipio = criar(codigoIbge, nome).extinguir();
municipios.put(chave(municipio.getIdentificador().orElseThrow()), municipio);
}
@Quando("eu incluo o município de código IBGE {string}, nome {string}")
public void eu_incluo_o_municipio(String codigoIbge, String nome) {
incluir(codigoIbge, nome);
}
@Quando("eu tento incluir o município de código IBGE {string}, nome {string}")
public void eu_tento_incluir_o_municipio(String codigoIbge, String nome) {
incluir(codigoIbge, nome);
}
@Quando("eu tento incluir o município extinto de código IBGE {string} e nome {string}")
public void eu_tento_incluir_o_municipio_extinto(String codigoIbge, String nome) {
incluir(criar(codigoIbge, nome).extinguir());
}
private void incluir(String codigoIbge, String nome) {
incluir(criar(codigoIbge, nome));
}
private void incluir(Municipio municipio) {
try {
resultadoInclusao = servico.incluir(municipio);
} catch (RuntimeException excecao) {
excecaoCapturada = excecao;
}
}
@Quando("eu busco o município de código IBGE {string}")
public void eu_busco_o_municipio(String codigoIbge) {
buscar(codigoIbge);
}
@Quando("eu tento buscar o município de código IBGE {string}")
public void eu_tento_buscar_o_municipio(String codigoIbge) {
buscar(codigoIbge);
}
private void buscar(String codigoIbge) {
try {
resultadoBusca = servico.buscar(new MunicipioCodigoIbge(codigoIbge));
} catch (RuntimeException excecao) {
excecaoCapturada = excecao;
}
}
@Quando("eu altero o município de código IBGE {string} para nome {string}")
public void eu_altero_o_municipio(String codigoIbge, String nome) {
alterar(codigoIbge, nome);
}
@Quando("eu tento alterar o município de código IBGE {string} para nome {string}")
public void eu_tento_alterar_o_municipio(String codigoIbge, String nome) {
alterar(codigoIbge, nome);
}
@Quando("eu tento alterar o município de código IBGE {string} para extinto com nome {string}")
public void eu_tento_alterar_o_municipio_para_extinto(String codigoIbge, String nome) {
alterar(criar(codigoIbge, nome).extinguir());
}
private void alterar(String codigoIbge, String nome) {
alterar(criar(codigoIbge, nome));
}
private void alterar(Municipio municipio) {
try {
servico.alterar(municipio);
} catch (RuntimeException excecao) {
excecaoCapturada = excecao;
}
}
@Quando("eu excluo o município de código IBGE {string}")
public void eu_excluo_o_municipio(String codigoIbge) {
excluir(codigoIbge);
}
@Quando("eu tento excluir o município de código IBGE {string}")
public void eu_tento_excluir_o_municipio(String codigoIbge) {
excluir(codigoIbge);
}
private void excluir(String codigoIbge) {
try {
servico.excluir(new MunicipioCodigoIbge(codigoIbge));
} catch (RuntimeException excecao) {
excecaoCapturada = excecao;
}
}
@Quando("eu extingo o município de código IBGE {string}")
public void eu_extingo_o_municipio(String codigoIbge) {
extinguir(codigoIbge);
}
@Quando("eu tento extinguir o município de código IBGE {string}")
public void eu_tento_extinguir_o_municipio(String codigoIbge) {
extinguir(codigoIbge);
}
private void extinguir(String codigoIbge) {
try {
servico.extinguir(new MunicipioCodigoIbge(codigoIbge));
} catch (RuntimeException excecao) {
excecaoCapturada = excecao;
}
}
@Entao("o município é incluído com sucesso")
public void o_municipio_e_incluido_com_sucesso() {
assertNull(excecaoCapturada);
assertNotNull(resultadoInclusao);
}
@Entao("o município de código IBGE {string} passa a existir")
public void o_municipio_passa_a_existir(String codigoIbge) {
assertTrue(municipios.containsKey(codigoIbge));
}
@Entao("a inclusão do município é rejeitada por falta de autorização")
public void a_inclusao_e_rejeitada_por_falta_de_autorizacao() {
assertInstanceOf(NaoAutorizadoExcecao.class, excecaoCapturada);
}
@Entao("a inclusão é rejeitada porque já existe um município com o identificador {string}")
public void a_inclusao_e_rejeitada_pelo_identificador(String codigoIbge) {
assertMensagemDeExistencia(MunicipioServico.IDENTIFICADOR_EXISTENTE_MODELO, codigoIbge);
}
@Entao("a inclusão é rejeitada porque já existe um município com o nome {string} na unidade federativa")
public void a_inclusao_e_rejeitada_pelo_nome(String nome) {
assertMensagemDeExistencia(MunicipioServico.NOME_EXISTENTE_MODELO, nome);
}
@Entao("a inclusão é rejeitada porque não existe unidade federativa com o identificador {string}")
public void a_inclusao_e_rejeitada_por_unidade_federativa_inexistente(String codigoIbge) {
assertInstanceOf(AgregadoInexistenteExcecao.class, excecaoCapturada);
assertEquals(String.format(UnidadeFederativaServico.IDENTIFICADOR_INEXISTENTE_MODELO, codigoIbge),
excecaoCapturada.getMessage());
}
@Entao("o município encontrado tem nome {string}")
public void o_municipio_encontrado_tem(String nome) {
assertEquals(nome, resultadoBusca.getNome());
}
@Entao("a busca é rejeitada porque não existe município com o identificador {string}")
public void a_busca_e_rejeitada_por_inexistencia(String codigoIbge) {
assertMensagemDeInexistencia(codigoIbge);
}
@Entao("o município é alterado com sucesso")
public void o_municipio_e_alterado_com_sucesso() {
assertNull(excecaoCapturada);
}
@Entao("o município de código IBGE {string} passa a ter nome {string}")
public void o_municipio_passa_a_ter(String codigoIbge, String nome) {
var municipio = municipios.get(codigoIbge);
assertNotNull(municipio);
assertEquals(nome, municipio.getNome());
}
@Entao("a alteração do município é rejeitada por falta de autorização")
public void a_alteracao_e_rejeitada_por_falta_de_autorizacao() {
assertInstanceOf(NaoAutorizadoExcecao.class, excecaoCapturada);
}
@Entao("a alteração é rejeitada porque não existe município com o identificador {string}")
public void a_alteracao_e_rejeitada_por_inexistencia(String codigoIbge) {
assertMensagemDeInexistencia(codigoIbge);
}
@Entao("a inclusão é rejeitada porque o município só pode ser extinto pela ação de extinção")
public void a_inclusao_e_rejeitada_por_extincao_indevida() {
assertMensagemDeExtincaoIndevida();
}
@Entao("a alteração é rejeitada porque o município só pode ser extinto pela ação de extinção")
public void a_alteracao_e_rejeitada_por_extincao_indevida() {
assertMensagemDeExtincaoIndevida();
}
@Entao("a alteração é rejeitada porque o município {string} está extinto")
public void a_alteracao_e_rejeitada_por_extincao(String codigoIbge) {
assertMensagemDeExtincao(codigoIbge);
}
@Entao("o município é excluído com sucesso")
public void o_municipio_e_excluido_com_sucesso() {
assertNull(excecaoCapturada);
}
@Entao("o município de código IBGE {string} deixa de existir")
public void o_municipio_deixa_de_existir(String codigoIbge) {
assertFalse(municipios.containsKey(codigoIbge));
}
@Entao("a exclusão do município é rejeitada por falta de autorização")
public void a_exclusao_e_rejeitada_por_falta_de_autorizacao() {
assertInstanceOf(NaoAutorizadoExcecao.class, excecaoCapturada);
}
@Entao("a exclusão é rejeitada porque não existe município com o identificador {string}")
public void a_exclusao_e_rejeitada_por_inexistencia(String codigoIbge) {
assertMensagemDeInexistencia(codigoIbge);
}
@Entao("o município é extinguido com sucesso")
public void o_municipio_e_extinguido_com_sucesso() {
assertNull(excecaoCapturada);
}
@Entao("o município de código IBGE {string} passa a estar extinto")
public void o_municipio_passa_a_estar_extinto(String codigoIbge) {
var municipio = municipios.get(codigoIbge);
assertNotNull(municipio);
assertTrue(municipio.isExtinto());
}
@Entao("a extinção do município é rejeitada por falta de autorização")
public void a_extincao_e_rejeitada_por_falta_de_autorizacao() {
assertInstanceOf(NaoAutorizadoExcecao.class, excecaoCapturada);
}
@Entao("a extinção é rejeitada porque não existe município com o identificador {string}")
public void a_extincao_e_rejeitada_por_inexistencia(String codigoIbge) {
assertMensagemDeInexistencia(codigoIbge);
}
@Entao("a extinção é rejeitada porque o município {string} está extinto")
public void a_extincao_e_rejeitada_por_extincao(String codigoIbge) {
assertMensagemDeExtincao(codigoIbge);
}
private void assertMensagemDeExistencia(String modelo, Object argumento) {
assertInstanceOf(AgregadoExistenteExcecao.class, excecaoCapturada);
assertEquals(String.format(modelo, argumento), excecaoCapturada.getMessage());
}
private void assertMensagemDeInexistencia(Object argumento) {
assertInstanceOf(AgregadoInexistenteExcecao.class, excecaoCapturada);
assertEquals(String.format(MunicipioServico.IDENTIFICADOR_INEXISTENTE_MODELO, argumento),
excecaoCapturada.getMessage());
}
private void assertMensagemDeExtincaoIndevida() {
assertInstanceOf(IllegalArgumentException.class, excecaoCapturada);
assertEquals(MunicipioServico.EXTINCAO_INDEVIDA, excecaoCapturada.getMessage());
}
private void assertMensagemDeExtincao(Object argumento) {
assertInstanceOf(IllegalStateException.class, excecaoCapturada);
assertEquals(String.format(Municipio.EXTINTO_MODELO, argumento), excecaoCapturada.getMessage());
}
}
Unidade federativa referenciada
Agora que municípios referenciam unidades federativas, excluir uma unidade federativa com municípios deixaria esses municípios órfãos. Para impedir isso, o repositório de unidade federativa passa a expor existeReferencia, que informa se algum outro agregado referencia a unidade federativa. Quais agregados são esses é um detalhe da implementação do repositório: o serviço de unidade federativa não precisa conhecer o município, e continuará sem conhecer outros agregados que venham a referenciá-la.
Acrescente existeReferencia à interface do repositório:
package com.example.exemplo.dominio.geografia.unidadefederativa.spi;
import java.util.Optional;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativa;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaCodigoIbge;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaSigla;
import dev.sauloaraujo.d5.framework.dominio.spi.IAgregadoRepositorio;
public interface IUnidadeFederativaRepositorio
extends IAgregadoRepositorio<UnidadeFederativaCodigoIbge, UnidadeFederativa> {
boolean existePorSigla(UnidadeFederativaSigla sigla, Optional<UnidadeFederativaCodigoIbge> identificadorIgnorado);
boolean existePorNome(String nome, Optional<UnidadeFederativaCodigoIbge> identificadorIgnorado);
boolean existeReferencia(UnidadeFederativaCodigoIbge identificador);
}
Em UnidadeFederativaServico, sobrescreva validarExclusao para usar existeReferencia e, validando o estado da aplicação, impedir a exclusão de uma unidade federativa referenciada:
package com.example.exemplo.dominio.geografia.unidadefederativa;
import java.util.Objects;
import java.util.Optional;
import org.apache.commons.lang3.Validate;
import com.example.exemplo.dominio.Autorizacao;
import com.example.exemplo.dominio.geografia.unidadefederativa.spi.IUnidadeFederativaRepositorio;
import dev.sauloaraujo.d5.framework.dominio.impl.AgregadoAutorizacoes;
import dev.sauloaraujo.d5.framework.dominio.impl.AgregadoServicoAbstrato;
import dev.sauloaraujo.d5.framework.dominio.spi.ISegurancaServico;
public final class UnidadeFederativaServico extends
AgregadoServicoAbstrato<UnidadeFederativaCodigoIbge, UnidadeFederativa, AgregadoAutorizacoes<UnidadeFederativa>> {
public static final String IDENTIFICADOR_INEXISTENTE_MODELO = "Não existe unidade federativa com o identificador %s";
public static final String IDENTIFICADOR_EXISTENTE_MODELO = "Já existe uma unidade federativa com o identificador %s";
public static final String SIGLA_EXISTENTE_MODELO = "Já existe uma unidade federativa com a sigla %s";
public static final String NOME_EXISTENTE_MODELO = "Já existe uma unidade federativa com o nome %s";
public static final String REFERENCIADA_MODELO = "A unidade federativa %s é referenciada e não pode ser excluída";
private final IUnidadeFederativaRepositorio repositorio;
public UnidadeFederativaServico(ISegurancaServico segurancaServico, IUnidadeFederativaRepositorio repositorio) {
super(segurancaServico, repositorio, IDENTIFICADOR_EXISTENTE_MODELO, IDENTIFICADOR_INEXISTENTE_MODELO);
this.repositorio = Objects.requireNonNull(repositorio);
}
@Override
public AgregadoAutorizacoes<UnidadeFederativa> criarAutorizacoes(
Optional<UnidadeFederativaCodigoIbge> identificador) {
Objects.requireNonNull(identificador);
var administrador = getSegurancaServico().possuiAutorizacao(Autorizacao.ADMINISTRADOR.name());
return new AgregadoAutorizacoes<>(administrador, true, administrador, administrador);
}
@Override
protected void validarInclusaoAlteracao(UnidadeFederativa agregado) {
Objects.requireNonNull(agregado);
var identificador = agregado.getIdentificador();
validarInexistencia(sigla -> repositorio.existePorSigla(sigla, identificador), agregado.getSigla(),
SIGLA_EXISTENTE_MODELO);
validarInexistencia(nome -> repositorio.existePorNome(nome, identificador), agregado.getNome(),
NOME_EXISTENTE_MODELO);
}
@Override
protected void validarExclusao(UnidadeFederativaCodigoIbge identificador) {
Objects.requireNonNull(identificador);
Validate.validState(!repositorio.existeReferencia(identificador), REFERENCIADA_MODELO, identificador);
}
}
Acrescente aos cenários da unidade federativa a exclusão de uma unidade federativa referenciada e, às step definitions, a simulação das unidades federativas referenciadas, através de um conjunto de identificadores, e o passo que verifica que a unidade federativa continua a existir depois da exclusão rejeitada:
# language: pt
Funcionalidade: Gerenciar unidades federativas
Para manter o cadastro de unidades federativas da aplicação
Como usuário do sistema
Quero incluir, buscar, alterar e excluir unidades federativas,
respeitando as regras de unicidade de sigla e nome e de autorização
Contexto:
Dado que não existe nenhuma unidade federativa cadastrada
Cenário: Incluir uma unidade federativa
Dado que sou um usuário administrador
Quando eu incluo a unidade federativa de código IBGE "35", sigla "SP" e nome "São Paulo"
Então a unidade federativa é incluída com sucesso
E a unidade federativa de código IBGE "35" passa a existir
Cenário: Não é possível incluir uma unidade federativa sem autorização de administrador
Dado que sou um usuário sem autorização de administrador
Quando eu tento incluir a unidade federativa de código IBGE "35", sigla "SP" e nome "São Paulo"
Então a inclusão é rejeitada por falta de autorização
Cenário: Não é possível incluir uma unidade federativa com código IBGE já existente
Dado que sou um usuário administrador
E que existe uma unidade federativa com código IBGE "35", sigla "SP" e nome "São Paulo"
Quando eu tento incluir a unidade federativa de código IBGE "35", sigla "RJ" e nome "Rio de Janeiro"
Então a inclusão é rejeitada porque já existe uma unidade federativa com o identificador "35"
Cenário: Não é possível incluir uma unidade federativa com sigla já existente
Dado que sou um usuário administrador
E que existe uma unidade federativa com código IBGE "35", sigla "SP" e nome "São Paulo"
Quando eu tento incluir a unidade federativa de código IBGE "33", sigla "SP" e nome "Rio de Janeiro"
Então a inclusão é rejeitada porque já existe uma unidade federativa com a sigla "SP"
Cenário: Não é possível incluir uma unidade federativa com nome já existente
Dado que sou um usuário administrador
E que existe uma unidade federativa com código IBGE "35", sigla "SP" e nome "São Paulo"
Quando eu tento incluir a unidade federativa de código IBGE "33", sigla "RJ" e nome "São Paulo"
Então a inclusão é rejeitada porque já existe uma unidade federativa com o nome "São Paulo"
Cenário: Não é possível incluir uma unidade federativa com nome já existente, ainda que com outras maiúsculas e minúsculas
Dado que sou um usuário administrador
E que existe uma unidade federativa com código IBGE "35", sigla "SP" e nome "São Paulo"
Quando eu tento incluir a unidade federativa de código IBGE "33", sigla "RJ" e nome "SÃO PAULO"
Então a inclusão é rejeitada porque já existe uma unidade federativa com o nome "SÃO PAULO"
Cenário: Buscar uma unidade federativa existente
Dado que existe uma unidade federativa com código IBGE "35", sigla "SP" e nome "São Paulo"
E que sou um usuário sem autorização de administrador
Quando eu busco a unidade federativa de código IBGE "35"
Então a unidade federativa encontrada tem sigla "SP" e nome "São Paulo"
Cenário: Não é possível buscar uma unidade federativa inexistente
Dado que sou um usuário sem autorização de administrador
Quando eu tento buscar a unidade federativa de código IBGE "35"
Então a busca é rejeitada porque não existe unidade federativa com o identificador "35"
Cenário: Alterar uma unidade federativa
Dado que sou um usuário administrador
E que existe uma unidade federativa com código IBGE "35", sigla "SP" e nome "São Paulo"
Quando eu altero a unidade federativa de código IBGE "35" para sigla "SP" e nome "Estado de São Paulo"
Então a unidade federativa é alterada com sucesso
E a unidade federativa de código IBGE "35" passa a ter sigla "SP" e nome "Estado de São Paulo"
Cenário: Alterar uma unidade federativa mantendo sua própria sigla e nome
Dado que sou um usuário administrador
E que existe uma unidade federativa com código IBGE "35", sigla "SP" e nome "São Paulo"
Quando eu altero a unidade federativa de código IBGE "35" para sigla "SP" e nome "São Paulo"
Então a unidade federativa é alterada com sucesso
Cenário: Não é possível alterar uma unidade federativa sem autorização de administrador
Dado que sou um usuário sem autorização de administrador
E que existe uma unidade federativa com código IBGE "35", sigla "SP" e nome "São Paulo"
Quando eu tento alterar a unidade federativa de código IBGE "35" para sigla "SP" e nome "Estado de São Paulo"
Então a alteração é rejeitada por falta de autorização
Cenário: Não é possível alterar uma unidade federativa inexistente
Dado que sou um usuário administrador
Quando eu tento alterar a unidade federativa de código IBGE "35" para sigla "SP" e nome "São Paulo"
Então a alteração é rejeitada porque não existe unidade federativa com o identificador "35"
Cenário: Não é possível alterar uma unidade federativa para a sigla de outra
Dado que sou um usuário administrador
E que existe uma unidade federativa com código IBGE "35", sigla "SP" e nome "São Paulo"
E que existe uma unidade federativa com código IBGE "33", sigla "RJ" e nome "Rio de Janeiro"
Quando eu tento alterar a unidade federativa de código IBGE "33" para sigla "SP" e nome "Rio de Janeiro"
Então a alteração é rejeitada porque já existe uma unidade federativa com a sigla "SP"
Cenário: Excluir uma unidade federativa
Dado que sou um usuário administrador
E que existe uma unidade federativa com código IBGE "35", sigla "SP" e nome "São Paulo"
Quando eu excluo a unidade federativa de código IBGE "35"
Então a unidade federativa é excluída com sucesso
E a unidade federativa de código IBGE "35" deixa de existir
Cenário: Não é possível excluir uma unidade federativa sem autorização de administrador
Dado que sou um usuário sem autorização de administrador
E que existe uma unidade federativa com código IBGE "35", sigla "SP" e nome "São Paulo"
Quando eu tento excluir a unidade federativa de código IBGE "35"
Então a exclusão é rejeitada por falta de autorização
Cenário: Não é possível excluir uma unidade federativa inexistente
Dado que sou um usuário administrador
Quando eu tento excluir a unidade federativa de código IBGE "35"
Então a exclusão é rejeitada porque não existe unidade federativa com o identificador "35"
Cenário: Não é possível excluir uma unidade federativa referenciada
Dado que sou um usuário administrador
E que existe uma unidade federativa com código IBGE "35", sigla "SP" e nome "São Paulo"
E que a unidade federativa de código IBGE "35" é referenciada
Quando eu tento excluir a unidade federativa de código IBGE "35"
Então a exclusão é rejeitada porque a unidade federativa "35" é referenciada
E a unidade federativa de código IBGE "35" continua a existir
package com.example.exemplo.dominio.geografia.unidadefederativa;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import com.example.exemplo.dominio.Autorizacao;
import com.example.exemplo.dominio.geografia.unidadefederativa.spi.IUnidadeFederativaRepositorio;
import dev.sauloaraujo.d5.framework.dominio.AgregadoExistenteExcecao;
import dev.sauloaraujo.d5.framework.dominio.AgregadoInexistenteExcecao;
import dev.sauloaraujo.d5.framework.dominio.spi.ISegurancaServico;
import dev.sauloaraujo.d5.framework.dominio.spi.NaoAutorizadoExcecao;
import io.cucumber.java.pt.Dado;
import io.cucumber.java.pt.Entao;
import io.cucumber.java.pt.Quando;
public final class UnidadeFederativaStepDefinitions {
private final Map<String, UnidadeFederativa> unidadesFederativas = new HashMap<>();
private final Set<String> referenciadas = new HashSet<>();
private final ISegurancaServico segurancaServico = mock(ISegurancaServico.class);
private final IUnidadeFederativaRepositorio repositorio = mock(IUnidadeFederativaRepositorio.class);
private final UnidadeFederativaServico servico = new UnidadeFederativaServico(segurancaServico, repositorio);
private UnidadeFederativa resultadoInclusao;
private UnidadeFederativa resultadoBusca;
private RuntimeException excecaoCapturada;
public UnidadeFederativaStepDefinitions() {
when(repositorio.existePorIdentificador(any()))
.thenAnswer(chamada -> unidadesFederativas.containsKey(chave(chamada.getArgument(0))));
when(repositorio.buscarPorIdentificador(any())).thenAnswer(chamada -> {
UnidadeFederativaCodigoIbge codigoIbge = chamada.getArgument(0);
var unidadeFederativa = unidadesFederativas.get(chave(codigoIbge));
if (unidadeFederativa == null) {
throw new AgregadoInexistenteExcecao(
String.format(UnidadeFederativaServico.IDENTIFICADOR_INEXISTENTE_MODELO, codigoIbge));
}
return unidadeFederativa;
});
when(repositorio.existePorSigla(any(), any())).thenAnswer(chamada -> {
UnidadeFederativaSigla sigla = chamada.getArgument(0);
Optional<UnidadeFederativaCodigoIbge> identificadorIgnorado = chamada.getArgument(1);
return unidadesFederativas.values().stream().anyMatch(
candidata -> candidata.getSigla().equals(sigla) && !foiIgnorada(candidata, identificadorIgnorado));
});
when(repositorio.existePorNome(any(), any())).thenAnswer(chamada -> {
String nome = chamada.getArgument(0);
Optional<UnidadeFederativaCodigoIbge> identificadorIgnorado = chamada.getArgument(1);
return unidadesFederativas.values().stream()
.anyMatch(candidata -> candidata.getNome().equalsIgnoreCase(nome)
&& !foiIgnorada(candidata, identificadorIgnorado));
});
when(repositorio.existeReferencia(any()))
.thenAnswer(chamada -> referenciadas.contains(chave(chamada.getArgument(0))));
when(repositorio.incluir(any())).thenAnswer(chamada -> {
UnidadeFederativa unidadeFederativa = chamada.getArgument(0);
unidadesFederativas.put(chave(unidadeFederativa.getIdentificador().orElseThrow()), unidadeFederativa);
return unidadeFederativa;
});
doAnswer(chamada -> {
UnidadeFederativa unidadeFederativa = chamada.getArgument(0);
unidadesFederativas.put(chave(unidadeFederativa.getIdentificador().orElseThrow()), unidadeFederativa);
return null;
}).when(repositorio).alterar(any());
doAnswer(chamada -> {
UnidadeFederativaCodigoIbge codigoIbge = chamada.getArgument(0);
unidadesFederativas.remove(chave(codigoIbge));
return null;
}).when(repositorio).excluir(any());
}
private static String chave(UnidadeFederativaCodigoIbge codigoIbge) {
return codigoIbge.toString();
}
private static boolean foiIgnorada(UnidadeFederativa candidata,
Optional<UnidadeFederativaCodigoIbge> identificadorIgnorado) {
return identificadorIgnorado.isPresent() && candidata.getIdentificador().equals(identificadorIgnorado);
}
private static UnidadeFederativa criar(String codigoIbge, String sigla, String nome) {
return new UnidadeFederativa(new UnidadeFederativaCodigoIbge(codigoIbge), new UnidadeFederativaSigla(sigla),
nome);
}
@Dado("que não existe nenhuma unidade federativa cadastrada")
public void que_nao_existe_nenhuma_unidade_federativa_cadastrada() {
unidadesFederativas.clear();
referenciadas.clear();
}
@Dado("que sou um usuário administrador")
public void que_sou_um_usuario_administrador() {
when(segurancaServico.possuiAutorizacao(Autorizacao.ADMINISTRADOR.name())).thenReturn(true);
}
@Dado("que sou um usuário sem autorização de administrador")
public void que_sou_um_usuario_sem_autorizacao_de_administrador() {
when(segurancaServico.possuiAutorizacao(Autorizacao.ADMINISTRADOR.name())).thenReturn(false);
}
@Dado("que existe uma unidade federativa com código IBGE {string}, sigla {string} e nome {string}")
public void que_existe_uma_unidade_federativa(String codigoIbge, String sigla, String nome) {
var unidadeFederativa = criar(codigoIbge, sigla, nome);
unidadesFederativas.put(chave(unidadeFederativa.getIdentificador().orElseThrow()), unidadeFederativa);
}
@Dado("que a unidade federativa de código IBGE {string} é referenciada")
public void que_a_unidade_federativa_e_referenciada(String codigoIbge) {
referenciadas.add(codigoIbge);
}
@Quando("eu incluo a unidade federativa de código IBGE {string}, sigla {string} e nome {string}")
public void eu_incluo_a_unidade_federativa(String codigoIbge, String sigla, String nome) {
incluir(codigoIbge, sigla, nome);
}
@Quando("eu tento incluir a unidade federativa de código IBGE {string}, sigla {string} e nome {string}")
public void eu_tento_incluir_a_unidade_federativa(String codigoIbge, String sigla, String nome) {
incluir(codigoIbge, sigla, nome);
}
private void incluir(String codigoIbge, String sigla, String nome) {
try {
resultadoInclusao = servico.incluir(criar(codigoIbge, sigla, nome));
} catch (RuntimeException excecao) {
excecaoCapturada = excecao;
}
}
@Quando("eu busco a unidade federativa de código IBGE {string}")
public void eu_busco_a_unidade_federativa(String codigoIbge) {
buscar(codigoIbge);
}
@Quando("eu tento buscar a unidade federativa de código IBGE {string}")
public void eu_tento_buscar_a_unidade_federativa(String codigoIbge) {
buscar(codigoIbge);
}
private void buscar(String codigoIbge) {
try {
resultadoBusca = servico.buscar(new UnidadeFederativaCodigoIbge(codigoIbge));
} catch (RuntimeException excecao) {
excecaoCapturada = excecao;
}
}
@Quando("eu altero a unidade federativa de código IBGE {string} para sigla {string} e nome {string}")
public void eu_altero_a_unidade_federativa(String codigoIbge, String sigla, String nome) {
alterar(codigoIbge, sigla, nome);
}
@Quando("eu tento alterar a unidade federativa de código IBGE {string} para sigla {string} e nome {string}")
public void eu_tento_alterar_a_unidade_federativa(String codigoIbge, String sigla, String nome) {
alterar(codigoIbge, sigla, nome);
}
private void alterar(String codigoIbge, String sigla, String nome) {
try {
servico.alterar(criar(codigoIbge, sigla, nome));
} catch (RuntimeException excecao) {
excecaoCapturada = excecao;
}
}
@Quando("eu excluo a unidade federativa de código IBGE {string}")
public void eu_excluo_a_unidade_federativa(String codigoIbge) {
excluir(codigoIbge);
}
@Quando("eu tento excluir a unidade federativa de código IBGE {string}")
public void eu_tento_excluir_a_unidade_federativa(String codigoIbge) {
excluir(codigoIbge);
}
private void excluir(String codigoIbge) {
try {
servico.excluir(new UnidadeFederativaCodigoIbge(codigoIbge));
} catch (RuntimeException excecao) {
excecaoCapturada = excecao;
}
}
@Entao("a unidade federativa é incluída com sucesso")
public void a_unidade_federativa_e_incluida_com_sucesso() {
assertNull(excecaoCapturada);
assertNotNull(resultadoInclusao);
}
@Entao("a unidade federativa de código IBGE {string} passa a existir")
public void a_unidade_federativa_passa_a_existir(String codigoIbge) {
assertTrue(unidadesFederativas.containsKey(codigoIbge));
}
@Entao("a unidade federativa de código IBGE {string} continua a existir")
public void a_unidade_federativa_continua_a_existir(String codigoIbge) {
assertTrue(unidadesFederativas.containsKey(codigoIbge));
}
@Entao("a inclusão é rejeitada por falta de autorização")
public void a_inclusao_e_rejeitada_por_falta_de_autorizacao() {
assertInstanceOf(NaoAutorizadoExcecao.class, excecaoCapturada);
}
@Entao("a inclusão é rejeitada porque já existe uma unidade federativa com o identificador {string}")
public void a_inclusao_e_rejeitada_pelo_identificador(String codigoIbge) {
assertMensagemDeExistencia(UnidadeFederativaServico.IDENTIFICADOR_EXISTENTE_MODELO, codigoIbge);
}
@Entao("a inclusão é rejeitada porque já existe uma unidade federativa com a sigla {string}")
public void a_inclusao_e_rejeitada_pela_sigla(String sigla) {
assertMensagemDeExistencia(UnidadeFederativaServico.SIGLA_EXISTENTE_MODELO, sigla);
}
@Entao("a inclusão é rejeitada porque já existe uma unidade federativa com o nome {string}")
public void a_inclusao_e_rejeitada_pelo_nome(String nome) {
assertMensagemDeExistencia(UnidadeFederativaServico.NOME_EXISTENTE_MODELO, nome);
}
@Entao("a unidade federativa encontrada tem sigla {string} e nome {string}")
public void a_unidade_federativa_encontrada_tem(String sigla, String nome) {
assertEquals(new UnidadeFederativaSigla(sigla), resultadoBusca.getSigla());
assertEquals(nome, resultadoBusca.getNome());
}
@Entao("a busca é rejeitada porque não existe unidade federativa com o identificador {string}")
public void a_busca_e_rejeitada_por_inexistencia(String codigoIbge) {
assertMensagemDeInexistencia(codigoIbge);
}
@Entao("a unidade federativa é alterada com sucesso")
public void a_unidade_federativa_e_alterada_com_sucesso() {
assertNull(excecaoCapturada);
}
@Entao("a unidade federativa de código IBGE {string} passa a ter sigla {string} e nome {string}")
public void a_unidade_federativa_passa_a_ter(String codigoIbge, String sigla, String nome) {
var unidadeFederativa = unidadesFederativas.get(codigoIbge);
assertNotNull(unidadeFederativa);
assertEquals(new UnidadeFederativaSigla(sigla), unidadeFederativa.getSigla());
assertEquals(nome, unidadeFederativa.getNome());
}
@Entao("a alteração é rejeitada por falta de autorização")
public void a_alteracao_e_rejeitada_por_falta_de_autorizacao() {
assertInstanceOf(NaoAutorizadoExcecao.class, excecaoCapturada);
}
@Entao("a alteração é rejeitada porque não existe unidade federativa com o identificador {string}")
public void a_alteracao_e_rejeitada_por_inexistencia(String codigoIbge) {
assertMensagemDeInexistencia(codigoIbge);
}
@Entao("a alteração é rejeitada porque já existe uma unidade federativa com a sigla {string}")
public void a_alteracao_e_rejeitada_pela_sigla(String sigla) {
assertMensagemDeExistencia(UnidadeFederativaServico.SIGLA_EXISTENTE_MODELO, sigla);
}
@Entao("a unidade federativa é excluída com sucesso")
public void a_unidade_federativa_e_excluida_com_sucesso() {
assertNull(excecaoCapturada);
}
@Entao("a unidade federativa de código IBGE {string} deixa de existir")
public void a_unidade_federativa_deixa_de_existir(String codigoIbge) {
assertFalse(unidadesFederativas.containsKey(codigoIbge));
}
@Entao("a exclusão é rejeitada por falta de autorização")
public void a_exclusao_e_rejeitada_por_falta_de_autorizacao() {
assertInstanceOf(NaoAutorizadoExcecao.class, excecaoCapturada);
}
@Entao("a exclusão é rejeitada porque não existe unidade federativa com o identificador {string}")
public void a_exclusao_e_rejeitada_por_inexistencia(String codigoIbge) {
assertMensagemDeInexistencia(codigoIbge);
}
@Entao("a exclusão é rejeitada porque a unidade federativa {string} é referenciada")
public void a_exclusao_e_rejeitada_por_referencia(String codigoIbge) {
assertInstanceOf(IllegalStateException.class, excecaoCapturada);
assertEquals(String.format(UnidadeFederativaServico.REFERENCIADA_MODELO, codigoIbge),
excecaoCapturada.getMessage());
}
private void assertMensagemDeExistencia(String modelo, Object argumento) {
assertInstanceOf(AgregadoExistenteExcecao.class, excecaoCapturada);
assertEquals(String.format(modelo, argumento), excecaoCapturada.getMessage());
}
private void assertMensagemDeInexistencia(Object argumento) {
assertInstanceOf(AgregadoInexistenteExcecao.class, excecaoCapturada);
assertEquals(String.format(UnidadeFederativaServico.IDENTIFICADOR_INEXISTENTE_MODELO, argumento),
excecaoCapturada.getMessage());
}
}
Execute os cenários, como na lição 1:
mvn -pl dominio-geografia -am test
O resultado deve informar 42 testes, os 17 cenários da unidade federativa e os 25 do município, sem falhas.
Aplicação
As classes de aplicação do município ficam no pacote com.example.exemplo.aplicacao.geografia.municipio. Crie o package-info.java desse pacote, anotado com @NullMarked:
@org.jspecify.annotations.NullMarked
package com.example.exemplo.aplicacao.geografia.municipio;
Projeção
Uma lista de municípios costuma exibir o nome da unidade federativa de cada um, e não apenas seu código. Por isso, a projeção traz, além do identificador, do nome, do indicador de extinção e do código da unidade federativa, o nome da unidade federativa — que não pertence ao agregado município, mas é obtido pela consulta que instancia a projeção.
package com.example.exemplo.aplicacao.geografia.municipio;
import java.util.Objects;
import com.example.exemplo.dominio.geografia.municipio.Municipio;
import com.example.exemplo.dominio.geografia.municipio.MunicipioCodigoIbge;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaCodigoIbge;
import dev.sauloaraujo.d5.framework.aplicacao.IProjecao;
public final class MunicipioProjecao implements IProjecao<Municipio> {
private final MunicipioCodigoIbge identificador;
private final String nome;
private final UnidadeFederativaCodigoIbge unidadeFederativa;
private final String unidadeFederativaNome;
private final boolean extinto;
public MunicipioProjecao(MunicipioCodigoIbge identificador, String nome,
UnidadeFederativaCodigoIbge unidadeFederativa, String unidadeFederativaNome, boolean extinto) {
this.identificador = Objects.requireNonNull(identificador);
this.nome = Objects.requireNonNull(nome);
this.unidadeFederativa = Objects.requireNonNull(unidadeFederativa);
this.unidadeFederativaNome = Objects.requireNonNull(unidadeFederativaNome);
this.extinto = extinto;
}
public MunicipioProjecao(String identificador, String nome, String unidadeFederativa, String unidadeFederativaNome,
boolean extinto) {
this(new MunicipioCodigoIbge(identificador), nome, new UnidadeFederativaCodigoIbge(unidadeFederativa),
unidadeFederativaNome, extinto);
}
public MunicipioCodigoIbge getIdentificador() {
return identificador;
}
public String getNome() {
return nome;
}
public UnidadeFederativaCodigoIbge getUnidadeFederativa() {
return unidadeFederativa;
}
public String getUnidadeFederativaNome() {
return unidadeFederativaNome;
}
public boolean isExtinto() {
return extinto;
}
}
Ordenação
Além do identificador e do nome, os municípios podem ser ordenados pelo nome da unidade federativa.
package com.example.exemplo.aplicacao.geografia.municipio;
import dev.sauloaraujo.d5.framework.aplicacao.IOrdenacao;
public enum MunicipioOrdenacao implements IOrdenacao<MunicipioProjecao> {
IDENTIFICADOR, NOME, UNIDADE_FEDERATIVA_NOME
}
Consulta
A consulta filtra os municípios por um trecho do nome e pelo código da unidade federativa.
package com.example.exemplo.aplicacao.geografia.municipio;
import java.util.Objects;
import java.util.Optional;
import dev.sauloaraujo.d5.framework.aplicacao.impl.Consulta;
public final class MunicipioConsulta extends Consulta<MunicipioOrdenacao> {
private final Optional<String> nome;
private final Optional<String> unidadeFederativa;
public MunicipioConsulta(Optional<MunicipioOrdenacao> ordenacao, Optional<Boolean> descendente,
Optional<Integer> indicePagina, Optional<Integer> tamanhoPagina, Optional<String> nome,
Optional<String> unidadeFederativa) {
super(ordenacao, descendente, indicePagina, tamanhoPagina);
this.nome = Objects.requireNonNull(nome);
this.unidadeFederativa = Objects.requireNonNull(unidadeFederativa);
}
public Optional<String> getNome() {
return nome;
}
public Optional<String> getUnidadeFederativa() {
return unidadeFederativa;
}
}
Repositório de aplicação e serviço de aplicação
Como na unidade federativa, crie a interface do repositório de aplicação no pacote spi e o serviço de aplicação, parametrizado agora por MunicipioAutorizacoes:
@org.jspecify.annotations.NullMarked
package com.example.exemplo.aplicacao.geografia.municipio.spi;
package com.example.exemplo.aplicacao.geografia.municipio.spi;
import com.example.exemplo.aplicacao.geografia.municipio.MunicipioConsulta;
import com.example.exemplo.aplicacao.geografia.municipio.MunicipioProjecao;
import dev.sauloaraujo.d5.framework.aplicacao.spi.IAgregadoAplicacaoRepositorio;
public interface IMunicipioAplicacaoRepositorio
extends IAgregadoAplicacaoRepositorio<MunicipioProjecao, MunicipioConsulta> {
}
package com.example.exemplo.aplicacao.geografia.municipio;
import com.example.exemplo.aplicacao.geografia.municipio.spi.IMunicipioAplicacaoRepositorio;
import com.example.exemplo.dominio.geografia.municipio.Municipio;
import com.example.exemplo.dominio.geografia.municipio.MunicipioAutorizacoes;
import com.example.exemplo.dominio.geografia.municipio.MunicipioCodigoIbge;
import com.example.exemplo.dominio.geografia.municipio.MunicipioServico;
import dev.sauloaraujo.d5.framework.aplicacao.impl.AgregadoAplicacaoServicoAbstrato;
public final class MunicipioAplicacaoServico extends
AgregadoAplicacaoServicoAbstrato<MunicipioCodigoIbge, Municipio, MunicipioAutorizacoes, MunicipioProjecao, MunicipioConsulta> {
public MunicipioAplicacaoServico(IMunicipioAplicacaoRepositorio repositorio, MunicipioServico servico) {
super(repositorio, servico);
}
}
Infraestrutura
A persistência de município fica no pacote com.example.exemplo.infraestrutura.geografia.municipio. Crie o package-info.java desse pacote, anotado com @NullMarked:
@org.jspecify.annotations.NullMarked
package com.example.exemplo.infraestrutura.geografia.municipio;
Entidade JPA
Diferente do agregado, que referencia a unidade federativa apenas pelo código, a entidade JPA do município a mapeia por uma associação @ManyToOne com UnidadeFederativaJpa, cuja coluna, UFI_ID, é uma chave estrangeira: o banco de dados rejeita, assim, um município de uma unidade federativa inexistente e a exclusão de uma unidade federativa com municípios, ainda que os serviços já impeçam essas situações. A associação é apenas um detalhe da persistência e é preguiçosa (FetchType.LAZY), para que ler um município não carregue a unidade federativa. Como a unidade federativa é derivada do identificador, a coluna também não pode ser alterada. O atributo extinto, mapeado para a coluna MUN_EX, também é anotado com @NotNull: mesmo sendo de um tipo primitivo, que nunca é null, o Hibernate só declara a coluna not null no banco de dados com essa anotação. A restrição de unicidade sobre a unidade federativa e o nome reforça no banco de dados a unicidade validada pelo serviço, mas, como a do nome da unidade federativa, diferencia maiúsculas de minúsculas.
package com.example.exemplo.infraestrutura.geografia.municipio;
import com.example.exemplo.dominio.geografia.municipio.Municipio;
import com.example.exemplo.dominio.geografia.municipio.MunicipioCodigoIbge;
import com.example.exemplo.infraestrutura.geografia.unidadefederativa.UnidadeFederativaJpa;
import dev.sauloaraujo.d5.framework.infraestrutura.IAgregadoJpa;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
@Entity
@Table(name = "MUN_MUNICIPIO", uniqueConstraints = @UniqueConstraint(columnNames = { "UFI_ID", "MUN_NM" }))
public class MunicipioJpa implements IAgregadoJpa<Municipio> {
@Id
@Column(name = "MUN_ID", updatable = false)
@NotNull
@Size(max = MunicipioCodigoIbge.TAMANHO)
private String identificador;
@Column(name = "MUN_NM")
@NotNull
@Size(max = 100)
private String nome;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "UFI_ID", updatable = false)
@NotNull
private UnidadeFederativaJpa unidadeFederativa;
@Column(name = "MUN_EX")
@NotNull
private boolean extinto;
protected MunicipioJpa() {
}
public MunicipioJpa(String identificador, String nome, UnidadeFederativaJpa unidadeFederativa, boolean extinto) {
this.identificador = identificador;
this.nome = nome;
this.unidadeFederativa = unidadeFederativa;
this.extinto = extinto;
}
public String getIdentificador() {
return identificador;
}
public String getNome() {
return nome;
}
public UnidadeFederativaJpa getUnidadeFederativa() {
return unidadeFederativa;
}
public boolean isExtinto() {
return extinto;
}
}
Repositório Spring Data
existePorNome restringe a verificação à unidade federativa informada e, como na unidade federativa, compara os nomes em minúsculas. Como a unidade federativa é uma associação, a verificação compara o seu identificador — m.unidadeFederativa.identificador. Já a consulta de pesquisar obtém o nome da unidade federativa através de uma junção pela associação — join m.unidadeFederativa u —, e o filtro pela unidade federativa compara u.identificador. A junção é interna: um município sempre pertence a uma unidade federativa existente, garantia dada pelo serviço de município na inclusão e pelo de unidade federativa na exclusão.
package com.example.exemplo.infraestrutura.geografia.municipio;
import org.jspecify.annotations.Nullable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.example.exemplo.aplicacao.geografia.municipio.MunicipioProjecao;
public interface IMunicipioRepositorioJpa extends JpaRepository<MunicipioJpa, String> {
@Query("""
select count(m) > 0
from MunicipioJpa m
where lower(m.nome) = lower(:nome)
and m.unidadeFederativa.identificador = :unidadeFederativa
and (:identificadorIgnorado is null or m.identificador <> :identificadorIgnorado)
""")
boolean existePorNome(String nome, String unidadeFederativa, @Nullable String identificadorIgnorado);
@Query("""
select new com.example.exemplo.aplicacao.geografia.municipio.MunicipioProjecao(m.identificador, m.nome, u.identificador, u.nome, m.extinto)
from MunicipioJpa m
join m.unidadeFederativa u
where (:nome is null or lower(m.nome) like :nome escape '\\')
and (:unidadeFederativa is null or u.identificador = :unidadeFederativa)
""")
Page<MunicipioProjecao> pesquisar(@Nullable String nome, @Nullable String unidadeFederativa, Pageable pageable);
}
Implementação do repositório
Para preencher a associação, paraJpa obtém a unidade federativa, derivada do identificador do município, com getReferenceById de IUnidadeFederativaRepositorioJpa, recebido no construtor, que devolve uma referência sem consultar o banco de dados. paraAgregado não precisa ler a associação, já que a unidade federativa é derivada do identificador, e informa explicitamente o indicador de extinção lido do banco de dados.
package com.example.exemplo.infraestrutura.geografia.municipio;
import java.util.Objects;
import java.util.Optional;
import com.example.exemplo.dominio.geografia.municipio.Municipio;
import com.example.exemplo.dominio.geografia.municipio.MunicipioCodigoIbge;
import com.example.exemplo.dominio.geografia.municipio.MunicipioServico;
import com.example.exemplo.dominio.geografia.municipio.spi.IMunicipioRepositorio;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaCodigoIbge;
import com.example.exemplo.infraestrutura.geografia.unidadefederativa.IUnidadeFederativaRepositorioJpa;
import dev.sauloaraujo.d5.framework.infraestrutura.AgregadoRepositorioAbstrato;
public class MunicipioRepositorio
extends AgregadoRepositorioAbstrato<MunicipioCodigoIbge, Municipio, String, MunicipioJpa>
implements IMunicipioRepositorio {
private final IMunicipioRepositorioJpa repositorioJpa;
private final IUnidadeFederativaRepositorioJpa unidadeFederativaRepositorioJpa;
public MunicipioRepositorio(IMunicipioRepositorioJpa repositorioJpa,
IUnidadeFederativaRepositorioJpa unidadeFederativaRepositorioJpa) {
super(repositorioJpa, MunicipioServico.IDENTIFICADOR_INEXISTENTE_MODELO);
this.repositorioJpa = Objects.requireNonNull(repositorioJpa);
this.unidadeFederativaRepositorioJpa = Objects.requireNonNull(unidadeFederativaRepositorioJpa);
}
@Override
protected MunicipioJpa paraJpa(Municipio agregado) {
Objects.requireNonNull(agregado);
var identificador = agregado.getIdentificador().orElseThrow().toString();
var unidadeFederativa = unidadeFederativaRepositorioJpa
.getReferenceById(agregado.getUnidadeFederativa().toString());
return new MunicipioJpa(identificador, agregado.getNome(), unidadeFederativa, agregado.isExtinto());
}
@Override
protected Municipio paraAgregado(MunicipioJpa agregadoJpa) {
Objects.requireNonNull(agregadoJpa);
var identificador = new MunicipioCodigoIbge(agregadoJpa.getIdentificador());
return new Municipio(identificador, agregadoJpa.getNome(), Optional.of(agregadoJpa.isExtinto()));
}
@Override
protected String paraIdentificadorJpa(MunicipioCodigoIbge identificador) {
Objects.requireNonNull(identificador);
return identificador.toString();
}
@Override
public boolean existePorNome(String nome, UnidadeFederativaCodigoIbge unidadeFederativa,
Optional<MunicipioCodigoIbge> identificadorIgnorado) {
Objects.requireNonNull(nome);
Objects.requireNonNull(unidadeFederativa);
Objects.requireNonNull(identificadorIgnorado);
var identificadorIgnoradoJpa = identificadorIgnorado.map(MunicipioCodigoIbge::toString).orElse(null);
return repositorioJpa.existePorNome(nome, unidadeFederativa.toString(), identificadorIgnoradoJpa);
}
}
Implementação do repositório de aplicação
Em paraPropriedadeOrdenacao, a ordenação pelo nome da unidade federativa é mapeada para u.nome, qualificada pelo alias da junção declarada na consulta de pesquisar. As demais propriedades, não qualificadas, referem-se ao município.
package com.example.exemplo.infraestrutura.geografia.municipio;
import java.util.Objects;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import com.example.exemplo.aplicacao.geografia.municipio.MunicipioConsulta;
import com.example.exemplo.aplicacao.geografia.municipio.MunicipioOrdenacao;
import com.example.exemplo.aplicacao.geografia.municipio.MunicipioProjecao;
import com.example.exemplo.aplicacao.geografia.municipio.spi.IMunicipioAplicacaoRepositorio;
import dev.sauloaraujo.d5.framework.infraestrutura.AgregadoAplicacaoRepositorioAbstrato;
public class MunicipioAplicacaoRepositorio
extends AgregadoAplicacaoRepositorioAbstrato<MunicipioProjecao, MunicipioOrdenacao, MunicipioConsulta>
implements IMunicipioAplicacaoRepositorio {
private final IMunicipioRepositorioJpa repositorioJpa;
public MunicipioAplicacaoRepositorio(IMunicipioRepositorioJpa repositorioJpa) {
this.repositorioJpa = Objects.requireNonNull(repositorioJpa);
}
@Override
protected Page<MunicipioProjecao> pesquisar(MunicipioConsulta consulta, Pageable pageable) {
Objects.requireNonNull(consulta);
Objects.requireNonNull(pageable);
var nome = paraLike(consulta.getNome());
var unidadeFederativa = consulta.getUnidadeFederativa().orElse(null);
return repositorioJpa.pesquisar(nome, unidadeFederativa, pageable);
}
@Override
protected String paraPropriedadeOrdenacao(MunicipioOrdenacao ordenacao) {
Objects.requireNonNull(ordenacao);
return switch (ordenacao) {
case IDENTIFICADOR -> "identificador";
case NOME -> "nome";
case UNIDADE_FEDERATIVA_NOME -> "u.nome";
};
}
}
Unidade federativa referenciada
Implemente existeReferencia no repositório de unidade federativa. A consulta verifica se algum município referencia a unidade federativa, comparando m.unidadeFederativa.identificador; caso outros agregados passem a referenciá-la, basta acrescentar suas verificações a essa consulta.
package com.example.exemplo.infraestrutura.geografia.unidadefederativa;
import org.jspecify.annotations.Nullable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.example.exemplo.aplicacao.geografia.unidadefederativa.UnidadeFederativaProjecao;
public interface IUnidadeFederativaRepositorioJpa extends JpaRepository<UnidadeFederativaJpa, String> {
@Query("""
select count(u) > 0
from UnidadeFederativaJpa u
where u.sigla = :sigla
and (:identificadorIgnorado is null or u.identificador <> :identificadorIgnorado)
""")
boolean existePorSigla(String sigla, @Nullable String identificadorIgnorado);
@Query("""
select count(u) > 0
from UnidadeFederativaJpa u
where lower(u.nome) = lower(:nome)
and (:identificadorIgnorado is null or u.identificador <> :identificadorIgnorado)
""")
boolean existePorNome(String nome, @Nullable String identificadorIgnorado);
@Query("""
select count(m) > 0
from MunicipioJpa m
where m.unidadeFederativa.identificador = :identificador
""")
boolean existeReferencia(String identificador);
@Query("""
select new com.example.exemplo.aplicacao.geografia.unidadefederativa.UnidadeFederativaProjecao(u.identificador, u.sigla, u.nome)
from UnidadeFederativaJpa u
where (:sigla is null or lower(u.sigla) like :sigla escape '\\')
and (:nome is null or lower(u.nome) like :nome escape '\\')
""")
Page<UnidadeFederativaProjecao> pesquisar(@Nullable String sigla, @Nullable String nome, Pageable pageable);
}
package com.example.exemplo.infraestrutura.geografia.unidadefederativa;
import java.util.Objects;
import java.util.Optional;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativa;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaCodigoIbge;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaServico;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaSigla;
import com.example.exemplo.dominio.geografia.unidadefederativa.spi.IUnidadeFederativaRepositorio;
import dev.sauloaraujo.d5.framework.infraestrutura.AgregadoRepositorioAbstrato;
public class UnidadeFederativaRepositorio extends
AgregadoRepositorioAbstrato<UnidadeFederativaCodigoIbge, UnidadeFederativa, String, UnidadeFederativaJpa>
implements IUnidadeFederativaRepositorio {
private final IUnidadeFederativaRepositorioJpa repositorioJpa;
public UnidadeFederativaRepositorio(IUnidadeFederativaRepositorioJpa repositorioJpa) {
super(repositorioJpa, UnidadeFederativaServico.IDENTIFICADOR_INEXISTENTE_MODELO);
this.repositorioJpa = Objects.requireNonNull(repositorioJpa);
}
@Override
protected UnidadeFederativaJpa paraJpa(UnidadeFederativa agregado) {
Objects.requireNonNull(agregado);
var identificador = agregado.getIdentificador().orElseThrow().toString();
return new UnidadeFederativaJpa(identificador, agregado.getSigla().toString(), agregado.getNome());
}
@Override
protected UnidadeFederativa paraAgregado(UnidadeFederativaJpa agregadoJpa) {
Objects.requireNonNull(agregadoJpa);
return new UnidadeFederativa(new UnidadeFederativaCodigoIbge(agregadoJpa.getIdentificador()),
new UnidadeFederativaSigla(agregadoJpa.getSigla()), agregadoJpa.getNome());
}
@Override
protected String paraIdentificadorJpa(UnidadeFederativaCodigoIbge identificador) {
Objects.requireNonNull(identificador);
return identificador.toString();
}
@Override
public boolean existePorSigla(UnidadeFederativaSigla sigla,
Optional<UnidadeFederativaCodigoIbge> identificadorIgnorado) {
Objects.requireNonNull(sigla);
Objects.requireNonNull(identificadorIgnorado);
var identificadorIgnoradoJpa = identificadorIgnorado.map(UnidadeFederativaCodigoIbge::toString).orElse(null);
return repositorioJpa.existePorSigla(sigla.toString(), identificadorIgnoradoJpa);
}
@Override
public boolean existePorNome(String nome, Optional<UnidadeFederativaCodigoIbge> identificadorIgnorado) {
Objects.requireNonNull(nome);
Objects.requireNonNull(identificadorIgnorado);
var identificadorIgnoradoJpa = identificadorIgnorado.map(UnidadeFederativaCodigoIbge::toString).orElse(null);
return repositorioJpa.existePorNome(nome, identificadorIgnoradoJpa);
}
@Override
public boolean existeReferencia(UnidadeFederativaCodigoIbge identificador) {
Objects.requireNonNull(identificador);
return repositorioJpa.existeReferencia(identificador.toString());
}
}
Testes do repositório
Escreva os testes do repositório de município, cobrindo, além das operações do repositório, as variações de existePorNome: na mesma unidade federativa, em outra unidade federativa, ignorando o identificador do próprio município e ignorando maiúsculas e minúsculas. Por causa da chave estrangeira, os testes persistem a unidade federativa antes dos municípios, diretamente através de IUnidadeFederativaRepositorioJpa.
package com.example.exemplo.infraestrutura.geografia.municipio;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import com.example.exemplo.dominio.geografia.municipio.Municipio;
import com.example.exemplo.dominio.geografia.municipio.MunicipioCodigoIbge;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaCodigoIbge;
import com.example.exemplo.infraestrutura.geografia.unidadefederativa.IUnidadeFederativaRepositorioJpa;
import com.example.exemplo.infraestrutura.geografia.unidadefederativa.UnidadeFederativaJpa;
import dev.sauloaraujo.d5.framework.dominio.AgregadoInexistenteExcecao;
@DataJpaTest
class MunicipioRepositorioTest {
private static final UnidadeFederativaCodigoIbge SAO_PAULO = new UnidadeFederativaCodigoIbge("35");
private static final UnidadeFederativaCodigoIbge RIO_DE_JANEIRO = new UnidadeFederativaCodigoIbge("33");
@Autowired
private IMunicipioRepositorioJpa repositorioJpa;
@Autowired
private IUnidadeFederativaRepositorioJpa unidadeFederativaRepositorioJpa;
private MunicipioRepositorio repositorio;
@BeforeEach
void configurar() {
repositorio = new MunicipioRepositorio(repositorioJpa, unidadeFederativaRepositorioJpa);
unidadeFederativaRepositorioJpa.save(new UnidadeFederativaJpa("35", "SP", "São Paulo"));
}
private static Municipio criar(String codigoIbge, String nome) {
return new Municipio(new MunicipioCodigoIbge(codigoIbge), nome, Optional.empty());
}
@Test
void incluiEBuscaPorIdentificador() {
repositorio.incluir(criar("3550308", "São Paulo"));
var municipio = repositorio.buscarPorIdentificador(new MunicipioCodigoIbge("3550308"));
assertEquals("São Paulo", municipio.getNome());
assertEquals(SAO_PAULO, municipio.getUnidadeFederativa());
assertFalse(municipio.isExtinto());
}
@Test
void buscaPorIdentificadorInexistenteLancaExcecao() {
assertThrows(AgregadoInexistenteExcecao.class,
() -> repositorio.buscarPorIdentificador(new MunicipioCodigoIbge("3550308")));
}
@Test
void existePorIdentificador() {
repositorio.incluir(criar("3550308", "São Paulo"));
assertTrue(repositorio.existePorIdentificador(new MunicipioCodigoIbge("3550308")));
assertFalse(repositorio.existePorIdentificador(new MunicipioCodigoIbge("3304557")));
}
@Test
void alteraPreservandoExtincao() {
repositorio.incluir(criar("3550308", "São Paulo").extinguir());
repositorio.alterar(new Municipio(new MunicipioCodigoIbge("3550308"), "São Paulo Alterado", Optional.of(true)));
var municipio = repositorio.buscarPorIdentificador(new MunicipioCodigoIbge("3550308"));
assertEquals("São Paulo Alterado", municipio.getNome());
assertTrue(municipio.isExtinto());
}
@Test
void exclui() {
repositorio.incluir(criar("3550308", "São Paulo"));
repositorio.excluir(new MunicipioCodigoIbge("3550308"));
assertFalse(repositorio.existePorIdentificador(new MunicipioCodigoIbge("3550308")));
}
@Test
void existePorNomeNaMesmaUnidadeFederativa() {
repositorio.incluir(criar("3550308", "São Paulo"));
assertTrue(repositorio.existePorNome("São Paulo", SAO_PAULO, Optional.empty()));
assertFalse(repositorio.existePorNome("São Paulo", RIO_DE_JANEIRO, Optional.empty()));
assertFalse(repositorio.existePorNome("Santos", SAO_PAULO, Optional.empty()));
}
@Test
void existePorNomeIgnorandoOProprioIdentificador() {
repositorio.incluir(criar("3550308", "São Paulo"));
var identificador = Optional.of(new MunicipioCodigoIbge("3550308"));
assertFalse(repositorio.existePorNome("São Paulo", SAO_PAULO, identificador));
}
@Test
void existePorNomeNaoIgnorandoUmMunicipioDiferente() {
repositorio.incluir(criar("3550308", "São Paulo"));
repositorio.incluir(criar("3548500", "Santos"));
var identificador = Optional.of(new MunicipioCodigoIbge("3548500"));
assertTrue(repositorio.existePorNome("São Paulo", SAO_PAULO, identificador));
}
@Test
void existePorNomeIgnorandoMaiusculasEMinusculas() {
repositorio.incluir(criar("3550308", "São Paulo"));
assertTrue(repositorio.existePorNome("SÃO PAULO", SAO_PAULO, Optional.empty()));
}
}
Pelo mesmo motivo, e porque a junção da pesquisa é interna, os testes do repositório de aplicação também persistem as unidades federativas antes dos municípios. Além dos filtros, eles cobrem a ordenação pelo nome da unidade federativa.
package com.example.exemplo.infraestrutura.geografia.municipio;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import com.example.exemplo.aplicacao.geografia.municipio.MunicipioConsulta;
import com.example.exemplo.aplicacao.geografia.municipio.MunicipioOrdenacao;
import com.example.exemplo.dominio.geografia.municipio.Municipio;
import com.example.exemplo.dominio.geografia.municipio.MunicipioCodigoIbge;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaCodigoIbge;
import com.example.exemplo.infraestrutura.geografia.unidadefederativa.IUnidadeFederativaRepositorioJpa;
import com.example.exemplo.infraestrutura.geografia.unidadefederativa.UnidadeFederativaJpa;
@DataJpaTest
class MunicipioAplicacaoRepositorioTest {
private static final UnidadeFederativaCodigoIbge RIO_DE_JANEIRO = new UnidadeFederativaCodigoIbge("33");
@Autowired
private IMunicipioRepositorioJpa repositorioJpa;
@Autowired
private IUnidadeFederativaRepositorioJpa unidadeFederativaRepositorioJpa;
private MunicipioRepositorio repositorio;
private MunicipioAplicacaoRepositorio repositorioAplicacao;
@BeforeEach
void configurar() {
repositorio = new MunicipioRepositorio(repositorioJpa, unidadeFederativaRepositorioJpa);
repositorioAplicacao = new MunicipioAplicacaoRepositorio(repositorioJpa);
unidadeFederativaRepositorioJpa.save(new UnidadeFederativaJpa("35", "SP", "São Paulo"));
unidadeFederativaRepositorioJpa.save(new UnidadeFederativaJpa("33", "RJ", "Rio de Janeiro"));
}
private static Municipio criar(String codigoIbge, String nome) {
return new Municipio(new MunicipioCodigoIbge(codigoIbge), nome, Optional.empty());
}
private static MunicipioConsulta consultar(Optional<String> nome, Optional<String> unidadeFederativa) {
return consultar(MunicipioOrdenacao.NOME, nome, unidadeFederativa);
}
private static MunicipioConsulta consultar(MunicipioOrdenacao ordenacao, Optional<String> nome,
Optional<String> unidadeFederativa) {
return new MunicipioConsulta(Optional.of(ordenacao), Optional.empty(), Optional.empty(), Optional.empty(), nome,
unidadeFederativa);
}
@Test
void pesquisaRetornaProjecaoOrdenadaPorNome() {
repositorio.incluir(criar("3550308", "São Paulo"));
repositorio.incluir(criar("3304557", "Rio de Janeiro").extinguir());
var resultado = repositorioAplicacao.pesquisar(consultar(Optional.empty(), Optional.empty()));
assertEquals(2, resultado.getTotal());
var primeira = resultado.getProjecoes().get(0);
assertEquals(new MunicipioCodigoIbge("3304557"), primeira.getIdentificador());
assertEquals("Rio de Janeiro", primeira.getNome());
assertEquals(RIO_DE_JANEIRO, primeira.getUnidadeFederativa());
assertEquals("Rio de Janeiro", primeira.getUnidadeFederativaNome());
assertTrue(primeira.isExtinto());
assertEquals("São Paulo", resultado.getProjecoes().get(1).getNome());
}
@Test
void pesquisaOrdenadaPorNomeDaUnidadeFederativa() {
repositorio.incluir(criar("3509502", "Campinas"));
repositorio.incluir(criar("3303302", "Niterói"));
var resultado = repositorioAplicacao
.pesquisar(consultar(MunicipioOrdenacao.UNIDADE_FEDERATIVA_NOME, Optional.empty(), Optional.empty()));
assertEquals("Niterói", resultado.getProjecoes().get(0).getNome());
assertEquals("Campinas", resultado.getProjecoes().get(1).getNome());
}
@Test
void pesquisaFiltrandoPorTrechoDoNome() {
repositorio.incluir(criar("3550308", "São Paulo"));
repositorio.incluir(criar("3304557", "Rio de Janeiro"));
var resultado = repositorioAplicacao.pesquisar(consultar(Optional.of("paulo"), Optional.empty()));
assertEquals(1, resultado.getTotal());
assertEquals("São Paulo", resultado.getProjecoes().get(0).getNome());
}
@Test
void pesquisaFiltrandoPorUnidadeFederativa() {
repositorio.incluir(criar("3550308", "São Paulo"));
repositorio.incluir(criar("3304557", "Rio de Janeiro"));
var resultado = repositorioAplicacao.pesquisar(consultar(Optional.empty(), Optional.of("33")));
assertEquals(1, resultado.getTotal());
assertEquals("Rio de Janeiro", resultado.getProjecoes().get(0).getNome());
}
}
Acrescente aos testes do repositório de unidade federativa a verificação de existeReferencia, persistindo um município diretamente através de IMunicipioRepositorioJpa, com a unidade federativa obtida por getReferenceById:
package com.example.exemplo.infraestrutura.geografia.unidadefederativa;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativa;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaCodigoIbge;
import com.example.exemplo.dominio.geografia.unidadefederativa.UnidadeFederativaSigla;
import com.example.exemplo.infraestrutura.geografia.municipio.IMunicipioRepositorioJpa;
import com.example.exemplo.infraestrutura.geografia.municipio.MunicipioJpa;
import dev.sauloaraujo.d5.framework.dominio.AgregadoInexistenteExcecao;
@DataJpaTest
class UnidadeFederativaRepositorioTest {
@Autowired
private IUnidadeFederativaRepositorioJpa repositorioJpa;
@Autowired
private IMunicipioRepositorioJpa municipioRepositorioJpa;
private UnidadeFederativaRepositorio repositorio;
@BeforeEach
void configurar() {
repositorio = new UnidadeFederativaRepositorio(repositorioJpa);
}
private static UnidadeFederativa criar(String codigoIbge, String sigla, String nome) {
return new UnidadeFederativa(new UnidadeFederativaCodigoIbge(codigoIbge), new UnidadeFederativaSigla(sigla),
nome);
}
@Test
void incluiEBuscaPorIdentificador() {
repositorio.incluir(criar("35", "SP", "São Paulo"));
var unidadeFederativa = repositorio.buscarPorIdentificador(new UnidadeFederativaCodigoIbge("35"));
assertEquals(new UnidadeFederativaSigla("SP"), unidadeFederativa.getSigla());
assertEquals("São Paulo", unidadeFederativa.getNome());
}
@Test
void buscaPorIdentificadorInexistenteLancaExcecao() {
assertThrows(AgregadoInexistenteExcecao.class,
() -> repositorio.buscarPorIdentificador(new UnidadeFederativaCodigoIbge("35")));
}
@Test
void existePorIdentificador() {
repositorio.incluir(criar("35", "SP", "São Paulo"));
assertTrue(repositorio.existePorIdentificador(new UnidadeFederativaCodigoIbge("35")));
assertFalse(repositorio.existePorIdentificador(new UnidadeFederativaCodigoIbge("33")));
}
@Test
void altera() {
repositorio.incluir(criar("35", "SP", "São Paulo"));
repositorio.alterar(criar("35", "SP", "São Paulo Alterado"));
var unidadeFederativa = repositorio.buscarPorIdentificador(new UnidadeFederativaCodigoIbge("35"));
assertEquals("São Paulo Alterado", unidadeFederativa.getNome());
}
@Test
void exclui() {
repositorio.incluir(criar("35", "SP", "São Paulo"));
repositorio.excluir(new UnidadeFederativaCodigoIbge("35"));
assertFalse(repositorio.existePorIdentificador(new UnidadeFederativaCodigoIbge("35")));
}
@Test
void existePorSiglaConsiderandoTodasAsUnidadesFederativas() {
repositorio.incluir(criar("35", "SP", "São Paulo"));
assertTrue(repositorio.existePorSigla(new UnidadeFederativaSigla("SP"), Optional.empty()));
assertFalse(repositorio.existePorSigla(new UnidadeFederativaSigla("RJ"), Optional.empty()));
}
@Test
void existePorSiglaIgnorandoOProprioIdentificador() {
repositorio.incluir(criar("35", "SP", "São Paulo"));
var identificador = Optional.of(new UnidadeFederativaCodigoIbge("35"));
assertFalse(repositorio.existePorSigla(new UnidadeFederativaSigla("SP"), identificador));
}
@Test
void existePorSiglaNaoIgnorandoUmaUnidadeFederativaDiferente() {
repositorio.incluir(criar("35", "SP", "São Paulo"));
repositorio.incluir(criar("33", "RJ", "Rio de Janeiro"));
var identificador = Optional.of(new UnidadeFederativaCodigoIbge("33"));
assertTrue(repositorio.existePorSigla(new UnidadeFederativaSigla("SP"), identificador));
}
@Test
void existePorNomeConsiderandoTodasAsUnidadesFederativas() {
repositorio.incluir(criar("35", "SP", "São Paulo"));
assertTrue(repositorio.existePorNome("São Paulo", Optional.empty()));
assertFalse(repositorio.existePorNome("Rio de Janeiro", Optional.empty()));
}
@Test
void existePorNomeIgnorandoOProprioIdentificador() {
repositorio.incluir(criar("35", "SP", "São Paulo"));
var identificador = Optional.of(new UnidadeFederativaCodigoIbge("35"));
assertFalse(repositorio.existePorNome("São Paulo", identificador));
}
@Test
void existePorNomeNaoIgnorandoUmaUnidadeFederativaDiferente() {
repositorio.incluir(criar("35", "SP", "São Paulo"));
repositorio.incluir(criar("33", "RJ", "Rio de Janeiro"));
var identificador = Optional.of(new UnidadeFederativaCodigoIbge("33"));
assertTrue(repositorio.existePorNome("São Paulo", identificador));
}
@Test
void existePorNomeIgnorandoMaiusculasEMinusculas() {
repositorio.incluir(criar("35", "SP", "São Paulo"));
assertTrue(repositorio.existePorNome("SÃO PAULO", Optional.empty()));
}
@Test
void existeReferenciaPorMunicipio() {
repositorio.incluir(criar("35", "SP", "São Paulo"));
repositorio.incluir(criar("33", "RJ", "Rio de Janeiro"));
municipioRepositorioJpa
.save(new MunicipioJpa("3550308", "São Paulo", repositorioJpa.getReferenceById("35"), false));
assertTrue(repositorio.existeReferencia(new UnidadeFederativaCodigoIbge("35")));
assertFalse(repositorio.existeReferencia(new UnidadeFederativaCodigoIbge("33")));
}
}
Execute os testes da infraestrutura:
mvn -pl infraestrutura -am test
UI
O município é exposto no pacote com.example.exemplo.ui.backend.geografia.municipio. Crie o package-info.java desse pacote, anotado com @NullMarked:
@org.jspecify.annotations.NullMarked
package com.example.exemplo.ui.backend.geografia.municipio;
Controlador
Além das operações herdadas de AgregadoControladorAbstrato, o controlador expõe a ação de extinguir em POST {identificador}/extinguir, delegando a MunicipioServico.extinguir. Como todo método mapeado para POST, ela é executada em uma transação graças ao gerenciamento de transações.
package com.example.exemplo.ui.backend.geografia.municipio;
import java.util.Objects;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.exemplo.aplicacao.geografia.municipio.MunicipioAplicacaoServico;
import com.example.exemplo.aplicacao.geografia.municipio.MunicipioConsulta;
import com.example.exemplo.aplicacao.geografia.municipio.MunicipioProjecao;
import com.example.exemplo.dominio.geografia.municipio.Municipio;
import com.example.exemplo.dominio.geografia.municipio.MunicipioAutorizacoes;
import com.example.exemplo.dominio.geografia.municipio.MunicipioCodigoIbge;
import com.example.exemplo.dominio.geografia.municipio.MunicipioServico;
import dev.sauloaraujo.d5.framework.ui.AgregadoControladorAbstrato;
import dev.sauloaraujo.d5.framework.ui.Backend;
@RestController
@RequestMapping(Backend.PREFIXO + "geografia/municipio")
public class MunicipioControlador extends
AgregadoControladorAbstrato<MunicipioCodigoIbge, Municipio, MunicipioAutorizacoes, MunicipioProjecao, MunicipioConsulta> {
private final MunicipioServico servico;
public MunicipioControlador(MunicipioServico servico, MunicipioAplicacaoServico servicoAplicacao) {
super(servico, servicoAplicacao);
this.servico = Objects.requireNonNull(servico);
}
@PostMapping("{identificador}/extinguir")
public void extinguir(@PathVariable MunicipioCodigoIbge identificador) {
Objects.requireNonNull(identificador);
servico.extinguir(identificador);
}
}
Autenticação
O município não precisa entrar no recurso de autenticação: não altere AutenticacaoRecurso nem AutenticacaoControlador. O recurso continua trazendo apenas o usuário e as autorizações sobre a unidade federativa.
Testando com o Swagger UI
Encerre a aplicação, se ela ainda estiver em execução, e suba novamente BackendAplicacao — pela IDE ou executando de novo, no diretório exemplo, os dois comandos da lição 1: sem o mvn install, o spring-boot:run usaria os módulos instalados no repositório Maven local ao final da lição 1, que não têm o município. Em seguida, acesse o Swagger UI e autentique-se como na lição 1. As operações de município aparecem sob a tag municipio-controlador, inclusive a de extinguir:

Como o banco de dados em memória é recriado a cada inicialização, inclua de novo a unidade federativa de São Paulo, como na lição 1 — sem ela, a inclusão do município responde 404. Em seguida, inclua o município de São Paulo através de POST /backend/geografia/municipio/incluir. O corpo de exemplo sugerido pelo Swagger UI traz, além de identificador e nome, os atributos extinto, com o valor true, e unidadeFederativa: remova os dois. Com extinto igual a true, a inclusão responde 422, porque um município só se torna extinto pela ação de extinguir; omitido, o indicador assume false. Já unidadeFederativa aparece no exemplo porque o município a expõe através de getUnidadeFederativa, mas é derivada do identificador e, por isso, ignorada na requisição:
{
"identificador": "3550308",
"nome": "São Paulo"
}

POST /backend/geografia/municipio/{identificador}/extinguir recebe apenas o identificador no caminho e extingue o município:

Depois disso, a alteração do município responde 409, com a mensagem O município 3550308 está extinto. O recurso devolvido por GET /backend/geografia/municipio/{identificador}/alteracao mostra o município extinto e inclui a autorização extinguir, que o cliente pode usar para exibir ou ocultar a ação. Como as autorizações não dependem do estado do município, extinguir continua true para um administrador; para saber se a ação ainda é possível, o cliente consulta o atributo extinto do município:

Por fim, tente excluir a unidade federativa de São Paulo: como ela é referenciada pelo município, a exclusão responde 409, com a mensagem A unidade federativa 35 é referenciada e não pode ser excluída.

Na lição 3, você vai criar o subdomínio CRM e o cliente, que referencia o município.