Escolar Documentos
Profissional Documentos
Cultura Documentos
O processo para criar os tipos anteriores é simplesmente percorrer cada tipo aninhado na documentação da API
do YouTube do Google e capturar os campos conforme mostrado. Eles os descreverão como strings, inteiros,
tipos aninhados ou links para outros tipos. Para cada subtipo que possui sua própria seção, basta criar outro registro!
Dica
O nome do tipo do registro não importa. A parte crítica é garantir o nome do campo
corresponde aos nomes na estrutura JSON que está sendo repassada.
Com o que capturamos até agora em registros, agora precisamos criar SearchId e também SearchSnippet, novamente,
cada um em seu próprio arquivo:
Esses tipos de registro estão quase completos, visto que são quase todos tipos Java integrados. O único que
falta é SearchThumbnail. Se lermos os documentos de referência da API do YouTube, podemos facilmente
encerrar as coisas com esta definição de registro:
Este último tipo de registro é apenas uma string e alguns números inteiros, então terminamos!
Mas não exatamente. Investimos muito tempo configurando o OAuth 2 e um serviço HTTP remoto para conversar
com o YouTube. A cereja do bolo é construir a camada web do nosso aplicativo, mostrada na próxima seção.
@Controlador
@GetMapping
Índice de string (modelo modelo) {
model.addAttribute("canalVídeos", //
youTube.channelVideos("UCjukbYOd6pjrMpNMFAOKYyw",
10, YouTube.Sort.VIEW_COUNT));
retornar "índice";
}
}
• @Controller indica que este é um controlador web baseado em modelo. Cada método web retorna
o nome de um modelo a ser renderizado.
• Estamos injetando o serviço do YouTube por meio de injeção de construtor, um conceito abordado
de volta ao Capítulo 2, Criando um aplicativo da Web com Spring Boot.
• O método index possui um objeto Spring MVC Model, onde criamos um canalVideos
atributo. Ele invoca o método channelVideos do nosso serviço do YouTube com um ID de canal, um tamanho de página
Como estamos usando o Moustache como nosso mecanismo de modelagem preferido, o nome do modelo se
expande para src/main/resources/templates/index.mustache. Para defini-lo, podemos começar a codificar um
HTML 5 bem simples da seguinte maneira:
<!doctypehtml>
<html lang="pt">
<cabeça>
</head>
<corpo>
<h1>Saudações, fãs do Learning Spring Boot 3.0!</h1>
Machine Translated by Google
<p>
Nesta seção, estamos aprendendo como fazer
um aplicativo da web usando Spring Boot 3.0 + OAuth 2.0 </p>
<h2>Seus vídeos</h2>
<tabela>
<thead>
<tr>
<td>Id</td>
<td>Publicado</td>
<td>Miniatura</td>
<td>Título</td>
<td>Descrição</td>
</tr> </
thead>
<tbody>
{{#channelVideos.items}}
<tr>
<td>{{id.videoId}}</td>
<td>{{snippet.publishedAt}}</td>
<td>
<a href="https://www.youtube.com/watch?v= {{id.videoId}}"
target="_blank"> <img src="{{snippet.thumbnail.url}}"
alt= "thumbnail"/> </a> </td> <td>{{snippet.title}}</td>
<td>{{snippet.shortDescription}}
</td>
</tr> {{/
channelVideos.items}} </tbody> </
table> </
body>
Machine Translated by Google
Não exploraremos todas as facetas do HTML 5 no código anterior. Mas algumas das partes principais do
Bigode são as seguintes:
• As diretivas bigode são colocadas entre chaves duplas, seja para iterar sobre uma matriz
({{#channelVideos.items}}) ou um único campo ({{id.videoId}}).
• Uma diretiva Moustache que começa com um sinal de cerquilha (#) é um sinal para iterar, gerando
uma cópia do HTML para cada entrada. Como os campos de itens SearchListResponse são uma
matriz de entradas SearchResult, o HTML dentro dessa tag é repetido para cada entrada.
• O campo de miniatura dentro do SearchSnippet possui, na verdade, diversas entradas para cada vídeo. Como o
Mustache é um mecanismo sem lógica, precisamos aumentar essa definição de registro com alguns métodos extras
para dar suporte às nossas necessidades de modelagem.
Adicionar uma maneira de escolher a miniatura correta e também reduzir o campo de descrição para menos de
cem caracteres pode ser implementado atualizando o registro SearchSnippet da seguinte forma:
String shortDescription() {
if (this.description.length() <= 100) {
retorne esta.descrição;
}
retorne esta.descrição.substring(0, 100);
}
• O método de miniatura irá iterar sobre as entradas do mapa de miniaturas, encontrar aquela chamada
padrão e devolvê-lo
Para um acabamento saboroso, vamos aplicar CSS para que nossa mesa fique bem polida. Crie src/main/
resources/ static/style.css:
mesa {
layout da tabela: fixo; largura:
100%;
colapso da fronteira: colapso; borda: 3px
sólido #039E44;
}
thead th:nth-child(1) {
largura: 30%;
}
thead th:nth-child(2) {
largura: 20%;
}
thead th:nth-child(3) {
largura: 15%;
}
thead th:nth-child(4) {
largura: 35%;
}
th, td
{ preenchimento: 20px;
}
Machine Translated by Google
De acordo com o código anterior, o Spring MVC servirá recursos estáticos encontrados em src/main/resources/
estática automaticamente. Com tudo isso instalado, vamos lançar o aplicativo! Visite localhost:8080
e devemos ser automaticamente encaminhados para a página de login do Google:
Esta página de login mostrará todas as contas do Google que você usou (tenho várias!). O importante
é escolher uma conta que você registrou anteriormente no Google Cloud Dashboard com o aplicativo.
Caso contrário, não funcionará!
Agora, se tivéssemos aceitado a lista de escopo padrão do Google do CommonOAuth2Provider, tudo o que pediríamos ao
Google seriam detalhes da conta do usuário, como um endereço de e-mail. Então, teríamos sido redirecionados de volta
para nosso próprio aplicativo web.
Mas como personalizamos a propriedade scope para acessar a API do YouTube, outro prompt aparecerá, solicitando que
selecionemos um canal específico do YouTube (se você não tiver um, terá que declarar um, mesmo que você não carregue
nada!).
Machine Translated by Google
Escolha o seu canal (sim, tenho vários!). A partir daqui, seremos redirecionados de volta ao nosso modelo
Spring Boot Moustache, como segue:
Machine Translated by Google
Simples assim, temos os dados do YouTube em exibição em nossa própria página! As miniaturas ainda
possuem hiperlinks, permitindo abrir uma nova aba do navegador e assistir aos vídeos.
Dica
O código anterior busca dados do meu canal no YouTube, mostrando os vídeos mais populares (a Figura 4.7 foi
cortada para caber neste livro). No entanto, você pode inserir qualquer ID de canal (não o URL do canal
personalizado) e obter uma leitura. Por exemplo, confira o canal do meu amigo Dan Vega, um defensor do
desenvolvedor Spring que fez vários vídeos Spring, digitando youTube.channelVideos("UCc98QQw1D-
y38wg6mO3w4MQ", 10, YouTube.
Classificar.VIEW_COUNT).
Machine Translated by Google
Ao usar o OAuth2, construímos com sucesso um sistema onde podemos transferir o gerenciamento de usuários para um
serviço de terceiros. Isso reduz muito o nosso próprio risco quando se trata de gerenciamento de usuários, transferindo-
o para o Google (ou qualquer serviço OAuth2 que escolhermos).
Na verdade, este é um dos principais motivos para aproveitar um serviço de terceiros. Existem muitas start-ups e empresas que
oferecem serviços interessantes, exigindo pouco mais do que um ID de usuário e um endereço de e-mail para identificar os usuários.
Você já percebeu como alguns sites oferecem permissão para fazer login por meio de vários serviços externos?
Isso porque podemos definir entradas em múltiplas plataformas, adicionar todas as suas entradas ao aplicativo.
yaml e coordene todos eles.
Deixarei para você como exercício mexer em seu aplicativo para oferecer suporte a vários serviços externos.
Bônus
Sinta-se à vontade para se juntar a mim em uma transmissão ao vivo prática, onde literalmente criamos um cliente
Google OAuth2, registramos-no no Spring Boot 3 e fornecemos dados do YouTube em https://springbootlearning.
com/oauth2!
Resumo
Ao longo deste capítulo, aprendemos como proteger um aplicativo Spring MVC. Conectamos usuários
personalizados, aplicamos controles baseados em caminho e até adicionamos controles refinados em nível
de método usando Spring Security. Finalizamos terceirizando o gerenciamento de usuários para o grandioso
Google usando a integração OAuth2 do Spring Security. Aproveitamos isso para obter alguns dados do
YouTube e fornecer links de vídeo.
Este capítulo pode parecer longo, mas na verdade, a segurança é um assunto complexo. Esperamos que, com as diversas
táticas mostradas neste capítulo, você tenha algumas ideias sólidas sobre o que fazer quando chegar a hora de proteger
seus próprios aplicativos.
No próximo capítulo, Testando com Spring Boot, exploraremos como garantir que nosso código seja sólido
com vários mecanismos de teste.
Machine Translated by Google
5
Testando com Spring Boot
No capítulo anterior, aprendemos como proteger um aplicativo por meio de várias táticas, incluindo regras baseadas
em caminhos e regras baseadas em métodos. Aprendemos até como delegar para um sistema externo como o
Google para aliviar o risco do gerenciamento de usuários.
Neste capítulo, aprenderemos sobre testes no Spring Boot. O teste é uma abordagem multifacetada. Também
não é algo que você realmente termina. Isso porque toda vez que adicionamos novos recursos, devemos adicionar
casos de teste correspondentes para capturar os requisitos e verificar se eles são atendidos. É sempre possível
descobrir casos extremos nos quais não pensamos. E à medida que nosso aplicativo evolui, temos que atualizar
e atualizar nossos métodos de teste.
O teste é uma filosofia que, quando adotada, nos permite aumentar a nossa confiança no software que construímos . Por sua vez,
podemos levar essa confiança aos nossos clientes e clientes, demonstrando qualidade.
O objetivo deste capítulo é apresentar uma ampla gama de táticas de teste e suas diversas vantagens e desvantagens.
Não para que possamos garantir que o código de exemplo deste livro seja bem testado, mas para que você possa aprender como testar
melhor seus projetos e saber quais táticas usar e quando!
Nada mesmo.
Isso mesmo. Lembra como usamos Spring Initialzr (start.spring.io) nos capítulos anteriores para implementar nosso novo
projeto (ou ampliar um já existente)? Uma das dependências que foi adicionada automaticamente na parte inferior é esta:
<dependência>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>teste</scope>
</dependency>
Este iniciador Spring Boot com escopo de teste contém um conjunto totalmente carregado de dependências úteis,
incluindo o seguinte:
Se você nunca ouviu falar, mocking é uma forma de teste onde em vez de verificar os resultados,
verificamos os métodos invocados. Veremos como usar isso mais adiante neste capítulo.
Machine Translated by Google
Simplificando, TODOS esses kits de ferramentas já estão ao nosso alcance, prontos para escrever testes. Por que?
Para que não tenhamos que escolher e perder tempo. Não há necessidade de procurar o kit de teste
certo e nem precisamos escolher testar. Spring Initialzr, o campeão na construção de projetos Spring
Boot, adiciona todos eles sem sequer exigir que nos lembremos de adicioná-los.
Não usaremos necessariamente cada um desses kits de ferramentas neste capítulo, mas obteremos uma boa
seção transversal de funcionalidades. E ao final deste capítulo, você deverá ter uma perspectiva melhor sobre o
que esses kits de ferramentas oferecem.
Então, vamos começar escrevendo alguns casos de teste em torno do objeto de domínio VideoEntity que
definimos no Capítulo 3, Consultando dados com Spring Boot:
@Teste
void newVideoEntityShouldHaveNullId() {
Entidade VideoEntity = new VideoEntity("alice",
"Descrição do título");
assertThat(entity.getId()).isNull();
assertThat(entity.getUsername()).isEqualTo("alice");
assertThat(entity.getName()).isEqualTo("título");
assertThat(entity.getDescription())
.isEqualTo("descrição");
}
}
• CoreDomainTest: Este é o nome deste conjunto de testes. Por convenção, as classes do conjunto de
testes terminam com a palavra Teste. Não é incomum que terminem com UnitTest para testes
unitários, IntegrationTest para testes de integração e quaisquer outros qualificadores.
• @Test: Esta anotação JUnit sinaliza que este método é um caso de teste. Certifique-se de usar o org.
Versão junit.jupiter.api de @Test, e não a versão org.junit. O antigo
Machine Translated by Google
O pacote é JUnit 5, enquanto o último pacote é JUnit 4 (ambos estão no caminho de classe para suportar
compatibilidade com versões anteriores).
• assertThat(): um método auxiliar estático AssertJ que recebe um valor e o verifica com uma
coleção de cláusulas.
• isEqualTo(): Verifica se os vários campos são iguais aos seus valores esperados.
Dentro do nosso IDE, podemos clicar com o botão direito na classe e executá-la:
Figura 5.1 – Clicar com o botão direito em uma classe de teste e executá-la
Machine Translated by Google
Figura 5.2 – Vendo os resultados do teste (marca de verificação verde para aprovação)
Fora desta saída está o fato de que este caso de teste levou cerca de 49 milissegundos para ser executado. Executar testes
com frequência é fundamental quando se trata de adotar uma filosofia de teste. Cada vez que editamos algum código,
devemos executar nossos conjuntos de testes – se possível, todos eles.
Antes de passarmos para mais técnicas de teste, lembra como concordamos que qualquer método
voltado ao público deveria ser testado? Isso se estende a coisas como o método toString() da classe de
domínio, mostrado a seguir:
@Teste
void toStringShouldAlsoBeTested() {
Entidade VideoEntity = new VideoEntity("alice", "título",
"descrição");
assertThat(entidade.toString())
.isEqualTo("VideoEntity{id=null, nome de usuário='alice', nome='título',
descrição='descrição'}");
}
• @Test: Novamente, esta anotação é usada para indicar que este é um método de teste.
• toStringShouldAlsoBeTested(): Sempre tente usar nomes de métodos de teste como forma de capturar a
intenção do teste. Dica: sempre gosto de usar deveria em algum lugar do nome do método para
aprimorar sua finalidade.
• Novamente, a primeira linha cria uma instância de VideoEntity com informações básicas.
• assertThat(): Serve para verificar se o valor do método toString() possui o valor esperado.
Machine Translated by Google
A afirmação deste método de teste poderia ser adicionada ao método de teste anterior. Afinal,
ambos têm o mesmo VideoEntity. Por que dividi-lo em métodos separados? Para capturar
claramente a intenção de testar o método toString() da entidade. O método de teste anterior
concentra-se em preencher uma entidade usando seu construtor e depois verificar seus métodos getter.
O método toString() é um método separado. Ao dividir as asserções em métodos de teste menores, há menos
chance de um teste falhar mascarar outro.
@Teste
void settersShouldMutateState() {
Entidade VideoEntity = new VideoEntity("alice", "título",
"descrição");
entidade.setId(99L);
entidade.setName("novo nome");
entidade.setDescription("nova descrição");
entidade.setUsername("bob");
assertThat(entity.getId()).isEqualTo(99L);
assertThat(entity.getUsername()).isEqualTo("bob");
assertThat(entity.getName()).isEqualTo("novo nome");
assertThat(entity.getDescription()) //
.isEqualTo("nova descrição");
• settersShouldMutateState(): Este método de teste tem como objetivo verificar a identidade da entidade
métodos setter
• A primeira linha cria a mesma instância de entidade que os outros casos de teste
• Utiliza as mesmas asserções AssertJ de antes, mas com valores diferentes, verificando se o estado
sofreu mutação corretamente
Com esta classe de teste em vigor, estamos em condições de nos envolver na cobertura de teste. O IntelliJ (e os IDEs mais
modernos) oferecem os meios para executar os casos de teste com utilitários de cobertura, conforme mostrado aqui:
Machine Translated by Google
O IntelliJ mostra quais linhas foram testadas com realces coloridos. Isso revela que nosso VideoEntity
a classe de entidade é totalmente coberta, exceto para o construtor protegido sem argumentos. Fica como exercício escrever outro
caso de teste verificando esse construtor.
Esta seção mostrou como escrever testes em nível de unidade em uma classe de domínio. É fácil estender esse conceito para
classes que contêm funções, algoritmos e outros recursos funcionais.
Mas outra área que devemos levar em consideração é o fato de que a maior parte das aplicações existentes são aplicações web.
E a próxima seção mostrará várias táticas para verificar os controladores web Spring MVC.
a funcionalidade da web.
Embora pudéssemos instanciar um controlador web Spring MVC e interrogá-lo usando várias asserções, isso ficaria complicado. E
parte do que buscamos é passar pelo maquinário do Spring MVC.
Essencialmente, precisamos fazer uma chamada pela web e esperar a resposta de um controlador.
Machine Translated by Google
Para testar a classe HomeController que construímos anteriormente neste livro, precisamos
criar uma nova classe de teste, HomeControllerTest.java, abaixo de src/test/java no mesmo
pacote que HomeController:
@WebMvcTest(controladores = HomeController.class)
@Teste
@WithMockUser
conteúdo().string( //
contémString("Autoridades: [ROLE_USER]"))) // .andReturn() //
getResponse().getContentAsString();
assertThat(html).contains( //
"<form action=\"/logout\"", // "<form action=\"/
search\"", // "<form action=\"/new-video\"");
}
}
• @WebMvcTest: uma anotação de teste do Spring Boot que habilita o maquinário do Spring
MVC. O parâmetro controllers restringe este conjunto de testes apenas à classe HomeController.
Machine Translated by Google
• @Autowired MockMvc mvc: @WebMvcTest adiciona uma instância do utilitário MockMvc do Spring ao contexto do aplicativo.
Em seguida, podemos conectá-lo automaticamente em nosso conjunto de testes para uso de todos os métodos de teste.
• @WithMockUser: esta anotação do Spring Security Test simula um usuário efetuando login
com um nome de usuário user e uma autoridade ROLE_USER (valores padrão).
• Após a chamada MockMVC vem uma asserção AssertJ, verificando bits da saída HTML.
Este método de teste invoca essencialmente o URL base da classe HomeController e verifica vários
aspectos dela, como o código de resposta e também seu conteúdo.
Um recurso importante do nosso aplicativo da web é a capacidade de criar novos vídeos. Estamos interagindo com o formulário
HTML que colocamos na página da web anteriormente neste capítulo por meio do método mostrado abaixo:
@Teste
@WithMockUser
verificar(videoService).create( //
novo NovoVídeo( //
"Novo vídeo", //
"nova descrição"), //
"do utilizador");
}
Machine Translated by Google
• @Test: uma anotação JUnit 5 para sinalizar isso como um método de teste.
• @WithMockUser: faz com que passemos pelas verificações do Spring Security por meio de autenticação simulada.
• Desta vez, o método de teste utiliza MockMVC para realizar post("/new-video") com dois
parâmetros (nome e descrição). Como a página da web usa falsificação de solicitação entre sites
(CSRF), podemos usar .with(csrf()) para fornecer automaticamente o token CSRF adequado, simulando
ainda mais isso como uma solicitação válida e não um ataque.
• verify(videoService): Hook do Mockito para verificar se o método create() do bean simulado VideoService foi chamado com
os mesmos parâmetros alimentados pelo MockMVC e o nome de usuário de @WithMockUser.
Com tudo isso criado, podemos facilmente executar nosso conjunto de testes, conforme mostrado aqui:
Esta captura de tela dos resultados do teste nos mostra exercitando com sucesso alguns métodos do controlador em menos de
um segundo.
Ser capaz de provar o comportamento fundamental do controlador tão rapidamente é fundamental. Permite-nos construir um
regime de testes, verificando todos os nossos controladores. E como mencionado anteriormente neste capítulo, quanto mais
testes escrevermos, mais confiança poderemos incutir em nosso sistema.
Algo que abordamos brevemente nesta seção foi o uso de um bean VideoService simulado.
Há muito mais que podemos fazer com zombaria, como abordaremos na próxima seção.
Algo fundamental é identificar quaisquer colaboradores. Como o único serviço injetado no HomeController é o VideoService,
vamos dar uma olhada mais de perto.
Machine Translated by Google
VideoService, conforme definido no Capítulo 3, Consultando dados com Spring Boot, tem um
colaborador, VideoRepository. Essencialmente, para testar o bean VideoService como um teste de
unidade, precisamos isolá-lo de quaisquer influências externas. Isso pode ser feito usando zombaria.
Na seção anterior, aproveitamos a anotação @WebMvcTest baseada em fatia do Spring Boot Test. Nesta seção,
usaremos uma tática diferente para configurar as coisas:
@ExtendWith(MockitoExtension.class)
Serviço de VideoService;
Repositório @Mock VideoRepository;
@BeforeEach
void configuração() {
this.service = novo VideoService (repositório);
}
}
• @BeforeEach: anotação do JUnit 5 para fazer esse método de configuração ser executado antes de cada método de teste
Mockito sempre teve seu método estático mock(), que possibilita a criação de objetos simulados.
Mas usar a anotação @Mock (e a extensão MockitoExtension JUnit 5) deixa bem claro qual componente
está em teste.
Com esse maquinário instalado, estamos prontos para adicionar nosso primeiro método de teste:
@Teste
void getVideosShouldReturnAll() {
// dado
VideoEntity video1 = new VideoEntity("alice", "Primavera
Boot 3 Intro", "Aprenda o básico!");
VideoEntity video2 = new VideoEntity("alice", "Primavera
Boot 3 Deep Dive", "Vá fundo!");
quando(repository.findAll()).thenReturn(List.of(video1,
vídeo2));
// quando
List<VideoEntity> vídeos = service.getVideos();
// então
assertThat(vídeos).containsExactly(vídeo1, vídeo2);
}
• @Test : Mais uma vez, esta é a anotação do JUnit 5 que o sinaliza como um método de teste.
• As duas primeiras linhas tratam da criação de alguns dados de teste. A terceira linha usa Mockito para
definir como o VideoRepository simulado responde quando seu método findAll() é invocado.
Esses marcadores, embora precisos, não capturam todo o fluxo deste método de teste.
Para começar, este método tem três comentários que denotam três fases: dado, quando e então. O conceito
dado, quando e então é um elemento básico por trás do design orientado a comportamento (BDD). A ideia é
que , dado um conjunto de entradas, quando você executa uma ação X, você pode esperar Y.
Machine Translated by Google
Os casos de teste que fluem dessa maneira tendem a ser mais fáceis de ler. E não apenas por desenvolvedores de
software, mas também por analistas de negócios e outros colegas de equipe que não estão tão focados em escrever código,
mas sim em capturar a intenção do cliente.
Dica
Seríamos negligentes se não mencionássemos que o Mockito inclui uma série de operadores correspondentes. Veja
o seguinte caso de teste, onde estamos testando a capacidade de criar novas entradas de vídeo:
@Teste
void criandoANewVideoShouldReturnTheSameData() {
// dado
dado(repository.saveAndFlush(any(VideoEntity.class)))
.willReturn(new VideoEntity("alice", "nome", "des"));
// quando
VideoEntity newVideo = serviço.criar
(new NewVideo("nome", "des"), "alice");
// então
assertThat(newVideo.getName()).isEqualTo("nome");
assertThat(newVideo.getDescription()).isEqualTo("des");
assertThat(newVideo.getUsername()).isEqualTo("alice");
}
• dado(): Este método de teste usa o operador BDDMockito.given do Mockito, um sinônimo do operador
quando() do Mockito
A classe BDDMockito do Mockito também possui um operador then() que poderíamos ter usado em vez das
asserções. Isso depende se estamos testando dados ou comportamento!
Embora o BDDMockito forneça boas alternativas, é mais fácil (pelo menos para mim) simplesmente usar os mesmos
operadores em todos os lugares. Se estamos fazendo stub ou zombando depende do caso de teste.
Confira o seguinte caso de teste final, onde estamos verificando a operação de exclusão do nosso serviço:
@Teste
void deletandoAVideoShouldWork() {
// dado
Entidade VideoEntity = new VideoEntity("alice", "nome",
"desc");
entidade.setId(1L);
quando(repositório.findById(1L))
.thenReturn(Opcional.de(entidade));
// quando
serviço.delete(1L);
// então
verificar(repositório).findById(1L);
verificar(repositório).delete(entidade);
}
Este método de teste tem algumas diferenças importantes em relação aos anteriores:
• quando(): Como o operador dado() do Mockito é apenas um sinônimo, é mais fácil usar o mesmo
operador when() em todos os lugares.
Machine Translated by Google
• Verify(): Como o comportamento do serviço é mais complexo, os dados predefinidos não funcionarão.
Em vez disso, devemos passar a verificar os métodos invocados dentro do serviço.
Deve-se ressaltar que livros inteiros foram escritos sobre Mockito. Meu amigo, Ken Kousen, escreveu recentemente Mockito Made Clear
(veja https://springbootlearning.com/mockito-book), que eu recomendo para um mergulho mais profundo.
Estamos apenas arranhando a superfície da sofisticação que este kit de ferramentas nos proporciona. Basta dizer que
capturamos uma quantidade apreciável da API VideoService por meio de cenários de teste legíveis.
No entanto, uma coisa é fundamental em tudo isso: esses casos de teste são baseados em unidades. E isso vem com certas
limitações. Para aumentar nossa confiança, expandiremos nosso alcance de testes por meio do uso de um banco de dados
na memória na próxima seção.
Existem empresas com equipes de engenheiros de teste cujo único trabalho é escrever esses documentos de teste, atualizá-
los à medida que as alterações são implementadas e executá-los em aplicativos em laboratórios de teste independentes.
Imagine esperar uma semana para que seu novo recurso seja verificado por este regime.
Os testes automatizados trouxeram uma nova onda de capacitação para os desenvolvedores. Eles podem capturar casos de teste que
descrevem o cenário que almejavam. Mesmo assim, os desenvolvedores ainda se depararam com o problema de conversar com um verdadeiro
banco de dados (porque vamos encarar os fatos – os testes não são reais, a menos que você esteja falando com um banco de dados físico) até
que as pessoas começaram a desenvolver bancos de dados que pudessem falar SQL, mas rodar localmente e na memória.
Os sistemas de banco de dados de nível de produção são executados na memória. Os servidores são especificados com grandes
quantidades de memória e espaço em disco para suportar um servidor de banco de dados. Mas não é disso que estamos falando.
Um banco de dados na memória referente ao seu aplicativo é um banco de dados executado no mesmo
espaço de memória que o seu aplicativo.
Existem algumas opções. Nesta seção, usaremos o banco de dados HyperSQL (HSQLDB).
Podemos escolher isso no Spring Initializr em https://start.spring.io e adicioná-lo ao nosso projeto de
construção com as seguintes coordenadas Maven:
<dependência>
<groupId>org.hsqldb</groupId>
Machine Translated by Google
<artifactId>hsqldb</artifactId> <scope>tempo de
execução</scope> </dependency>
Essa dependência tem um aspecto fundamental: é de tempo de execução, o que significa que nada em nosso código
precisa ser compilado nela. Só é necessário quando o aplicativo é executado.
Agora, para testar o VideoRepository, que construímos no Capítulo 3, Consultando dados com Spring Boot, crie a
classe VideoRepositoryHsqlTest abaixo de src/test/java, no pacote relacionado:
@DataJpaTest
classe pública VideoRepositoryHsqlTest {
@BeforeEach
void setUp()
{ repositório.saveAll( // List.of( // new
VideoEntity( //
"alice", // "Precisa de ajuda com
seu aplicativo
SPRING BOOT 3?", // "SPRING BOOT 3 só irá acelerar as coisas acima."),
• @DataJpaTest: Esta é a anotação de teste do Spring Boot e indica que queremos que ele execute toda
a sua varredura automatizada de definições de classe de entidade e repositórios Spring Data JPA.
Machine Translated by Google
• @BeforeEach: Esta é a anotação do JUnit 5 e garante que este método seja executado antes de cada
método de teste.
Com esta configuração, podemos começar a elaborar métodos de teste para exercitar nossos vários métodos
de repositório. Agora, é importante entender que não estamos focados em confirmar se o Spring Data JPA
funciona ou não. Isso implicaria que estamos a verificar o quadro, uma tarefa fora do nosso âmbito.
Não – precisamos verificar se escrevemos as consultas corretas, seja usando localizadores personalizados,
consulta por exemplo ou quaisquer outras estratégias que desejamos aproveitar.
@Teste
void findAllShouldProduceAllVideos() {
List<VideoEntity> vídeos = repositório.findAll();
assertThat(vídeos).hasSize(3);
}
• Utilizando AssertJ, verifica o tamanho dos resultados. Poderíamos nos aprofundar um pouco mais nas afirmações
(faremos isso mais tarde nesta seção!).
Fica como exercício expandir este método de teste (depois de ler mais abaixo) para verificar os dados de
forma abrangente.
Parte do nosso recurso de pesquisa era fazer uma verificação sem distinção entre maiúsculas e minúsculas em um vídeo. Escreva um teste para isso assim:
@Teste
void findByNameShouldRetrieveOneEntry() {
List<VideoEntity> vídeos = repositório //
.findByNameContainsIgnoreCase("SpRinG boot 3");
assertThat(vídeos).hasSize(1);
assertThat(vídeos).extracting(VideoEntity::getName) //
.containsExactlyInAnyOrder( //
"Precisa de ajuda com seu aplicativo SPRING BOOT 3?");
}
Machine Translated by Google
Este método de teste é mais extenso e pode ser explicado da seguinte forma:
• Observe como o nome do método de teste nos dá uma ideia do que ele faz?
• Usando o operador extracting() do AssertJ e uma referência de método Java 8 , podemos extrair o
campo de nome de cada entrada.
Uma pergunta que você pode ter é: por que não estamos fazendo afirmações contra objetos VideoEntity?
Afinal, os registros Java 17 tornam muito simples instanciar instâncias deles.
A razão para evitar isso em um caso de teste, especialmente aquele que se comunica com um banco de dados
real, é que o campo id é preenchido pela operação saveAll() no método setUp(). Embora possamos debater
maneiras de lidar dinamicamente com isso entre setUp() e um determinado método de teste, não é crítico que
trabalhemos para confirmar as chaves primárias.
Em vez disso, concentre-se em tentar verificar as coisas da perspectiva do aplicativo. Nessa situação,
queremos saber se nossa entrada parcial mista produz o vídeo correto e verificar se o campo de nome se
ajusta perfeitamente.
Outro teste que podemos escrever é confirmar se a pesquisa por nome ou descrição funciona. Então, adicione o
seguinte método de teste:
@Teste
void findByNameOrDescriptionShouldFindTwo() {
List<VideoEntity> vídeos = repositório //
.findByNameContainsOrDescriptionContainsAllIgnoreCase (
"Código", "SEU CÓDIGO");
assertThat(vídeos).hasSize(2);
assertThat(vídeos) //
.extracting(VideoEntity::getDescription) //
.contains("Como desenvolvedor profissional, NUNCA faça isso
ao seu código.", //
"Descubra maneiras de não apenas depurar seu código");
}
Machine Translated by Google
• Novamente, afirmar o tamanho dos resultados é um teste fácil para verificar se estamos no caminho certo.
• Desta vez, estamos usando o operador extracting() para buscar o campo de descrição.
• Simplesmente verificamos se o operador de extração contém algumas descrições sem nos preocuparmos com a ordem.
É importante lembrar que, sem a cláusula ORDER BY, os bancos de dados não são obrigados a retornar os resultados
na mesma ordem em que foram armazenados.
Uma coisa deve ser destacada: esta classe de teste usou injeção de campo para conectar automaticamente o VideoRepository.
Em aplicativos Spring modernos, geralmente é recomendado usar injeção de construtor. Vimos isso com mais
detalhes no Capítulo 2, Criando um aplicativo Web com Spring Boot, na seção Injetando dependências por meio
de chamadas de construtor.
Embora a injeção de campo seja geralmente vista como um risco que pode levar a exceções de ponteiro nulo, quando se trata
de classes de teste, tudo bem. Isso ocorre porque o ciclo de vida para criar e destruir classes de teste é controlado pelo JUnit e
nem por nós nem pelo Spring Framework.
Agora, ainda há um método de repositório que ainda precisamos testar, que é a operação delete().
Abordaremos isso mais adiante neste capítulo, quando explorarmos o teste de políticas de segurança com o Spring Security Test.
Enquanto isso, devemos abordar uma questão crítica que ainda está diante de nós: e se nosso banco de dados de destino não
estiver incorporado?
Se usarmos algo mais convencional na produção, como PostgreSQL, MySQL, MariaDB, Oracle ou algum outro banco de dados
relacional, teremos que lidar com o fato de que eles não estão disponíveis como processos incorporados e co-localizados.
Poderíamos continuar usando HSQL como base para escrever casos de teste. E mesmo usando JPA como padrão,
ainda corremos o risco de nossas operações SQL não funcionarem corretamente quando chegarmos à produção.
Embora SQL seja um padrão (ou melhor, vários padrões), existem lacunas não cobertas pelas especificações.
E cada mecanismo de banco de dados preenche essas lacunas com suas soluções, além de oferecer recursos fora das especificações.
Isso nos leva à necessidade de escrever casos de teste contra, digamos, PostgreSQL, mas não sermos capazes de usar o que
vimos até agora. E isso nos leva à próxima seção.
O fato de cada mecanismo de banco de dados ter pequenas variações nas implementações de SQL exige que testemos
nossas operações de banco de dados em relação à mesma versão que pretendemos usar na produção!
Com o surgimento do Docker em 2013 e a crescente colocação de diversas ferramentas e aplicações dentro de
containers, tornou-se possível encontrar um container para o banco de dados que buscamos.
Cultivado ainda mais pelo código aberto, quase todos os bancos de dados que podemos encontrar possuem uma versão em contêiner.
Embora isso nos permita ativar uma instância em nossa estação de trabalho local, a tarefa de iniciar manualmente
um banco de dados local sempre que quisermos executar nossos testes não é suficiente.
Digite Testcontainers. Com seu primeiro lançamento lançado em 2015, Testcontainers fornece um mecanismo para
iniciar um contêiner de banco de dados, invocar uma série de casos de teste e, em seguida, encerrar o contêiner.
Tudo sem nenhuma ação manual de você ou de mim.
Para adicionar Testcontainers a qualquer aplicativo Spring Boot, novamente, só precisamos visitar Spring
Initializr em start.spring.io. A partir daí, podemos selecionar Testcontainers, bem como PostgreSQL Driver.
As alterações a serem adicionadas ao nosso arquivo de compilação pom.xml são mostradas aqui:
<testcontainers.version>1.17.6</testcontainers.version>
<dependência>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>tempo de execução</scope>
</dependency>
<dependência>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>teste</scope>
</dependency>
<dependência>
<groupId>org.testcontainers</groupId>
<artifactId>junito-júpiter</artifactId>
<scope>teste</scope>
</dependency>
Machine Translated by Google
• org.postgresql:postgresql: Uma biblioteca de terceiros gerenciada pelo Spring Boot. Este é o driver para
conectar-se a um banco de dados PostgreSQL; portanto, ele só precisa ter escopo de tempo de execução.
Não há nada em nossa base de código que deva ser compilado.
É importante entender que Testcontainers envolve uma frota de vários módulos, todos gerenciados sob a
égide dos repositórios GitHub. Eles fazem isso lançando uma lista de materiais (BOM) do Maven, um
artefato central que contém todas as versões.
A propriedade testcontainers.version especifica qual versão do Testcontainers BOM desejamos usar, que é
adicionada ao arquivo pom.xml em uma seção separada abaixo de <dependencies/>, conforme mostrado aqui:
<gerenciamento de dependência>
<dependências>
<dependência>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<versão>${testcontainers.versão}</versão>
<type>pom</type>
<scope>importar</scope>
</dependency>
</dependências>
</dependencyManagement>
• pom: Um tipo de dependência que indica que este artefato não possui código, apenas informações de construção do Maven.
• importação: Um escopo indicando que esta dependência deve ser substituída efetivamente por qualquer
esta lista técnica contém. É um atalho para adicionar uma pilha de versões declaradas.
Com tudo isso configurado, podemos escrever alguns casos de teste na próxima seção!
Machine Translated by Google
@Testcontainers
@DataJpaTest
@AutoConfigureTestDatabase(substituir = Substituir.NONE)
classe pública VideoRepositoryTestcontainersTest {
@Container //
final estático PostgreSQLContainer<?> banco de dados = //
novo PostgreSQLContainer<>("postgres:9.6.12") //
.withUsername("postgres");
}
Esta estrutura para casos de teste pode ser descrita da seguinte forma:
• @DataJpaTest: anotação do Spring Boot Test que usamos na seção anterior, indicando
que todas as classes de entidade e repositórios Spring Data JPA devem ser verificados.
• @AutoConfigureTestDatabase: Esta anotação do Spring Boot Test informa ao Spring Boot que, em vez
de trocar o bean DataSource como normalmente faz, NÃO substituí-lo como normalmente faz quando
há um banco de dados incorporado no caminho de classe (mais sobre por que isso é necessário em
breve ).
• @Container: anotação do Testcontainer para sinalizar este como o contêiner a ser controlado através do
Ciclo de vida JUnit.
• PostgreSQLContainer: Cria uma instância do Postgres através do Docker. A string do construtor especifica
as coordenadas do Docker Hub da imagem exata que desejamos. Observe que isso facilita a criação
de várias classes de teste, cada uma focada em versões diferentes do Postgres!
Tudo isso nos permite criar uma instância real do Postgres e aproveitá-la a partir de uma classe de teste. As
anotações extras unem as ações de início e parada do Docker com as ações de início e parada do nosso cenário de teste.
Machine Translated by Google
Spring Boot, com sua magia de autoconfiguração, cria um bean DataSource real ou um bean incorporado.
Se detectar H2 ou HSQL no caminho de classe de teste, ele passará a usar o banco de dados incorporado.
Caso contrário, ele possui algumas configurações padrão de configuração automática com base no driver JDBC que ele vê.
E nenhuma das situações é o que queremos. Queremos que ele abandone H2 e HSQL e use Postgres.
Mas o nome do host e a porta estarão errados, pois este não é um Postgres independente, mas sim um
Postgres baseado em Docker.
Não tenha medo, ApplicationContextInitializer vem para o resgate. Esta é a classe Spring Framework
que nos dá acesso ao ciclo de vida de inicialização da aplicação, conforme mostrado aqui:
TestPropertySourceUtils.
addInlinedPropertiesToEnvironment(applicationContext,
"spring.datasource.url=" + banco de dados.getJdbcUrl(),
"spring.datasource.username="+database.getUsername(),
"spring.datasource.password="+database.getPassword(),
"spring.jpa.hibernate.ddl-auto=create-drop");
}
}
• ApplicationContextInitializer<ConfigurableApplicationContext>:
Essa classe é o que nos dá uma ideia do contexto do aplicativo.
• inicializar(): Este método é o retorno de chamada que o Spring invocará enquanto o contexto do aplicativo
está sendo criado.
Para aplicar esse conjunto de propriedades conectando o banco de dados gerenciado por Testcontainers ao
DataSource autoconfigurado do Spring Boot, precisamos simplesmente adicionar o seguinte à classe de teste:
@ContextConfiguration(inicializadores = DataSourceInitializer.
aula)
Como cada método de teste começa com um banco de dados limpo, precisamos pré-carregar algum conteúdo, conforme mostrado aqui:
@BeforeEach
void configuração() {
repositório.saveAll( //
Lista de( //
nova VideoEntity( //
"Alice", //
"Precisa de ajuda com seu aplicativo SPRING BOOT 3?", //
"SPRING BOOT 3 só vai acelerar as coisas."),
new VideoEntity("Alice", //
"Não faça ISSO com seu próprio CÓDIGO!", //
"Como desenvolvedor profissional, NUNCA faça isso com
seu código."),
new VideoEntity("bob", //
"SEGREDOS para consertar CÓDIGO QUEBRADO!", //
• @BeforeEach: A anotação JUnit que executa esse código antes de cada método de teste.
• List.of(): Um operador Java 17 para montar uma lista de forma rápida e fácil.
E se precisarmos testar diferentes conjuntos de dados? Diferentes cenários baseados em dados? Escreva outra aula de teste!
Você pode usar Testcontainers facilmente entre diferentes classes de teste. Ao integrar-se firmemente ao JUnit, não há
necessidade de se preocupar com alguma instância estática flutuando em uma classe de teste, destruindo essa classe de teste.
Agora, com tudo isso configurado, podemos finalmente escrever alguns testes, conforme mostrado aqui:
@Teste
void findAllShouldProduceAllVideos() {
List<VideoEntity> vídeos = repositório.findAll();
assertThat(vídeos).hasSize(3);
}
Este método de teste verifica se o método findAll() retorna todas as três entidades armazenadas no banco de dados.
Considerando que findAll() é fornecido pelo Spring Data JPA, isso beira o teste do Spring Data JPA e não do
nosso código. Mas às vezes precisamos desse tipo de teste para simplesmente verificar se configuramos tudo
corretamente.
Às vezes, isso também é chamado de teste de fumaça, um caso de teste que verifica se as coisas estão funcionando e funcionando.
Um caso de teste mais aprofundado envolve provar que nosso localizador personalizado que suporta nosso recurso de pesquisa está
funcionando, conforme mostrado aqui:
@Teste
void findByName() {
List<VideoEntity> vídeos = repositório.
findByNameContainsIgnoreCase("SPRING BOOT 3");
assertThat(vídeos).hasSize(1);
}
Este método de teste possui as mesmas anotações e anotações AssertJ, mas se concentra no mesmo
findByNameContainsIgnoreCase, usando dados armazenados em um banco de dados.
Machine Translated by Google
Para finalizar, vamos verificar nosso localizador personalizado superlongo com um caso de teste, conforme mostrado aqui:
@Teste
void findByNameOrDescription() {
List<VideoEntity> vídeos = repositório.
findByNameContainsOrDescriptionContainsAllIgnoreCase
("CÓDIGO", "seu código");
assertThat(vídeos).hasSize(2);
}
Caramba! O nome do método é tão longo que prejudica a formatação deste livro. Isso pode ser um sinal
de que este cenário anseia pela consulta por exemplo. É hora de voltar ao Capítulo 3, Consultando
dados com Spring Boot, e considerar substituir essa consulta, talvez?
Com tudo isso implementado, podemos executar nosso conjunto de testes e saber com segurança que nosso repositório de dados
interage adequadamente com o banco de dados. Não apenas sabemos que nosso repositório está fazendo as coisas certas, mas
nossos métodos de teste também têm como objetivo garantir que nosso sistema esteja fazendo a coisa certa.
Esses testes verificam se nosso design de consultas que não diferenciam maiúsculas de minúsculas em vários campos oferece
suporte à camada de serviço anterior:
E embora esta seção tenha se concentrado em um repositório conectado a um banco de dados, essa tática funciona
em muitos outros lugares – RabbitMQ, Apache Kafka, Redis, Hazelcast, qualquer coisa. Se você encontrar uma
imagem do Docker Hub, poderá conectá-la ao seu código por meio de Testcontainers. Às vezes, há anotações de atalho.
Outras vezes, você só precisa criar o contêiner como acabamos de fazer.
Tendo verificado nosso controlador web, nossa camada de serviço e agora nossa camada de repositório, só há uma
coisa a resolver: verificar nossa política de segurança.
Sim e não.
Machine Translated by Google
Usamos a anotação @WithMockUser do Spring Security Test anteriormente neste capítulo. Mas isso
ocorre porque qualquer classe de teste anotada por @WebMvcTest terá, por padrão, nossas políticas
Spring Security em vigor.
Mas não cobrimos todos os caminhos de segurança necessários. E em segurança, muitas vezes há muitos caminhos a
percorrer. E à medida que cavamos, descobriremos exatamente o que isso significa.
Para começar, precisamos de uma nova classe de teste, conforme mostrado aqui:
• @WebMvcTest: esta anotação Spring Boot Test indica que esta é uma classe de teste baseada na
web focada em HomeController. É importante entender que as políticas do Spring Security estarão
em vigor.
• @Autowired MockMvc: injeta automaticamente uma instância Spring MockMVC para criarmos
casos de teste.
Com isso implementado, podemos começar verificando o acesso à página inicial. Neste contexto, faz
sentido inspecionar nosso SecurityConfig:
http.authorizeHttpRequests() //
.requestMatchers("/login").permitAll() //
.requestMatchers("/", "/search").authenticated() //
.requestMatchers(HttpMethod.GET, "/api/**").
autenticado()
.requestMatchers(HttpMethod.POST, "/delete/**",
"/novo-vídeo").autenticado() //
.anyRequest().denyAll() //
.e() //
.formLogin() //
Machine Translated by Google
.e() //
.httpBásico();
Esta lista de regras de segurança tem uma regra em negrito no topo. Indica que o acesso a/requer
acesso autenticado e nada mais.
Para verificar se usuários não autenticados têm acesso negado, escreva o seguinte caso de teste:
@Teste
• NÃO possui uma daquelas anotações @WithMockUser. Isso significa que não há autenticação
as credenciais são armazenadas no contexto do servlet, simulando assim um usuário não autorizado.
Acabamos de escrever um caso de teste de caminho incorreto, um requisito crítico ao testar políticas de segurança. Precisamos também
@Teste
.perform(get("/")) //
.andExpect(status().isOk());
}
Agora completamos nosso regime de testes para verificar se a página inicial está devidamente bloqueada.
No entanto, usuários não autenticados e usuários ROLE_USER não são os únicos usuários que nosso sistema possui. Também
temos administradores com ROLE_ADMIN. E para cada função, realmente deveríamos ter um teste separado para garantir que
nossa política de segurança esteja configurada corretamente.
@Teste
A única diferença é que @WithMockUser possui alice e ROLE_ADMIN armazenados no contexto do servlet.
Considerando que nosso HomeController também nos permite adicionar novos objetos de vídeo, também devemos
escrever alguns métodos de teste para garantir que tudo seja tratado corretamente, conforme mostrado aqui:
@Teste
• O nome do método descreve claramente que é para verificar se um usuário não autorizado NÃO cria
um novo vídeo. Novamente, este método não possui anotação @WithMockUser.
• with(csrf()): Temos proteções CSRF habilitadas. Essa configuração adicional nos permite conectar
o valor CSRF, simulando uma tentativa legítima de acesso.
Se você fornecer todos os valores esperados, incluindo um token CSRF válido, ocorrerá uma falha, conforme esperado.
CSRF
No Capítulo 4, Protegendo um aplicativo com Spring Boot, descobrimos que o Spring Security ativa
automaticamente a verificação de token CSRF em formulários e outras ações para evitar ataques CSRF.
Para casos de teste em que não temos tokens CSRF renderizados em páginas HTML, ainda devemos apresentar
esse valor para evitar o desligamento do CSRF.
Agora, vamos escrever um teste onde o usuário tenha as permissões corretas para criar um novo vídeo:
@Teste
Resumo 151
• status().is3xxRedirection(): Verifica se obtemos algo na série 300 de sinais de resposta HTTP. Isso torna nosso caso
de teste menos frágil se, digamos, alguém mudar de redirecionamentos suaves para redirecionamentos físicos no
futuro.
Este método de teste é idêntico ao método de teste anterior. A única diferença é a configuração (alice/ROLE_USER) e os
resultados (redirecionar para /).
E é isso que torna esses métodos de teste focados na segurança. O objetivo aqui é ver que acessar os mesmos endpoints,
mas com credenciais diferentes (ou nenhuma) produz resultados adequados.
Graças ao MockMVC e ao Spring Security Test, é fácil exercitar o mecanismo Spring MVC e se defender dele. E graças ao
Spring Boot Test, é muito fácil ativar partes reais de nosso aplicativo, novamente aumentando a confiança.
Resumo
Ao longo deste capítulo, exploramos diversas maneiras de escrever casos de teste. Vimos testes simples, testes
intermediários e testes complexos. Tudo isso nos dá maneiras de testar diferentes aspectos de nossa aplicação.
E cada tática tem várias vantagens e desvantagens. Podemos obter mecanismos de banco de dados reais se estivermos dispostos a gastar
o tempo de execução extra. Também podemos garantir que nossas políticas de segurança sejam devidamente alteradas tanto com usuários
Esperamos que isso tenha despertado seu apetite para adotar totalmente os testes em seus aplicativos.
No próximo capítulo, Configurando um aplicativo com Spring Boot, aprenderemos como parametrizar, configurar e substituir
configurações de nosso aplicativo.
Parte 3:
Liberando um aplicativo
com Spring Boot
Construir um aplicativo é apenas metade da batalha. Liberar o aplicativo para produção é fundamental. Você
aprenderá como configurar seu aplicativo para vários ambientes, incluindo a nuvem. Você também descobrirá
diferentes maneiras de empacotá-lo e colocá-lo nas mãos de seus clientes. Por fim, você aprenderá como
acelerar as coisas com imagens nativas.
6
Configurando um aplicativo
com Spring Boot
No capítulo anterior, aprendemos como testar vários aspectos de uma aplicação, incluindo controladores
web, repositórios e objetos de domínio. Também exploramos testes de caminho de segurança, bem como o
uso de Testcontainers para emular a produção.
Neste capítulo, aprenderemos como configurar nosso aplicativo, que é uma parte crítica do desenvolvimento de aplicativos. Embora à primeira
vista isso possa parecer como definir um punhado de propriedades, há um conceito mais profundo em jogo.
Nosso código precisa de uma conexão com o mundo real. Nesse sentido, estamos falando de qualquer coisa a que nossa aplicação se conecte:
bancos de dados, corretores de mensagens, sistemas de autenticação, serviços externos e muito mais. Os detalhes necessários para apontar
nosso aplicativo para um determinado banco de dados ou agente de mensagens estão contidos nessas configurações de propriedade. Ao tornar
a configuração do aplicativo um cidadão de primeira classe no Spring Boot, a implantação do aplicativo se torna versátil.
O objetivo deste capítulo é revelar como a configuração de aplicativos se torna não apenas simples, mas, em vez disso, uma ferramenta para
fazer com que os aplicativos atendam melhor às nossas necessidades. Dessa forma, podemos dedicar todo o nosso tempo atendendo às
necessidades da aplicação!
Configurar nosso aplicativo usando arquivos de propriedades é extremamente útil. Embora o Spring Boot ofereça muitas
propriedades personalizadas que podemos usar, é possível criar as nossas próprias!
Vamos começar criando algumas propriedades personalizadas. Para fazer isso, crie uma nova classe
AppConfig, assim:
@ConfigurationProperties("app.config")
registro público AppConfig (cabeçalho de string, introdução de string,
Lista<UserAccount> usuários) {
}
• @ConfigurationProperties: uma anotação Spring Boot que sinaliza esse registro como uma fonte de
configurações de propriedade. O valor app.config é o prefixo de suas propriedades.
• AppConfig: o nome deste pacote de propriedades de configuração com segurança de tipo. Não importa
o nome que lhe dermos.
Os próprios campos são os nomes das propriedades, que abordaremos com mais detalhes em breve.
app.config.users[1].authorities[0]=ROLE_USER
app.config.users[2].username=admin
app.config.users[2].password=senha
app.config.users[2].authorities[0]=ROLE_ADMIN
• app.config.header: Um valor de string para inserir na parte superior do nosso modelo (o que
faremos em breve).
Essas configurações de propriedade são legais, mas ainda não temos acesso a elas. Precisamos habilitá-los
adicionando um pouco mais. Na verdade, não importa onde, desde que esteja em um componente Spring que
sabemos que será detectado pela verificação de componentes do Spring Boot.
Como esse conjunto de propriedades (você pode ter mais de uma!) abrange toda a aplicação, por que não aplicá-
las ao ponto de entrada da nossa aplicação?
@SpringBootApplication
@EnableConfigurationProperties(AppConfig.class)
classe pública Capítulo6Aplicativo {
public static void main(String[] args) {
SpringApplication.run(Chapter6Application.class, args);
}
}
Este código é muito semelhante ao código dos capítulos anteriores, com uma alteração:
Dica
Qual é o melhor lugar para ativar propriedades personalizadas? Na verdade, isso não importa. Contanto
que seja habilitado em algum bean Spring que será obtido pelo Spring Boot, ele será adicionado ao
contexto do aplicativo. Porém, se este determinado conjunto de propriedades for específico de um
determinado bean Spring, é recomendável colocar a anotação nessa definição de bean, enfatizando
que o bean vem com um complemento de propriedades configuráveis. Se as propriedades forem
usadas por mais de um bean, considere o que acabamos de fazer.
Machine Translated by Google
Para aproveitá-lo em nosso HomeController, precisamos apenas fazer as seguintes alterações no topo
da classe:
@Controlador
HomeController tem uma alteração: um campo do tipo AppConfig é declarado abaixo de VideoService
e é inicializado na chamada do construtor.
Com isso implementado, podemos usar os valores fornecidos para renderizar o modelo de índice mais abaixo em
HomeController, assim:
@GetMapping
public String index(Model model, // Autenticação de
autenticação) { model.addAttribute("videos",
videoService.getVideos());
model.addAttribute("autenticação", autenticação);
model.addAttribute("cabeçalho", appConfig.header()); model.addAttribute("introdução",
appConfig.intro()); retornar "índice";
Isso pegará os valores de string que colocamos em application.properties e os encaminhará para que
renderizem index.mustache.
<h1>{{cabeçalho}}</h1>
<p>{{enter}}</p>
Simplesmente usamos a sintaxe dupla do Moustache para capturar os atributos {{header}} e {{intro}}.
O que costumava ser codificado agora é uma variável de modelo!
Isso é legal, mas parametrizar alguns valores fixos em um modelo é tão importante? Talvez não. Para
obter o poder das propriedades de configuração do Spring Boot, vamos nos aprofundar no campo de usuários.
É importante entender que os campos de propriedade Java são fundamentalmente construídos a partir de
pares de strings de valores-chave. Os valores não estão entre aspas duplas, mas são tratados dessa forma.
Spring vem com alguns conversores convenientes integrados, mas centralizado na propriedade do
usuário AppConfig, dentro do tipo UserAccount, está um List<GrantedAuthority>. A conversão de strings
em GrantedAuthority não é clara e requer que escrevamos e registremos um conversor.
O código para lidar com contas de usuário é focado na segurança, então faz sentido registrar este
conversor personalizado dentro do nosso SecurityConfig já existente:
@Feijão
@ConfigurationPropertiesBinding
Converter<String, GrantedAuthority> conversor() {
retornar novo Converter<String, GrantedAuthority>() {
@Sobrepor
• @Bean: Este conversor deve ser registrado no contexto da aplicação para que seja escolhido
corretamente.
• Convert(): No centro está um pequeno método que converte uma String em um GrantedAuthority usando
SimpleGrantedAuthority.
Conversores de mola são bastante úteis. No entanto, você pode ser incentivado pelo seu IDE
a simplificar um pouco esse código. Pode sugerir que você converta isso em uma expressão
lambda Java (return source -> new SimpleGrantedAuthority(source)), ou talvez reduza-a em
uma referência de método (return SimpleGrantedAuthority::new).
Mas isso não vai funcionar. Pelo menos, não sem alguma ajuda.
Isso ocorre porque Java possui apagamento de tipo. Em tempo de execução, o Java descartará as informações
genéricas, o que impossibilitará o Spring de encontrar o conversor adequado e aplicá-lo. Portanto, devemos manter
as coisas como estão ou adotar uma estratégia diferente, como mostrado aqui:
@ConfigurationPropertiesBinding
Conversor GrantedAuthorityCnv() {
retornar SimpleGrantedAuthority::new;
}
• GrantedAuthorityCnv: Ao estender a interface Converter do Spring com uma interface personalizada que aplica
nossos parâmetros genéricos, estamos congelando uma cópia desses parâmetros que
A primavera pode encontrar e usar
Isso pode parecer ser aproximadamente a mesma quantidade de código. É simplesmente uma maneira diferente
de reunir as mesmas informações. É uma preferência pessoal se um Converter<String, GrantedAuthority>
totalmente expandido dentro de uma definição de bean ou uma interface reduzida, mas separada, é mais fácil de
compreender.
De qualquer forma, agora podemos iniciar nosso aplicativo, sabendo que temos vários aspectos de nosso aplicativo reformulados
para serem orientados por propriedades.
E com tudo isso implementado, podemos começar a explorar como um aplicativo baseado em propriedades nos dá
a capacidade de personalizar coisas para diferentes ambientes na próxima seção.
Machine Translated by Google
Na seção anterior, percebemos a capacidade de extrair certos aspectos de nossa aplicação em arquivos de
propriedades. O próximo grande passo que você pode dar é perceber até onde você pode ir com isso.
Inevitavelmente, nos deparamos com situações como levar nossa aplicação para um novo ambiente e nos
perguntar: “podemos alterar as propriedades para ESTA situação?”
Por exemplo, e se nosso aplicativo, antes do lançamento, tiver que ser instalado em uma plataforma de teste onde possa ser
verificado? Os bancos de dados são diferentes. A equipe de teste pode querer um conjunto diferente de contas de teste. E
quaisquer outros serviços externos (corretores de mensagens, sistemas de autenticação e assim por diante) provavelmente
também serão diferentes.
Então, surge a pergunta: “posso ter um conjunto diferente de propriedades?” Ao que Spring Boot diz “sim!”
app.config.users[0].username=test1
app.config.users[0].password=senha
app.config.users[0].authorities[0]=ROLE_NOTHING
app.config.users[1].username=test2
app.config.users[1].password=senha
app.config.users[1].authorities[0]=ROLE_USER
app.config.users[2].username=test3
app.config.users[2].password=senha
app.config.users[2].authorities[0]=ROLE_ADMIN
• A equipe de teste provavelmente teria um conjunto de usuários que deseja realizar em todos os seus cenários
Para executar a aplicação, basta ativar o perfil de teste Spring. Existem várias maneiras de mudar
isso:
• Alguns IDEs até suportam isso diretamente! Confira a seguinte captura de tela do IntelliJ
IDEA (Ultimate Edition, não Community Edition):
Olhe na parte inferior para perfis ativos e veja onde entramos no teste.
Quando ativamos um perfil Spring, o Spring Boot adicionará application-test.properties à nossa configuração.
Dica
Imagine se tivéssemos A) um laboratório de desenvolvimento onde tivéssemos um conjunto reduzido de servidores, B) uma
plataforma de testes de tamanho completo com um conjunto separado de servidores e C) um ambiente de produção com servidores
de tamanho completo. Poderíamos considerar o seguinte:
• Torne a produção a configuração padrão e coloque as URLs de conexão apontadas para nossos servidores de produção em
application.properties. Além disso, tenha um perfil de teste equivalente capturado no arquivo application-test.properties
com URLs de conexão para os servidores de teste.
Por fim, faça com que os desenvolvedores individuais usem um perfil de desenvolvimento, usando o arquivo application-dev.
arquivo de propriedades com URLs de conexão para o laboratório de desenvolvimento de servidores.
• Outra táctica é inverter a produção e o desenvolvimento. Quando alguém executa o aplicativo por padrão, sem perfis
personalizados ativados, ele é executado no modo dev. Para executar coisas em produção, ative o perfil de produção e
aplique application-production.properties.
Mantenha o perfil de teste igual ao exemplo anterior.
Até agora, falamos sobre diferentes ambientes e ambientes físicos reais. Claro, poderíamos estar discutindo
servidores tradicionais em diversas configurações de rack. Mas poderíamos muito bem estar falando de
servidores virtualizados. De qualquer forma, é valioso ajustar URLs de conexão entre outras configurações
por meio do uso de arquivos de configuração específicos do ambiente.
E se nosso aplicativo estiver sendo implantado na nuvem? Em nuvens diferentes? E se começássemos com um conjunto tradicional
de hardware, conforme discutido anteriormente nesta seção, mas a administração decidisse migrar para AWS, Azure ou VMware
Tanzu?
Não há necessidade de entrar em nosso código e começar a fazer alterações. Em vez disso, tudo o que precisamos fazer é
trabalhar em um novo arquivo de propriedades e conectar quaisquer detalhes da conexão para nos comunicarmos com nossos
serviços baseados em nuvem!
Machine Translated by Google
E às vezes, o número de configurações de propriedades necessárias pode explodir. Com o paradigma chave/valor
do arquivo de propriedades, isso pode ficar complicado. Na seção anterior, onde tínhamos listas e valores
complexos, tornou-se complicado especificar valores de índice.
YAML é uma maneira mais sucinta de representar as mesmas configurações. Talvez um exemplo seja
adequado. Crie um arquivo application-alternate.yaml na pasta src/main/resources, conforme mostrado aqui:
aplicativo:
configuração:
- ROLE_USER
-
- ROLE_USER
-
- ROLE_ADMIN
• A natureza aninhada do YAML evita entradas duplicadas e deixa claro onde cada propriedade
está localizado
• Como as próprias autoridades são uma lista, ela também usa hífens
No Capítulo 4, Protegendo um aplicativo com Spring Boot, na seção Aproveitando o Google para autenticar usuários
seção, vimos em primeira mão o uso de YAML para definir as configurações do Spring Security OAuth2.
YAML é fino e elegante porque evita a duplicação de entradas. Além disso, sua natureza aninhada torna as
coisas um pouco mais legíveis.
Troca
Tudo tem compensações, certo? Já vi muitos sistemas construídos sobre arquivos de configuração
YAML, especialmente no espaço de configuração da nuvem. Se o seu arquivo YAML for tão grande que
não cabe em uma tela, o formato legível pode funcionar contra você. Isso ocorre porque o espaçamento
e a tabulação entre os níveis aninhados são importantes. O exemplo que acabei de dar é sobre algo não
específico do Spring Boot. O número de propriedades com as quais você estará lidando provavelmente
será adequado. Só não se surpreenda se você trabalhar em outra coisa e descobrir o lado negro do YAML.
Um recurso bônus fornecido pela maioria dos IDEs modernos é o suporte à conclusão de código para ambos os aplicativos.
Nesta captura de tela, embora as propriedades disponíveis apareçam com notação de chave/valor padrão, elas serão
aplicadas automaticamente no formato YAML, se necessário.
Spring Boot nos incentiva a usar classes de propriedades de configuração personalizadas e com segurança de tipo. Para
permitir que nossas configurações apareçam em pop-ups de conclusão de código como o mostrado anteriormente, tudo o
que precisamos fazer é adicionar a seguinte dependência ao nosso arquivo pom.xml!
<dependência>
<groupId>org.springframework.boot</groupId>
<artifactId>processador de configuração de inicialização de primavera
</artifactId>
<opcional>verdadeiro</opcional>
</dependency>
Mas esta não é a única maneira de fornecer configurações de propriedades. Na próxima seção, aprenderemos como variáveis de
ambiente também podem ser usadas para substituir configurações de propriedade.
Ficar preso a um aplicativo agrupado e não ter como substituir os vários arquivos de propriedades contidos nele
seria um empecilho.
Talvez você tenha se deparado com situações em que precisa descompactar o arquivo JAR, editar alguns
arquivos de propriedades e agrupá-lo novamente. Não faça isso! Este é um hack que pode ter surgido há
20 anos , mas simplesmente não funciona hoje. Na era atual de pipelines controlados e processos de
lançamento seguros, é simplesmente muito arriscado obter manualmente um arquivo JAR e ajustá-lo dessa forma.
E graças à experiência real da equipe Spring, não há necessidade de fazer isso.
Você pode substituir facilmente qualquer propriedade usando uma variável de ambiente. Lembra como, no início deste
capítulo, ativamos um perfil usando a caixa de diálogo Executar do IntelliJ IDEA? Você pode fazer a mesma coisa
diretamente na linha de comando, assim:
• alternativa: O perfil que estamos executando. Na verdade, na saída do console, você pode ver O
seguinte perfil 1 está ativo: “alternativo” como prova de que está sendo executado com esse perfil ativado.
• ./mvnw: Execute coisas usando o wrapper Maven. Esta é uma maneira prática de usar o Maven sem
ter que instalá-lo em seu sistema (muito útil para sistemas CI!).
Também é importante entender que as variáveis de ambiente, quando usadas desta forma, aplicam-se apenas ao
comando atual. Para tornar as variáveis de ambiente persistentes durante o shell atual, você deve exportar a
variável (que não abordaremos aqui).
E é fácil ativar vários perfis. Basta separar os nomes dos perfis com vírgulas, conforme mostrado aqui:
Isso é quase o mesmo que ativar um único perfil, exceto que os perfis de teste e alternativo foram ativados.
Considerando que os perfis de teste e alternativo definiram um conjunto diferente de usuários, talvez você esteja se
perguntando quais estão ativos. É simples – os arquivos de propriedades são aplicados da esquerda para a direita.
Como o perfil alternativo é o último, ele irá sobrepor quaisquer novas propriedades enquanto substitui quaisquer
duplicatas. Assim, são as contas baseadas em YAML que acabam sendo configuradas!
Essa não é a única regra quando se trata de substituições e opções de propriedade. Basta ler a próxima seção para entender
todas as opções que temos para ajustar as propriedades.
De volta ao Capítulo 1, Recursos principais do Spring Boot, obtivemos um resumo da ordem das configurações de propriedades.
• atributos de propriedades em seus testes. Disponível com a anotação @SpringBootTest e também testes baseados
em fatias (que abordamos no Capítulo 5, Testando com Spring Boot na seção Testando repositórios de dados com
Testcontainers)
Esta lista é ordenada da prioridade mais baixa para a prioridade mais alta. O arquivo application.properties é bastante baixo,
o que significa que oferece uma ótima maneira de definir uma linha de base para nossas propriedades, mas temos várias
maneiras de substituí-las em testes ou na implantação. Mais abaixo na lista estão todas as maneiras pelas quais podemos
substituir essa linha de base.
Isso mesmo. Podemos ter arquivos de propriedades do aplicativo adjacentes ao nosso arquivo JAR executável e fazer
com que eles sirvam como substituições. Lembra daquele aviso anterior para não abrir um JAR e ajustar suas propriedades?
Não há necessidade! Você pode simplesmente criar um novo arquivo de propriedades e aplicar quaisquer ajustes.
Aviso
Ainda existe algum risco em ajustar propriedades dinamicamente a partir da linha de comando. Sempre que fizer
isso, tome nota e considere capturar essas alterações em seu sistema de controle de versão, talvez como um perfil
diferente. Nada é pior do que encontrar uma solução e vê-la ser substituída por um patch que não captou suas
alterações.
Existe um conceito fundamental em engenharia de software que envolve essa ideia de dissociar o código da
configuração por meio do poder das configurações de propriedades. É conhecido como aplicativo de doze
fatores, um conceito que surgiu em 2011 do fornecedor de nuvem Heroku (agora propriedade da Salesforce).
Para ser mais específico, a configuração é o terceiro fator listado em https://12factor.net/.
Machine Translated by Google
Resumo 169
A ideia é externalizar tudo o que pode variar de ambiente para ambiente. Isso prolonga a vida útil de nosso aplicativo
quando projetamos com esse tipo de flexibilidade. Nossos exemplos de configurações de propriedades podem ter
sido um pouco artificiais, mas esperamos que você possa ver o valor de externalizar certos bits.
E com a capacidade do Spring Boot de reunir propriedades específicas do perfil, fica ainda mais fácil aplicar esse
recurso ao nosso código.
É discutível se todos os fatores dos aplicativos de doze fatores são relevantes hoje ou aplicáveis à nossa
próxima aplicação . Mas muitos dos fatores listados no site certamente contribuem para tornar nossos
aplicativos mais fáceis de implantar, vincular e empilhar, como blocos de construção para construir sistemas.
Então, eu consideraria lê-lo em seu tempo livre.
Resumo
Neste capítulo, aprendemos como externalizar partes do nosso sistema, desde o conteúdo que aparece na camada
web até a lista de usuários com permissão para autenticação no sistema. Vimos como criar classes de configuração
com segurança de tipo, inicializá-las a partir de arquivos de propriedades e, em seguida, injetá-las em partes de
nosso aplicativo. Vimos até como usar propriedades baseadas em perfil e escolher entre usar arquivos de
propriedades Java tradicionais ou usar YAML. Em seguida, exploramos ainda mais maneiras de substituir as
configurações de propriedades na linha de comando e verificamos uma lista abrangente de ainda mais maneiras de substituir propriedades.
Embora nossos exemplos provavelmente não tenham sido tão realistas quanto poderiam ter sido, o conceito está aí.
A externalização de propriedades que provavelmente variam de ambiente para ambiente é um recurso valioso, e o
Spring Boot facilita o uso desse padrão.
No próximo capítulo, Liberando um aplicativo com Spring Boot, finalmente lançaremos nosso aplicativo. Lá,
aprenderemos o que o Spring Boot faz não apenas para nos ajudar a realizar essa tarefa, mas também para
gerenciar nosso aplicativo no dia 2.
Machine Translated by Google
Machine Translated by Google
7
Liberando um aplicativo
com Spring Boot
No capítulo anterior, aprendemos sobre todas as diversas maneiras pelas quais podemos configurar nosso
aplicativo usando Spring Boot. Isso desbloqueou a capacidade de executar nosso aplicativo em vários ambientes,
o que o tornou mais flexível.
O local mais crítico para nossa aplicação é a produção. Caso contrário, não estamos fazendo o que nos propusemos
a fazer. A produção pode ser um lugar assustador. A equipe Spring, em sua experiência aguerrida, incorporou
muitos recursos ao Spring Boot para facilitar os processos envolvidos na montagem de aplicativos, preparação e,
por fim, gerenciamento deles depois de implantados.
Com base nas ferramentas abordadas no capítulo anterior, veremos como o Spring Boot pode transformar o que
costumava ser um lugar assustador em um ambiente próspero.
• Criando um superJAR
Criando um superJAR
Isso pode ou não parecer familiar, mas era uma vez, há muito tempo, os desenvolvedores compilavam seu código,
executavam scripts para montar os bits binários em arquivos ZIP e os arrastavam para aplicativos que resultariam
na gravação de um CD ou na preparação do arquivo. arquivo em algum artefato retrô, como uma unidade de fita
ou disco rígido jumbo.
Em seguida, eles arrastariam o artefato para outro local, fosse uma sala abobadada com controle de acesso especial
ou uma instalação totalmente diferente do outro lado da cidade.
Para ser honesto, parece algo saído de um filme de ficção científica pós-techno.
Mas a verdade é que o mundo da produção sempre esteve separado do mundo do desenvolvimento, quer estejamos
falando de cubículos com dezenas de codificadores em uma extremidade do edifício e a sala de servidores de
destino do outro lado da sala , ou se estivermos descrevendo uma start-up com cinco pessoas espalhadas pelo
mundo, implantando a solução baseada em nuvem da Amazon.
De qualquer forma, o local onde nossa aplicação deve residir para que os clientes possam acessá-la e o local onde
desenvolvemos são dois locais diferentes.
E o mais importante é minimizar todas as etapas necessárias para mover o código do nosso IDE para os servidores,
atendendo a solicitações da Web de todo o mundo.
É por isso que, desde o início de 2014, a equipe do Spring Boot desenvolveu uma ideia inovadora: construir um super JAR.
• limpar: Exclui essa pasta de destino e quaisquer outras saídas geradas. É sempre bom incluir isso antes de
construir um super JAR para garantir que todas as saídas geradas estejam atualizadas.
• pacote: invoca a fase de pacote do Maven, que causará as fases de verificação, compilação e teste
ser invocado na ordem correta.
Usando Windows?
O script mvnw funciona apenas em máquinas Mac ou Linux. Se você estiver no Windows, deverá ter um
ambiente shell comparável ou poderá usar ./mvnw.cmd. De qualquer forma, ao usar start.spring.io para
construir seu projeto, você terá ambos em mãos.
Machine Translated by Google
<plug-in>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
Este plugin se conecta à fase de pacote do Maven e executa algumas etapas extras:
1. Ele captura o arquivo JAR originalmente gerado pelos procedimentos de empacotamento padrão do Maven (destino/
ch7-0.0.1-SNAPSHOT.jar neste caso) e extrai todo o seu conteúdo.
4. No novo arquivo JAR, ele adiciona o codificador do carregador Spring Boot, que é um código cola que pode ler
arquivos JAR de dentro, permitindo que ele se torne um arquivo JAR executável.
5. Adiciona o código do nosso aplicativo ao novo arquivo JAR em uma subpasta chamada BOOT-INF.
6. Ele adiciona TODOS os JARs de dependência de terceiros do nosso aplicativo a esse arquivo JAR em uma
subpasta chamada BOOT-INF/lib.
7. Finalmente, ele adiciona alguns metadados sobre as camadas do aplicativo a este arquivo JAR abaixo de
BOOT-INF como classpath.idx e layers.idx (mais sobre isso na próxima seção!).
Com nada além da JVM, podemos lançar nossa aplicação da seguinte forma:
_
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \\\\
\\/ ___)| |_)| | | | || (_| | |____| .__|_| |_|_|| |_\__, | / / / / ) ) ) )
'
=========|_|=============|___/=/_/_/_/
:: Bota Primavera :: (v3.0.0)
…etc…
Com um comando tão simples, nosso aplicativo foi transformado de um aplicativo executado dentro de
nosso IDE centrado no desenvolvedor em um aplicativo executado em qualquer máquina com o Java
Development Kit (JDK) correto.
Machine Translated by Google
Talvez isso não seja tão impressionante quanto você foi levado a acreditar, talvez porque seja incrivelmente simples.
Então, vamos revisitar o que exatamente aconteceu:
• Não há necessidade de baixar e instalar um contêiner de servlet Apache Tomcat em qualquer lugar. Estamos usando o Apache
Tomcat incorporado. Isso significa que este pequeno arquivo JAR contém os meios para ser executado.
• Não há necessidade de passar pelo procedimento legado de instalar um servidor de aplicativos, criar um arquivo WAR, combiná-
lo com dependências de terceiros usando algum arquivo assembly feio para criar um arquivo EAR e, em seguida, fazer
upload de tudo para alguma interface de usuário atroz .
• Podemos enviar tudo isso para nosso provedor de nuvem favorito e comandar a execução do sistema
10.000 cópias.
Ter o Maven gerando um aplicativo executável dependente apenas de Java é épico quando se trata de implantação.
Novidades
Antigamente (1997), era prática comum eu realizar uma liberação caminhando (sim, caminhando fisicamente) até vários
chefes de departamento e fazendo-os assinar um pedaço de papel.
Com isso em mãos, eu gravaria um CD com binários compilados. A partir daí, eu executaria o restante de um procedimento
de 17 páginas para essencialmente construir os binários e levá-los até um laboratório ou local do cliente e instalar o software.
Esse processo geralmente demorava alguns dias.
Remover as barreiras técnicas para fazer lançamentos como este é inovador.
Deve-se ressaltar que os super JARs não foram inventados pela equipe Spring Boot. O plugin Maven Shade existe desde 2007. O
trabalho deste plugin é executar as mesmas etapas de agrupar tudo em um arquivo JAR, mas de forma diferente.
Este plugin descompacta todos os arquivos JAR recebidos, seja o código do nosso aplicativo ou uma dependência de terceiros.
Todos os arquivos descompactados são misturados em um novo arquivo JAR. Este novo arquivo é chamado de JAR sombreado.
Algumas outras ferramentas e plug-ins fazem a mesma coisa, mas são fundamentalmente errados ao misturar as coisas dessa
maneira.
Algumas aplicações precisam estar dentro de um JAR para funcionar corretamente. Também existe o risco de os arquivos
que não são de classe não acabarem no lugar certo. Os aplicativos que consomem classes de terceiros de arquivos JAR
podem apresentar algum comportamento aberrante. E também há uma chance de você estar violando a licença de alguma biblioteca.
Se você for a qualquer mantenedor de biblioteca e perguntar se eles lidarão com relatórios de bugs quando você usar seus arquivos
JAR lançados além do escopo da forma como são lançados, você poderá não obter o suporte esperado.
Spring Boot simplesmente permite que nosso código seja executado como se os arquivos JAR de terceiros estivessem lá, como sempre. Nenhum
sombreamento é necessário.
Mas resta uma coisa: a parte onde mencionei que o app está pronto para rodar onde quer que você tenha um JDK instalado.
Machine Translated by Google
Docker é construído sobre o paradigma dos contêineres de transporte. Os contêineres marítimos, responsáveis pela
movimentação da maior parte das mercadorias mundiais em navios e trens, têm um formato comum. Isso significa que as
pessoas podem planejar como enviar seus produtos sabendo que os contêineres movimentados pelo mundo inteiro são todos
do mesmo tamanho e estrutura.
O Docker é construído sobre a biblioteca libcontainer do Linux, um kit de ferramentas que não garante um ambiente
completamente virtual, mas sim parcialmente virtual. Ele permite que os processos, a memória e a pilha de rede de um
contêiner sejam isolados do servidor host.
Essencialmente, você instala o mecanismo Docker em todas as máquinas de destino. A partir daí, você está livre para instalar
qualquer contêiner, conforme necessário, para fazer o que for necessário.
Em vez de perder tempo criando uma máquina virtual inteira, você simplesmente ativa contêineres dinamicamente.
Com a natureza mais orientada para aplicativos do Docker, ele se torna uma escolha muito mais ágil.
Docker existe desde 2013. Quando escrevi Learning Spring Boot 2.0 Second Edition
em 2017, era altamente experimental na época. Então, eu não mencionei isso. As pessoas o usavam ocasionalmente,
talvez para demonstrações ou outras tarefas pontuais, mas não era amplamente utilizado para sistemas em produção.
Avançando até hoje, você poderá ver o Docker sendo usado EM TODA PARTE.
Todo provedor baseado em nuvem oferece suporte a contêineres Docker. Os sistemas de CI mais populares permitem
executar trabalhos baseados em contêiner. Há uma infinidade de empresas construídas em torno do paradigma de
orquestração de sistemas de contêineres Docker. Atomic Jar, uma empresa construída em torno da plataforma de
teste baseada em Docker, Testcontainers, está disponível. Pedir aos desenvolvedores e administradores de sistemas
que instalem o Docker em máquinas locais e em servidores voltados para o cliente não é mais a grande questão de antes.
Supondo que você tenha instalado o Docker (visite docker.com para começar) em sua máquina,
basta isso!
% ./mvnw spring-boot:build-image
Desta vez, em vez de se conectar a qualquer fase específica do ciclo de vida de construção e implantação do Maven, o plugin
Spring Boot Maven executará uma tarefa personalizada para preparar um contêiner.
Machine Translated by Google
Assar um contêiner é uma frase do Docker que significa montar todas as peças necessárias para operar um contêiner.
Baking implica que só precisamos gerar a imagem deste contêiner uma vez e podemos reutilizá- la quantas
vezes forem necessárias para executar quantas instâncias forem necessárias.
Quando o Spring Boot executa um processo de construção de imagem, ele primeiro executa a fase de pacote do Maven.
Isso inclui a execução do complemento padrão de testes unitários. Depois disso, ele montará o uber JAR de que falamos
na seção anterior.
Com um super JAR executável em nossas mãos, o Spring Boot pode então passar para a próxima fase: aproveitar o
Paketo Buildpacks para reunir o tipo certo de contêiner.
O Docker possui uma solução de cache integrada que envolve camadas. Se uma determinada etapa na construção de um contêiner não
apresentar alterações em relação ao processo de montagem do contêiner anterior, ela usará a camada de cache do mecanismo Docker.
Porém, se algum aspecto for alterado, isso invalidará a camada em cache e criará uma nova.
As camadas armazenadas em cache podem incluir tudo, desde a imagem base na qual um contêiner é construído (por exemplo,
se você estivesse estendendo um contêiner simples baseado no Ubuntu) até os pacotes do Ubuntu baixados para aprimorá-lo
junto com nosso código personalizado.
Uma coisa que NÃO queremos fazer é misturar o código do nosso aplicativo personalizado com as dependências
de terceiros que nosso aplicativo usa. Por exemplo, se estivéssemos usando a versão GA do Spring Framework
6.0.0, isso certamente seria útil armazenar em cache. Dessa forma, não precisamos ficar puxando para baixo!
Mas se nosso código personalizado fosse misturado na mesma camada e um único arquivo Java fosse alterado, toda a
camada seria invalidada e teríamos que extrair tudo novamente.
Portanto, colocar coisas como Spring Boot, Spring Framework, Moustache e outras bibliotecas em uma camada, enquanto
colocamos nosso código personalizado em uma camada separada, é uma boa escolha de design.
No passado, isso exigia diversas etapas manuais. Mas a equipe do Spring Boot fez dessa abordagem em camadas a
configuração padrão! Confira o seguinte trecho da execução de ./mvnw spring- boot:build-image:
• Ele está usando o Docker para construir uma imagem chamada docker.io/library/ch7:0.0.1-SNAPSHOT.
Isso inclui o nome do nosso módulo, bem como a versão, ambos encontrados em nosso arquivo pom.xml.
• Ele usa o Paketo Buildpack do Docker Hub, conforme mostrado puxando o pacote paketobuildpacks/
construtor e paketobuildpacks/executar contêineres.
A saída completa da tarefa de construção de imagem do plugin Spring Boot Maven é francamente
longa e larga demais para caber em um livro. Mas você pode conferir o resultado completo em https://
springbootlearning.com/build-image-output.
Nesta fase temos um container totalmente montado. Vamos dar uma olhada! Usando o Docker, agora podemos executar
o contêiner, conforme mostrado aqui:
. ____ _ __ _
_
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \\\\
\\/ ___)| |_)| | | | || (_| | |____| .__|_| |_|_| |_\__,
| |//// ) ) ) )
'
=========|_|=============|___/=/_/_/_/
:: Bota Primavera :: (v3.0.0)
principal]
[ cslChapter7Application Iniciando :
Chapter7Application v0.0.1-SNAPSHOT usando Java 17.0.5 em 5e4fb7fdead2 com PID 1 (/workspace/
BOOT-INF/classes iniciadas por cnb em /workspace)
• -p 8080:8080: um argumento que mapeia o número da porta interna do contêiner 8080 para todos
fora do contêiner na porta 8080
O restante da saída é a saída do Docker do contêiner em execução. O contêiner agora está em execução. Na
verdade, de outro shell, podemos vê-lo em voo fazendo o seguinte:
% janela de encaixe ps
docker ps é um comando que mostra qualquer processo do Docker em execução. A saída é um pouco limitada
aqui dentro de um livro, mas esta saída de uma linha mostra o seguinte:
• /cnb/process/web: O comando que o Paketo Buildpack está usando para executar nosso Spring
Aplicativo de inicialização.
Machine Translated by Google
• "5 minutos atrás" e "Até 5 minutos": quando o contêiner foi iniciado e por quanto tempo
está em alta.
• irritado_cray: O nome amigável que o Docker deu à instância deste contêiner. Você pode
consulte uma instância pelo código hash ou por este.
irritado_cray
% janela de encaixe ps
Isso desliga todo o contêiner. Podemos ver isso no console onde lançamos tudo.
Importante
Neste caso de criação de um contêiner Docker, Docker escolheu o nome aleatório e amigável para humanos
irritado_cray. Cada contêiner que você criar terá um nome diferente, exclusivo e amigável . Você pode usar esses
detalhes, o valor hash do contêiner ou simplesmente apontar e clicar no aplicativo Docker Desktop para controlar
contêineres em sua máquina.
Dito isso, podemos agora conferir a etapa mais importante: a liberação do container.
Você pode enviar o contêiner para seu provedor de nuvem favorito. Quase todos eles suportam Docker.
Mas você também pode enviar o contêiner para o Docker Hub.
Docker Hub oferece vários planos. Você pode até obter uma conta gratuita. Sua empresa ou universidade
também pode conceder acesso. Não deixe de conferir https://docker.com/pricing, escolher o plano mais adequado
para você e criar sua conta. Supondo que você tenha feito isso, verifique o restante desta seção!
Machine Translated by Google
Supondo que tudo isso foi feito, agora podemos enviar nosso contêiner para o Docker Hub executando os
seguintes comandos:
Essas etapas para enviar estão ocultas nos comandos sucintos a seguir. A primeira envolve marcar o
contêiner local, conforme mostrado aqui:
• docker tag <image> <tag>: marca o nome do contêiner local de ch7 que possui o local
tag de 0.0.1-SNAPSHOT com um prefixo de nosso ID de usuário Docker Hub. Isso é fundamental porque todas as
nossas imagens de contêiner DEVEM corresponder ao nosso Docker Hub ID!
Isso pode ser meio confuso. Os contêineres Docker Hub têm três características:
• Nome do contêiner
• Etiqueta do contêiner
• Namespace do contêiner
Eles vão juntos como namespace/name:tag. Esta convenção de nomenclatura NÃO precisa corresponder à
nomenclatura local do contêiner. Você pode simplesmente reutilizá-lo, mas como será público, você pode
escolher outra coisa.
A marcação é essencialmente a maneira de pegar seu contêiner local e atribuir-lhe um nome público. Embora
existam outros repositórios Docker, por enquanto continuaremos com o Docker Hub como nosso repositório de
contêineres preferido. E para cumprir a política do Docker Hub, seu namespace precisa corresponder ao ID da
nossa conta Docker Hub.
Machine Translated by Google
Depois que o contêiner for marcado, ele poderá ser enviado ao Docker Hub usando o seguinte comando:
• docker push <tagged image>: envia o contêiner para o Docker Hub usando o nome da imagem marcada
voltada para o público
Para reiterar o último ponto, você envia o contêiner para o Docker Hub usando o nome público.
No nosso caso, devemos usar gturnquist848/learning-spring-boot-3rd-edition- ch7:0.0.1-SNAPSHOT.
Depois de enviar o contêiner, podemos vê-lo no Docker Hub, conforme mostrado aqui:
Poderíamos nos aprofundar muito mais no Docker, no Docker Hub e no mundo dos contêineres, mas, francamente,
existem livros inteiros dedicados a esse tópico.
O objetivo do Spring Boot é tornar super simples agrupar um aplicativo completo dentro de um contêiner e liberá-lo
para nossos usuários, e fizemos isso sem escrever nenhum código personalizado.
Como seria pegar aquele contêiner do outro lado… e começar a ajustar as coisas na produção? Confira a seção a
seguir.
Esta é a própria natureza das operações. E os vários membros da equipa Spring não são estranhos ao mundo da
produção.
Existem várias coisas que podemos ajustar e ajustar depois de receber um super JAR ou um contêiner.
Supondo que temos um super JAR construído a partir do código deste capítulo, podemos facilmente digitar algo assim:
Isso iniciaria o aplicativo com todas as configurações padrão, incluindo a porta servlet padrão 8080.
Mas e se precisássemos que ele fosse executado próximo a outro aplicativo da web Spring Boot que acabamos de instalar ontem?
Isso sugere que precisaríamos ouvir em uma porta diferente. Não diga mais. Tudo o que precisamos fazer é executar um
comando ligeiramente diferente, como este:
TomcatWebServer: Tomcat iniciado na(s) porta(s): 9000 (http) com caminho de contexto ''
Na parte inferior da saída do console, podemos ver que o Apache Tomcat agora está escutando na porta 9000.
Isso é legal, mas é um pouco complicado ter que digitar esse parâmetro extra todas as vezes, certo?
Machine Translated by Google
servidor.port=9000
Este arquivo de substituição de propriedade contém uma propriedade: configuração server.port do Spring Boot com um
valor de 9000.
[ principal] osbwembdedded.tomcat.
TomcatWebServer : Tomcat iniciado na(s) porta(s): 9000 (http) com caminho de
contexto ''
Desta vez, quando o Spring Boot é iniciado, ele olha em volta e identifica o application.properties
arquivo em nossa pasta local. Em seguida, ele aplica todas as suas configurações como substituições àquelas dentro do arquivo JAR e pronto!
Mas isso não é tudo. Qualquer propriedade que precisemos substituir está à nossa disposição. Podemos ter vários arquivos de substituição.
Qual é um exemplo disso? No mundo de requisitos em constante evolução, não é difícil imaginar o nosso gerente
aparecendo e nos dizendo que precisamos executar não uma, mas três instâncias.
Agora, precisamos hospedar nosso aplicativo nas portas 9000, 9001 e 9002 para corresponder ao balanceador de carga que os
administradores de sistema acabaram de configurar!
Vamos expandir as coisas e criar um nome tático para cada instância. Algo super original, como instância1,
instância2 e instância3.
Em seguida, faça outra cópia, desta vez como application-instance3.properties. Desta vez, faça com que seu
server.port tenha um valor de 9002.
Machine Translated by Google
Com isso implementado, agora podemos executar três instâncias usando o suporte de perfil do Spring Boot.
Começaremos lançando instance1, da seguinte forma:
[ principal] osbwembdedded.tomcat.
TomcatWebServer : Tomcat iniciado na(s) porta(s): 9000 (http) com caminho de
contexto ''
Aqui, podemos ver que a instância1 agora está sendo executada na porta 9000.
[ principal] osbwembdedded.tomcat.
TomcatWebServer : Tomcat iniciado na(s) porta(s): 9001 (http) com caminho de
contexto ''
Nesta saída do console, podemos ver que a instância2 está sendo executada na porta 9001.
Não pare por aí! Vamos abrir uma terceira aba do console e executar instance3, conforme mostrado aqui:
[ principal] osbwembdedded.tomcat.
TomcatWebServer : Tomcat iniciado na(s) porta(s): 9002 (http) com caminho de
contexto ''
Surpresa!
Agora temos três instâncias do nosso aplicativo em execução em portas diferentes. Enterrado na saída do
primeiro console, podemos ver o seguinte:
2022-11-20T15:52:28.076-05:00 INFORMAÇÕES
Esta linha mostra que o Spring Boot detectou que o perfil instance1 está ativo.
Existe uma entrada semelhante para as outras duas saídas do console. Não vamos mostrá-los aqui. Basta dizer que
os perfis são uma maneira poderosa de executar várias instâncias do que começou como um aplicativo simples e único.
No entanto, ainda não terminamos. Isso ocorre porque a configuração padrão deste aplicativo era com um banco
de dados HSQL na memória. Isso significa que as três instâncias não compartilham um banco de dados comum.
Considerando que a integração do código já foi testada no PostgreSQL com Testcontainers, poderíamos ajustar
as configurações para nos permitir apontar para uma instância de produção desse banco de dados!
Primeiro, precisamos aumentar esse banco de dados. E Testcontainers nos mostrou o caminho usando Docker.
Para executar uma instância autônoma, tente isto:
Este comando irá gerar uma cópia do PostgreSQL com as seguintes características:
• -p 5432:5432: A porta padrão 5432 será exportada para o público com a mesma porta.
• --name my-postgres: O contêiner será executado com um nome fixo em vez de um nome aleatório.
Isso nos impedirá de executar várias cópias ao mesmo tempo.
• spring.datasource.username: contém o nome de usuário padrão do postgres sob o qual o contêiner é executado
Estas são todas as propriedades necessárias para o Spring Boot montar um bean JDBC DataSource.
• spring.jpa.hibernate.show-sql: Isso ativará a capacidade do Spring Data JPA de imprimir instruções SQL geradas
Com todas essas configurações, estamos alinhando JDBC e JPA para se comunicarem com o contêiner de banco de dados
PostgreSQL que acabamos de criar minutos atrás.
A única coisa que precisamos resolver… é garantir que todas as três instâncias não criem os mesmos dados pré-
carregados. No Capítulo 3, Consultando dados com Spring Boot, e no Capítulo 4, Protegendo um aplicativo com Spring
Boot, adicionamos alguns bits ao nosso aplicativo que pré-carregariam os dados de login do usuário, junto com algumas
entradas de vídeo. Na verdade, isso é melhor feito com ferramentas externas. Devemos deixar os DBAs cuidarem da
configuração do esquema e do carregamento dos dados. Um aplicativo que inicia e para, e executa múltiplas instâncias,
NÃO é o veículo para aplicar uma política de gerenciamento de dados persistente. Assim, quaisquer desses beans
precisam ser comentados ou pelo menos sinalizados para serem executados SOMENTE com algum OUTRO perfil,
como setup. Se você examinar o código final fornecido para este capítulo, conforme mostrado no início deste capítulo,
encontrará um arquivo application-setup.properties
arquivo, juntamente com tais restrições no código. Não os mostraremos aqui, mas se você quiser que o aplicativo pré-
carregue esses dados, execute-o com a configuração do perfil (e faça isso apenas UMA VEZ após iniciar o banco de
dados!).
A partir daqui, o céu é o limite. Poderíamos executar uma dúzia de cópias, embora NÃO queiramos fazê-lo desta forma. É aí que
se torna valioso usar algo destinado a orquestrar vários aplicativos.
Resumo 187
Spinnaker é um pipeline de entrega contínua. Ele torna possível levar commits para nosso
repositório GitHub, empacotá-los em super JARs, preparar imagens de contêiner do Docker
e gerenciá-las com atualizações contínuas em produção. Confira https://springbootlearning.com/
spinnaker para obter mais informações sobre isso.
E, claro, existe o VMware Tanzu. Tanzu é um pacote completo, não apenas um orquestrador Docker.
Possui suporte sólido para Kubernetes, entre outras coisas. Não deixe de conferir em https://
springbootlearning.com/tanzu.
Todas essas são ferramentas poderosas, cada uma com suas vantagens. E fornecem uma maneira abrangente de gerenciar
aplicativos Spring Boot em produção.
Resumo
Neste capítulo, aprendemos várias habilidades importantes, incluindo a criação de um uber JAR que pode ser executado em
qualquer lugar, a criação de uma imagem de contêiner Docker que pode ser executada localmente sem a necessidade de
Java, o envio de nosso contêiner Docker para o Docker Hub, onde ele pode ser consumido por nosso clientes e executando
várias instâncias do nosso uber JAR apontado para um banco de dados persistente, diferente daquele que veio junto.
Isso conclui este capítulo! No próximo capítulo, nos aprofundaremos e descobriremos maneiras de acelerar nosso aplicativo
Spring Boot a uma velocidade quase warp por meio do poder do GraalVM e de algo chamado de aplicativos nativos.
Machine Translated by Google
Machine Translated by Google
8
Tornando-se nativo com Spring Boot
No capítulo anterior, aprendemos diversas maneiras de transformar nosso aplicativo de uma coleção de código em
um executável, pronto para qualquer ambiente de produção, inclusive a nuvem. Também aprendemos como ajustá-
lo e ajustá-lo para que pudéssemos aumentá-lo conforme necessário.
Com base nas ferramentas abordadas nos capítulos anteriores, veremos como os aplicativos Spring Boot estão
realmente prontos para o futuro, levando-os para algumas das plataformas mais avançadas onde o desempenho
pode ser realmente aumentado para 11 à medida que exploramos aplicativos nativos. .
O foco deste capítulo não é tanto escrever aplicativos Spring Boot, mas sim compilá- los em um formato mais rápido
e eficiente (o que veremos em breve). Portanto, não há necessidade de escrever novo código. Se você verificar o
link anterior, descobrirá que o código deste capítulo é uma cópia do código do capítulo anterior. No entanto, o
arquivo de construção é um pouco diferente, o que apresentaremos na próxima seção.
Machine Translated by Google
No entanto, as pessoas continuaram a criticar o Java por coisas que podem parecer fúteis, como o tempo de inicialização. Na
verdade, um aplicativo Java, executado em sua própria máquina virtual, não é tão rápido quanto os binários Go ou C++.
Mas, por muito tempo, isso não foi um problema, já que os aplicativos da web geralmente têm longos tempos de atividade.
Contudo, as novas fronteiras de produção expuseram esta fraqueza. Sistemas de implantação contínua , onde 10.000 instâncias são
executadas ao mesmo tempo e são substituídas várias vezes ao dia, fizeram com que o custo de 30 segundos começasse a
aumentar as contas de nuvem das pessoas.
Um novo player no espaço dos sistemas de produção são as funções executáveis. Isso mesmo. Agora é possível implantar uma
única função como um aplicativo inteiro em plataformas como AWS Lambda. E seus resultados poderiam ser canalizados diretamente
para outra função implantada.
Nesses cenários, onde as funções são ativadas imediatamente conforme a demanda, fatores como o tempo de inicialização
determinam FORTEMENTE as tecnologias que as pessoas usam.
GraalVM da Oracle é essencialmente uma nova máquina virtual que oferece suporte para praticamente qualquer
linguagem de programação existente. Não execute seus arquivos Java JAR na JVM. Execute-os no GraalVM!
GraalVM é um tempo de execução de alto desempenho voltado para Java, JavaScript, Python, Ruby, R, C e C++.
Quando você executa milhares de instâncias de sistemas, todo o desempenho de seus aplicativos pode fazer uma diferença
significativa.
E a equipe Spring, em sua busca contínua para reduzir a complexidade do Java, está aqui para ajudar. A partir de 2019 nasceu o
projeto experimental Spring Native. E desde então, quase todas as facetas do portfólio Spring foram ajustadas e ajustadas para
apoiar esse esforço de levar o poder do GraalVM a qualquer aplicação Spring Boot.
E assim, ao longo do restante deste capítulo, exploraremos o aplicativo que conhecemos nos capítulos anteriores e adaptando-o aos
rigores do GraalVM.
O código Java sempre, desde o início dos tempos, foi compilado em bytecode, destinado a ser executado em
Java Virtual Machine (JVM). Isso resultou na expressão comum escrever uma vez, executar em qualquer lugar.
Qualquer bytecode Java compilado, devido a todos os aspectos desses arquivos serem capturados pela
especificação Java, pode ser executado em qualquer JVM compatível, não importa em que máquina ele esteja.
Este foi um grande avanço em relação à era anterior, que envolvia a compilação separada para cada arquitetura
de máquina na qual um aplicativo seria implantado. Isso foi revolucionário em sua época e permitiu outros
aprimoramentos pós-compilação, como acelerações do compilador just-in-time (JIT) e tornar os aplicativos mais
finos e eficientes de forma dinâmica.
Compilar aplicativos para GraalVM envolve trocar parte dessa flexibilidade preservada por código mais rápido e com maior
eficiência de memória.
Isso pode gerar uma pergunta: por que não compilar CADA uma de nossas aplicações para GraalVM?
GraalVM, para fazer algumas das coisas que faz, exige que abandonemos alguns recursos principais:
Por que? Porque o GraalVM realiza análises avançadas do nosso código. Ele usa um conceito chamado acessibilidade,
onde essencialmente inicia o aplicativo e depois analisa qual código o GraalVM pode ver. Qualquer coisa que NÃO esteja
acessível é simplesmente cortada da imagem nativa final.
A reflexão ainda é possível em aplicativos nativos. Mas como nem tudo é visível diretamente, pode ser necessária uma
configuração extra para que nada seja esquecido.
Os proxies têm um problema semelhante. Quaisquer proxies que serão suportados devem ser gerados no momento da
construção da imagem nativa.
Isso significa que acessar bits de código por meio de táticas de reflexão, desserialização de dados e proxies é mais
complicado e não tão simples como antes. O risco é que certas partes do nosso aplicativo sejam cortadas se não as
capturarmos adequadamente.
Esta é uma das razões pelas quais cada projeto do portfólio Spring tem trabalhado diligentemente para garantir que
quaisquer bits que PRECISAM estar em nossas aplicações tenham as dicas necessárias para que o GraalVM os encontre.
Machine Translated by Google
É também uma razão pela qual o Spring Framework reduziu o uso de táticas de reflexão para gerenciar o contexto
do aplicativo. E é também a razão pela qual o Spring Boot adotou uma abordagem geral de não fazer proxy de
classes de configuração contendo definições de bean para reduzir o número de proxies reais em um aplicativo.
Nos últimos 2 anos, a equipe Spring trabalhou incansavelmente com a equipe GraalVM para ajustar e ajustar vários
aspectos do portfólio Spring, removendo chamadas de reflexão desnecessárias e reduzindo a necessidade de
proxies. Além disso, muitas melhorias foram feitas no GraalVM para que funcione melhor com o código Spring.
Para começarmos a usar o GraalVM, voltaremos ao nosso amigo favorito, Spring Initializr, em https://start.spring.io.
• Projeto: Maven
• Grupo: com.springbootlearning.learningspringboot3
• Artefato: capítulo 8
• Nome: Capítulo 8
• Embalagem: Frasco
• Java: 17
• Dependências:
Primavera Web
Bigode
Banco de dados H2
Segurança Primavera
A partir daqui, podemos clicar em EXPLORE. Um pop-up mostrando o arquivo de construção nos permite ver o que é necessário
para construir um aplicativo nativo Spring Boot.
• spring-boot-starter-data-jpa
• spring-boot-starter-web
• spring-boot-starter-segurança
• h2
Como estamos usando Spring Data JPA, que envolve (por padrão) o Hibernate e suas entidades
proxy, temos este plugin adicional:
<plugin>
<groupId>org.hibernate.orm.tooling</groupId> <artifactId>hibernate-
enhance-maven-plugin</artifactId> <version>${hibernate.version}</version>
<execuções>
<execução>
<id>melhorar</id>
<metas>
<goal>melhorar</goal> </
goals>
<configuration>
<enableLazyInitialization>
verdadeiro
</enableLazyInitialization>
<enableDirtyTracking>
verdadeiro
</enableDirtyTracking>
<enableAssociationManagement>
verdadeiro
</enableAssociationManagement> </
configuration> </
execution>
Machine Translated by Google
</execuções>
</plugin>
Isso ajuda a adicionar algumas configurações extras que a equipe do Hibernate identificou como críticas para que os proxies do
Hibernate funcionem corretamente com o GraalVM.
Algo também fornecido por spring-boot-starter-parent (referenciado no topo do nosso arquivo de construção)
é um perfil nativo do Maven. Quando o habilitamos, ele altera as configurações do spring- boot-maven-
plugin. Outras ferramentas também são colocadas online, incluindo o conjunto de ferramentas de compilação
antecipada (AOT) , bem como o plug-in maven nativo do GraalVM.
Veremos como utilizar tudo isso para construir aplicativos nativos extremamente rápidos na próxima seção.
Construir um projeto baseado em Maven com Spring Boot 3 requer que tenhamos o Java 17 instalado. Mas para
construir uma imagem nativa, precisamos mudar de rumo.
Native-maven-plugin mencionado na seção anterior, que vem com o perfil nativo do Maven, requer a
instalação de uma JVM diferente. Existem ferramentas adicionais necessárias para construir imagens
nativas. A maneira mais fácil de gerenciar diferentes JVMs em nossa máquina é usando sdkman (https://
sdkman.io).
SDKman?
sdkman é uma ferramenta de código aberto que permite instalar vários JDKs e alternar entre eles
com facilidade. É tão fácil quanto sdk install java 17.0.3-tem seguido de sdk use java 17.0.3-tem para
baixar, instalar e mudar para a versão Temurin Java 17.0.3 da Eclipse Foundation. (A Eclipse
Foundation é a atual mantenedora do Jakarta EE.) O sdkman também é capaz de instalar a versão
correta do JDK – por exemplo, se você estiver em um Mac M1 ou um Mac Intel mais antigo. E no
nosso caso, nos permite instalar o JDK próprio do GraalVM, que inclui todas as ferramentas
necessárias para construir imagens nativas em nossa máquina.
Machine Translated by Google
Para construir aplicações nativas no GraalVM, precisamos instalar uma versão do Java 17 que inclua
ferramentas GraalVM digitando o seguinte comando:
Podemos até dar uma olhada no que esta versão do Java tem:
% versão java
versão openjdk "17.0.5" 2022/10/18
Ambiente de tempo de execução OpenJDK GraalVM CE 22.3.0 (compilação
17.0.5+8-jvmci-22.3-b08)
Servidor OpenJDK de 64 bits VM GraalVM CE 22.3.0 (compilação
17.0.5+8-jvmci-22.3-b08, modo misto, compartilhamento)
Este é o OpenJDK versão 17, também conhecido como Java 17, mas possui GraalVM Community Edition
(CE) versão 22.3.0. Essencialmente, ele tem todos os bits do Java 17 misturados com o GraalVM 22.3.
O que é OpenJDK?
OpenJDK é a fonte de todas as distribuições de Java. A SUN Microsystems, os inventores do Java, e mais tarde
da Oracle, inicialmente fizeram os lançamentos oficiais do Java. No entanto, desde o Java 7, todas as versões do
Java começam com OpenJDK. Cada fornecedor é livre para seguir a linha de base do OpenJDK e aplicar adições
conforme acharem adequado. No entanto, TODAS as distribuições de Java são obrigadas a passar por um Kit de
Compatibilidade de Tecnologia (TCK) lançado pelo comitê executivo do Java para ganhar o logotipo da xícara de café.
Vários fornecedores oferecem diferentes níveis de suporte para diferentes períodos e quais patches
eles manterão ou levarão de volta à fonte. Existem até distribuições de determinados fornecedores
que NÃO são certificadas pelo TCK, portanto, leia todos os detalhes antes de escolher um JDK.
Com o Java 17 do GraalVM CE ativo, podemos finalmente construir nossa aplicação nativamente. Para fazer isso,
precisamos executar o seguinte comando:
Linux é provavelmente a plataforma mais simples para construir imagens nativas. Os Macs
também têm suporte forte, com algumas lacunas no chipset M1. Porém, para construir
imagens nativas no Windows, você precisa verificar a seção Windows da documentação de
referência do Spring Boot em https://springbootlearning.com/graalvm-windows. Lá, você
encontrará detalhes sobre o que precisa ser instalado na sua máquina para construir imagens
nativas no Windows. Além disso, não se esqueça de usar mvnw.cmd ao construir com Maven no Windows!
Este comando irá compilar nossa aplicação com o perfil nativo ativado. Ele aproveita o plugin nativo do maven
mencionado na seção anterior. Este processo pode demorar um pouco mais do que construir usando uma configuração
padrão. E há muitos avisos.
O processo envolve a varredura completa do código e a execução do que é conhecido como compilação AOT.
Basicamente, em vez de deixar as coisas em formato de bytecode, para serem convertidas em código de máquina local
quando a JVM for inicializada, ela converte as coisas antecipadamente.
Isso exige que certos recursos sejam restringidos, como o uso de proxies e reflexão. Parte do apoio do Spring para se
tornar pronto para GraalVM foi reduzir o uso de proxies e evitar reflexão quando não for necessário. Existem maneiras de
ainda usar esses recursos, mas eles podem sobrecarregar o executável nativo e remover alguns dos benefícios. As
ferramentas AOT também não conseguem ver tudo do outro lado das chamadas de reflexão e do uso de proxy, portanto,
exigem o registro de metadados adicionais.
O artefato resultante não é um arquivo JAR uber nem um arquivo JAR executável. Em vez disso, é um arquivo
executável para a plataforma em que foi criado.
Importante
Um dos recursos mais populares do Java desde o primeiro dia tem sido a natureza de gravação única/execução em qualquer lugar.
Isso funciona porque normalmente é compilado em bytecode independente de plataforma e é executado
dentro da JVM, uma máquina virtual que pode variar de máquina para máquina. GraalVM evita tudo isso.
O executável final NÃO tem essa natureza de execução em qualquer lugar. Você pode inspecionar o
aplicativo final digitando o arquivo target/ch8 no diretório base do projeto. Na minha máquina, está escrito Mach-O
Arm64 executável de 64 bits.
% alvo/ch8
. ____ _ __ _
_
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \\\\
|//// ___)| |_)| | | | || (_| | \\/ |____|
| .__|_| |_|_| |_\__, ) ) ) )
'
=========|_|=============|___/=/_/_/_/
:: Spring Boot :: …… (v3.0.0)
[principal] osbwembdedded.tomcat.
TomcatWebServer: Tomcat iniciado na(s) porta(s): 8080 (http) com caminho de contexto ''
principal]
[cslChapter8Application iniciou o :
Chapter8Application em 0,104 segundos (processo em execução por 0,121)
Na última linha, podemos ver que a aplicação inicializou em 0,104 segundos. Para um aplicativo Java, isso
é incrivelmente rápido.
Machine Translated by Google
Figura 8.2 – Aplicação nativa solicitando permissão para aceitar conexões de rede
Foi necessário um pouco mais de esforço para configurar nosso aplicativo para funcionar com GraalVM. Também demorou mais para construir o aplicativo
em si. Além disso, aproveitamos a incrível flexibilidade de gravação única/execução em qualquer lugar do Java.
Por que?
Imagine executar 1.000 cópias deste aplicativo na nuvem. E se nosso aplicativo demorasse 20 segundos para inicializar? 1.000 instâncias seriam
Adicionar 5,6 horas extras de tempo faturável cada vez que lançávamos uma mudança começaria a fazer sentido . Se adotarmos a entrega contínua e
adiarmos todos os commits de patch, nossa conta poderá ficar fora de controle. Talvez não do ponto de vista operacional, mas definitivamente do ponto de
vista do faturamento!
Se, em vez disso, nosso aplicativo for iniciado em 0,1 segundo, como acabou de acontecer, 1.000 instâncias nos renderiam pouco menos de 17 minutos
Além disso, nosso aplicativo seria executado em uma configuração de memória mais eficiente. Contanto que nosso
sistema de entrega contínua crie o aplicativo no mesmo sistema operacional que nosso ambiente de destino, gravar uma
vez/executar em qualquer lugar não será um problema.
Ainda há um problema persistente... e se não tivermos o ambiente de destino em nossa máquina de construção
local? E se estivéssemos trabalhando em Windows ou Mac, mas nossa nuvem operasse usando contêineres
Docker baseados em Linux?
Por exemplo, se planejamos executar nosso aplicativo em uma configuração de nuvem baseada em Linux, construir um
aplicativo localmente em um MacBook Pro ou em uma máquina Windows não será suficiente.
No Capítulo 7, Liberando um aplicativo com Spring Boot, aprendemos como usar ./mvnw spring- boot:build-image
e deixar um Paketo Buildpack montar nosso aplicativo em um contêiner Docker.
Podemos usar algo semelhante para construir um aplicativo nativo dentro de um contêiner Docker.
Esse processo pode demorar ainda mais do que construir o aplicativo nativo localmente, mas a vantagem é que,
quando concluído, você terá um contêiner Docker totalmente preparado com um aplicativo nativo nele.
Conforme discutido no capítulo anterior, agora você tem várias opções para executá-lo em sua máquina local,
enviá-lo para seu provedor de nuvem ou liberar o aplicativo para Docker Hub.
Aviso!
No momento em que este artigo foi escrito, os Macs M1 NÃO suportavam esta opção! Se você invocar ./
mvnw -Pnative spring-boot:build-image, ele iniciará o processo, mas em um determinado estágio, ele
simplesmente travará e nunca mais seguirá em frente. Para interromper o processo, você DEVE entrar no
Docker Desktop e encerrar o Paketo Buildpack que está sendo usado para realizar esta tarefa. Existem
substituições no spring- boot-maven-plugin que permitem conectar configurações alternativas do Buildpack.
Se você é nativo do spring-boot-starter-parent, observe o arquivo pom.xml de dentro do seu IDE e procure
esse perfil nativo – você verá como eles configuram esse plugin com um construtor.
Tendo feito tudo isso, pode haver alguma confusão persistente por aí.
Machine Translated by Google
Spring Native foi um projeto de ponte experimental construído para Spring Boot 2.7. Os bits que estão no Spring Native se tornaram
cidadãos de primeira classe no Spring Boot 3 e no Spring Framework 6. Não há nada que deva ser adicionado ao seu projeto para
compilá-lo no modo nativo.
Adicionamos suporte nativo GraalVM em start.spring.io, mas isso foi para fornecer suporte adicional ao
spring-boot-maven-plugin. Isso trouxe o plugin hibernate-enhance-maven- para ajudar a garantir que
estávamos construindo TODOS os metadados necessários para funcionar corretamente com o GraalVM.
Mas todo o processamento AOT e gerenciamento de metadados usado para fazer os aplicativos nativos funcionarem está na
versão mais recente do portfólio Spring.
O portfólio Spring está sendo adaptado para suportar GraalVM. A maioria de seus projetos apoia isso. Mas isso não significa que
todas as bibliotecas de terceiros que escolhemos sejam suportadas (ainda). Conforme declarado na documentação de referência
do Spring Boot, “as imagens nativas do GraalVM são uma tecnologia em evolução e nem todas as bibliotecas fornecem suporte”.
A equipe do Spring está trabalhando diligentemente não apenas para garantir que TODOS os seus módulos eventualmente
suportem imagens nativas, mas também estão trabalhando diretamente com a equipe do GraalVM para garantir que o próprio
GraalVM funcione corretamente.
Fique ligado em spring.io/blog para postagens futuras sobre melhorias em imagens nativas.
Resumo
Neste capítulo, aprendemos como construir imagens nativas usando GraalVM. Esta é uma versão mais rápida e eficiente do nosso
aplicativo do que jamais poderíamos construir usando a JVM padrão. Também aprendemos como incorporar imagens nativas em
um contêiner Docker usando Paketo Buildpacks.
No próximo capítulo, aprenderemos como tornar os aplicativos Spring Boot ainda mais eficientes, mergulhando nas águas da
programação reativa.
Machine Translated by Google
Parte 4:
Dimensionando um
aplicativo com Spring Boo
Às vezes, precisamos extrair mais desempenho de um conjunto existente de servidores. Embora fosse
bom simplesmente comprar mais servidores, existe outra maneira. Você aprenderá como a programação
reativa pode tornar seu aplicativo Spring Boot muito mais eficiente.
9
Escrita Reativa
Controladores da Web
Nos oito capítulos anteriores, reunimos todos os componentes principais necessários para construir um
aplicativo Spring Boot. Nós o empacotamos dentro de um contêiner Docker e até o ajustamos para rodar em
modo nativo no GraalVM em vez da JVM padrão.
Mas e se, depois de fazer tudo isso, nossa aplicação ainda sofresse muito tempo ocioso? E se nosso aplicativo
estivesse consumindo nossa conta de nuvem por ter que hospedar um grande número de instâncias apenas para
atender às nossas necessidades atuais?
Em outras palavras, existe outra maneira de extrair muito mais eficiência de tudo isso, sem abrir mão do Spring Boot?
• Descobrir exatamente o que é programação reativa e por que devemos nos preocupar
Não me entenda mal. As pessoas administram sistemas enormes com algum senso de poder. Mas as promessas das
construções multithread têm sido altas, sua implementação é complicada e francamente difícil de acertar, e os resultados
têm sido escassos.
As pessoas ainda acabam executando 10.000 instâncias de um serviço altamente necessário, o que pode resultar em uma
fatura mensal gigantesca caso hospedemos nosso aplicativo no Azure ou AWS.
Mas e se houvesse outra maneira? E se o conceito de muitos threads e muitas trocas fosse um fogo-fátuo?
Introdução ao Reativo
A evidência está aí. Kits de ferramentas JavaScript reativos no navegador, um ambiente onde há apenas um thread,
mostraram uma capacidade incrível. É isso mesmo: um ambiente de thread único pode ser dimensionado e ter um
desempenho poderoso se for abordado de maneira adequada.
“Reactive Streams é uma iniciativa para fornecer um padrão para processamento de fluxo
assíncrono com contrapressão sem bloqueio. Isso abrange esforços voltados para ambientes
de tempo de execução (JVM e JavaScript), bem como protocolos de rede.”
Uma característica importante observada é que fluxos de dados rápidos não podem ultrapassar o destino do fluxo. Os Fluxos
Reativos abordam essa preocupação introduzindo um conceito conhecido como contrapressão.
Os sinais de contrapressão também são incorporados ao padrão de forma que o encadeamento de vários componentes do
Reactive Streams resulta em contrapressão em toda a aplicação.
Machine Translated by Google
Existe até o RSocket, um protocolo de rede da camada 7. É análogo ao HTTP , pois é executado sobre o TCP
(ou WebSockets/Aeron) e é independente de linguagem, mas vem com contrapressão integrada. Os
componentes do Reactive Streams podem se comunicar pela rede de maneira puramente reativa, com controle adequado.
Não é incomum em sistemas tradicionais ser forçado a encontrar o ponto de ruptura. Em algum lugar do sistema está o
ponto onde algum componente está ficando sobrecarregado. Uma vez resolvido o problema, o problema simplesmente
passa para o próximo ponto, o que geralmente não é óbvio até que o problema principal seja resolvido.
• Editor: um componente que produz uma saída, seja uma saída ou uma quantidade infinita
• Assinante: um componente que está recebendo de um Publicador
• Assinatura: Captura os detalhes necessários para que os Assinantes comecem a consumir conteúdo
dos editores
Embora isso seja simples, é francamente simples demais. É recomendado encontrar um kit de ferramentas que
implemente as especificações e forneça mais estruturas e suporte para construir aplicações.
A outra coisa importante a entender sobre os fluxos reativos é que eles vêm com sinais. Cada vez que os dados
são manipulados ou ações são executadas, eles são associados a um sinal. Mesmo que não haja troca de dados,
os sinais ainda são tratados. Isso significa que fundamentalmente não existem métodos nulos na programação reativa.
Isso porque mesmo sem resultados de dados, ainda há a necessidade de enviar e receber sinais.
No restante deste livro, usaremos a implementação de Reactive Streams da equipe Spring, conhecida
como Project Reactor. É importante entender que embora o Project Reactor seja produzido pela equipe
Spring, o Reactor em si não possui nenhuma dependência do Spring. Reactor é uma dependência central
adquirida pelo Spring Framework, Spring Boot e o restante do portfólio Spring. Mas é um kit de ferramentas por si só.
Isso significa que não usaremos Reactive Streams diretamente, mas sim a implementação dele
pelo Project Reactor. Mas é bom entender de onde vem e como é possível integrar com outros
implementações da especificação, como RxJava 3.
Project Reactor é um kit de ferramentas fortemente baseado nos recursos de programação funcional do Java 8
combinados com funções lambda.
Machine Translated by Google
Este fragmento de código do Reactor tem alguns aspectos importantes, conforme mostrado aqui:
• Fluxo: Este é o tipo de fluxo de dados reativo do Reactor de 0 ou mais unidades de dados, cada uma chegando em algum
ponto no futuro.
• map(): Semelhante ao método map() do Java 8 Stream, ele permite transformar cada elemento de
dados em outra coisa, até mesmo de um tipo diferente. Neste cenário, ele converte a string em maiúsculas.
Este pedaço de código pode ser descrito como um fluxo ou uma receita reativa. Cada linha é capturada como um
objeto de comando em um processo conhecido como assembly. Algo que não é tão óbvio é que montagem não é o
mesmo que executar coisas.
Quando se trata de Fluxos Reativos, é importante entender que nada acontece até você se inscrever.
Uma vez estabelecida uma Assinatura, o Assinante pode emitir uma solicitação(n), solicitando n itens.
O Publicador pode então começar a publicar os itens por meio do sinal onNext do Assinante. Este
Publicador é livre e claro para publicar até (mas não excedendo) n invocações do método onNext.
O editor pode continuar enviando conteúdo ou pode sinalizar que não há mais por meio do sinal
onComplete.
Machine Translated by Google
Agora, tudo isso é bastante simples e ainda assim… um pouco tedioso. A recomendação é deixar a estrutura lidar
com isso. Os desenvolvedores de aplicativos são incentivados a escrever seus aplicativos em um nível superior,
permitindo que a estrutura faça todas as cerimônias reativas.
E assim, na próxima seção, veremos como Spring WebFlux e Project Reactor tornam super simples
construir um controlador web de forma reativa.
• Projeto: Maven
• Linguagem: Java
• Grupo: com.springbootlearning.learningspringboot3
• Artefato: capítulo 9
• Nome: Capítulo 9
• Embalagem: Frasco
• Java: 17
Com os metadados deste projeto selecionados, podemos agora começar a escolher nossas dependências. Agora, em vez
de adicionar coisas novas, como fizemos nos capítulos anteriores, estamos começando do zero com as seguintes opções:
É isso! Isso é tudo que precisamos para começar a construir um aplicativo da web reativo. Posteriormente neste
capítulo e no próximo capítulo, revisitaremos isso para adicionar novos módulos.
Clique em GERAR e baixe o arquivo ZIP; teremos um pequeno aplicativo da web saboroso com os seguintes itens
principais no arquivo de compilação pom.xml:
• reactor-test: módulo de teste do Project Reactor com ferramentas adicionais para ajudar a testar aplicativos reativos,
incluído automaticamente em qualquer aplicativo reativo
Não nos aprofundamos em todas as complexidades da programação reativa, mas uma coisa necessária é um
contêiner da Web que não fique preso no bloqueio de APIs. É por isso que temos o Reactor Netty, uma biblioteca
do Project Reactor que envolve o Netty sem bloqueio com ganchos do Reactor.
E esteja ciente de que o teste é vital. É por isso que os dois módulos de teste também estão incluídos. E
certamente aproveitaremos o módulo de teste do Reactor posteriormente neste capítulo e no próximo.
Mas antes de podermos fazer tudo isso, precisamos nos familiarizar com a escrita de um método web reativo, conforme
mostrado na próxima seção.
Na seção anterior, vimos um uso simples do tipo Flux do Reactor. Flux é a implementação do Publisher do
Reactor e fornece um punhado de operadores reativos.
@RestController
• @RestController: anotação do Spring Web para indicar que este controlador envolve dados,
não modelos
• @GetMapping: anotação do Spring Web para mapear chamadas web HTTP GET /api/employees
neste método
Flux é como combinar uma lista Java clássica com um futuro. Mas não realmente.
Machine Translated by Google
As listas podem conter vários itens, mas um Flux não contém todos de uma vez. E os Flux não são
consumidos por meio de iteração clássica ou loops for. Em vez disso, eles vêm com muitos recursos orientados a fluxo
operações, como mapa, filtro, flatMap e muito mais.
Quanto a ser semelhante a um Futuro, isso é verdade apenas no sentido de que quando um Fluxo é formado, seus
elementos contidos geralmente ainda não existem, mas chegarão no futuro. Mas o tipo Future do Java, anterior ao Java
8, possui apenas uma operação get. Conforme mencionado no parágrafo anterior, o Flux possui um rico conjunto de operadores.
Além de tudo isso, o Flux tem diferentes maneiras de mesclar várias instâncias do Flux em uma única, conforme
mostrado aqui:
• concatWith: Um operador Flux que combina aeb em um único Flux onde todos os elementos de a são
emitidos antes dos elementos de b
• mergeWith: Um operador Flux que combina aeb em um único Flux onde os elementos são emitidos à
medida que chegam em tempo real, permitindo a intercalação entre a e b
Você não pré-carregou aquele método Flux na web com dados codificados?
Sim, este exemplo desafia a natureza futurística do Flux em aplicações do mundo real, já que não pré-
carregamos um Flux usando o método justo. Em vez disso, é mais provável que conectemos uma fonte
de dados, como um banco de dados reativo ou um serviço de rede remoto. Usando APIs mais sofisticadas
do Flux, é possível emitir entradas à medida que elas ficam disponíveis no Flux para consumo downstream.
No método web que definimos primeiro, o Flux de dados é entregue ao Spring WebFlux, que então serializará
o conteúdo e fornecerá uma saída JSON.
É importante observar que todo o tratamento de sinais de Fluxos Reativos, incluindo assinatura, solicitação,
chamadas onNext e, finalmente, a invocação onComplete, é tratado pelo framework!
É vital entender que, na programação reativa, nada acontece até que nos inscrevamos. As chamadas da Web
não são feitas e as conexões com o banco de dados não são abertas. Os recursos não são alocados até que
alguém se inscreva. Todo o sistema foi projetado desde o início para ser preguiçoso.
Mas para métodos web, deixamos que o framework faça a inscrição para nós.
Agora, vamos aprender como criar um método web que consuma dados de forma reativa.
Machine Translated by Google
@PostMapping("/api/funcionários")
Mono<Employee> add(@RequestBody Mono<Employee> newEmployee)
{
retornar novoFuncionário //
.map(funcionário -> {
DATABASE.put(employee.nome(), funcionário);
funcionário que retorna;
});
}
• @PostMapping: anotação do Spring Web para mapear chamadas web HTTP POST /api/employees para
este método
• @RequestBody: esta anotação informa ao Spring Web para desserializar a solicitação HTTP recebida
body em um tipo de dados Employee
Os dados recebidos são agrupados dentro de um Reactor Mono. Esta é a contrapartida de item único de um Reactor
Flux. Ao mapeá-lo, podemos acessar seu conteúdo. O Reactor Mono também oferece suporte a muitos operadores,
assim como o Flux.
Embora possamos transformar o conteúdo, nesta situação estamos simplesmente armazenando o conteúdo em nosso
BANCO DE DADOS e depois devolvendo-o sem alterações.
Isso ocorre porque o Project Reactor lida perfeitamente com duas coisas importantes nos bastidores. A primeira é que
cada etapa desses pequenos fluxos ou receitas não é realizada diretamente. Em vez disso, toda vez que escrevemos
um mapa, um filtro ou algum outro operador do Reactor, estamos montando coisas. Não é quando a execução acontece.
Cada uma dessas operações monta um pequeno objeto de comando com todos os detalhes necessários para
realizar nossas ações. Por exemplo, a instrução no bloco de código anterior onde armazenamos o valor em DATABASE
e, em seguida, retornar o valor está todo encapsulado em uma função lambda Java 8 . Não há nenhum requisito
de que quando o método do controlador é invocado, esta função lambda interna seja chamada ao mesmo tempo.
O Project Reactor reúne todos esses objetos de comando que representam nossas ações e os empilha em filas de
trabalho internas. Em seguida, ele delega a execução ao seu Agendador integrado. Isso permite que o Reactor decida
exatamente como realizar as coisas. Existem vários agendadores para escolher, incluindo um único encadeamento
para um conjunto de encadeamentos, um Java ExecutorService e um agendador elástico limitado sofisticado.
O Agendador de sua escolha resolve seu acúmulo de trabalho à medida que os recursos do sistema ficam disponíveis.
Ao fazer com que cada etapa de um fluxo do Reactor atue de maneira preguiçosa e sem bloqueio, sempre que houver
um atraso no limite de E/S, o thread atual não ficará parado aguardando uma resposta. Em vez disso, o Agendador
volta para essa fila interna de trabalho e escolhe uma tarefa diferente para realizar. Isso é conhecido como roubo de
trabalho e permite que problemas tradicionais de latência se transformem em oportunidades para concluir outro
trabalho, resultando em melhor rendimento geral.
Como disse uma vez o líder da equipe Spring Data, Mark Paluch: “A programação reativa é baseada na reação à
disponibilidade de recursos”.
Anteriormente, mencionamos que o Reactor faz duas coisas. A segunda coisa é que, em vez de ter um pool de
threads gigante com 200 threads, o padrão é um Scheduler usando um pool com um thread por núcleo.
Além disso, Java tem muitas APIs inovadoras integradas em seu núcleo. Embora tenhamos recebido ferramentas
desde o início, como métodos e blocos sincronizados, juntamente com bloqueios e semáforos, é realmente difícil
fazer isso de maneira eficaz e correta.
Foi fácil: A) fazer certo, mas não aumentar o rendimento, B) melhorar o rendimento, mas introduzir conflitos, ou C)
introduzir conflitos e não melhorar o rendimento. E essas táticas muitas vezes exigem reescrever o aplicativo de uma
forma não intuitiva.
Machine Translated by Google
Um thread por núcleo, quando combinado com roubo de trabalho preguiçoso e sem bloqueio, pode ser MUITO mais eficiente.
É claro que a codificação com o Project Reactor não é invisível. Há um estilo de programação a ser incorporado,
mas como ele é fortemente inclinado para o mesmo estilo de programação do Java 8 Streams, que foi
amplamente adotado, não é a grande questão que era a programação simultânea Java dos primeiros dias.
Esta é também a razão pela qual absolutamente todas as partes do aplicativo precisam ser escritas dessa maneira. Imagine
uma máquina de 4 núcleos que possui apenas 4 threads do Reactor. Se um desses threads encontrasse um código de
bloqueio e fosse forçado a esperar, isso afetaria 25% do rendimento total.
Esta é a razão pela qual o bloqueio de APIs encontradas em locais como JDBC, JPA, JMS e servlets é um problema
profundo para a programação reativa.
Todas essas especificações foram construídas com base em paradigmas de bloqueio, portanto não são adequadas para
aplicações reativas, que exploraremos com mais detalhes no próximo capítulo, Trabalhando com dados reativamente.
Já que estamos falando de programação reativa, faz sentido escolher um mecanismo de modelagem que não bloqueie.
Portanto, neste capítulo, usaremos o Thymeleaf.
Para começar, primeiro precisamos atualizar o aplicativo que começamos a construir no início deste capítulo. Para fazer isso,
vamos revisitar https://start.spring.io.
Já fizemos essa dança nos capítulos anteriores. Em vez de criar um projeto inteiramente novo e começar de novo (ugh!),
inseriremos todos os mesmos metadados do projeto mostrados anteriormente neste capítulo na seção Criando um aplicativo
Spring Boot reativo.
• Folha de tomilho
Agora, em vez de usar GENERATE como fizemos da última vez, aperte o botão EXPLORE . Isso fará com que a
página da web forneça uma visualização on-line deste projeto barebones, mostrando-nos o arquivo de compilação pom.xml.
Tudo deve ser igual ao arquivo pom.xml que baixamos anteriormente, com uma diferença: aquela nova dependência para
spring-boot-starter-thymeleaf. Tudo o que precisamos fazer é isto:
Este iniciador extra do Spring Boot fará o download do Thymeleaf, um mecanismo de modelagem que não apenas se
integra perfeitamente ao Spring Boot, mas também vem carregado com suporte reativo. Isso nos prepara para escrever um
controlador web reativo para modelos, conforme mostrado na próxima seção.
@Controlador
• @Controller: anotação do Spring Web para indicar que esta classe contém métodos web que
renderizar modelos.
• @GetMapping: anotação do Spring Web para mapear chamadas GET/web para este método.
• Mono<Rendering>: Mono é o tipo reativo de valor único do Reactor. Renderização é o tipo de valor do
Spring WebFlux que nos permite passar o nome da visualização para renderizar junto com os atributos
do modelo.
• Flux.fromIterable(): Este método auxiliar estático nos permite agrupar qualquer Java Iterable e então usar
nossas APIs reativas.
• DATABASE.values(): Esta é uma fonte de dados temporária até chegarmos ao Capítulo 10, Trabalhando
com Dados Reativamente.
• map(): Esta operação nos permite acessar a lista dentro daquele Mono onde a transformamos em uma
Rendering. O nome da visualização que desejamos renderizar é “index”. Também carregamos o atributo
“employees” do modelo com os valores encontrados dentro deste Mono.
• build(): A renderização é uma construção de construtor, então esta é a etapa que transforma todas as peças em
uma instância final e imutável. É importante entender que quando estiver dentro do map()
operação, a saída é um Mono<Rendering>.
Existem alguns outros aspectos deste método web que são importantes para entender.
Primeiro de tudo, a operação map() no final da cadeia tem como objetivo transformar o tipo que está dentro do Mono.
Neste caso, ele converte List<Employee> em Rendering, mantendo tudo dentro deste Mono. Isso é feito
descompactando o Mono<List<Employee>> original e usando os resultados para criar um novo Mono<Rendering>.
Outra coisa que é importante reconhecer é que não estamos usando uma fonte de dados real. Estes são dados predefinidos
armazenados em um mapa Java básico. É por isso que pode parecer um pouco estranho agrupar uma lista Java de Employee
objetos em um Flux usando fromIterable, apenas para extraí-los de volta usando collectList.
Isso é para ilustrar a situação do mundo real que frequentemente enfrentamos ao receber uma coleção Iterable.
O curso de ação adequado é o que é mostrado no código: envolva-o em um Flux e, em seguida, execute nossas
várias transformações e filtros, até entregá-lo aos manipuladores da web do Spring WebFlux para ser renderizado
com Thymeleaf.
A etapa final na construção de uma solução baseada em modelo é criar um novo arquivo, index.html, abaixo de src/
main/resources/templates, conforme mostrado aqui:
<html xmlns:th="http://www.thymeleaf.org">
<cabeça>
<corpo>
<h2>Funcionários</h2>
<ul>
<li th:each="funcionário: ${funcionários}">
<div th:text=
"${employee.nome + ' (' + funcionário.role + ')'}">
</div>
</li>
</ul>
</body>
</html>
• th:each: o operador for-each do Thymeleaf, fornecendo um nó <li> para cada entrada no atributo
do modelo Employees. Em cada nó, funcionário é a variável substituta.
• th:text: Diretiva do Thymeleaf para inserir texto no nó. Nesta situação, estamos concatenando
dois atributos do registro do funcionário com strings.
Outra coisa que provavelmente não está visível é que TODAS as tags HTML neste modelo estão fechadas. Ou
seja, nenhuma tag pode ser deixada aberta devido ao analisador baseado em DOM do Thymeleaf. A maioria das
tags HTML possui uma tag de abertura e fechamento, mas algumas não, como <IMG>. Ao usar tais tags no
Thymeleaf, devemos fechá- las, seja com uma tag </IMG> correspondente ou usando o atalho <IMG/>.
Se você iniciar nosso aplicativo reativo e navegar até http://localhost:8080, verá uma pequena página
da web renderizada:
Machine Translated by Google
Este não seria um grande site se não incluíssemos a capacidade de adicionar informações ao nosso banco de dados de funcionários.
Em geral, para POST um novo objeto, devemos primeiro fornecer um objeto vazio durante a parte GET da operação,
então precisamos atualizar nosso método de índice, conforme mostrado aqui:
@GetMapping("/")
Mono<Renderização> index() {
retornar Flux.fromIterable(DATABASE.values()) //
.collectList() //
.map(funcionários -> Renderização //
.view("índice") //
.modelAttribute("funcionários", funcionários) //
.modelAttribute("novoFuncionário", novo Funcionário("", ""))
.construir());
}
Este código é igual à versão anterior, exceto pela linha destacada. Ele introduz um novo atributo de
modelo, newEmployee, contendo um objeto Employee vazio. Isso é tudo o que você precisa para
começar a criar um formulário HTML com Thymeleaf.
• th:action: diretiva do Thymeleaf para formar uma URL para uma rota que codificaremos mais adiante
processar novos registros de funcionários
• th:object: diretiva do Thymeleaf para vincular esse formulário HTML ao registro newEmployee que foi
fornecido como um atributo de modelo em nosso método de índice atualizado
O resto é HTML 5 <form> padrão, sobre o qual não entraremos. As partes expostas são a cola necessária para
conectar o processamento de formulários HTML ao Spring WebFlux.
A última etapa é codificar um manipulador POST em nosso HomeController, conforme mostrado aqui:
@PostMapping("/novo-funcionário")
Mono<String> newEmployee(@ModelAttribute Mono<Employee>
novo empregado) {
retornar novoFuncionário //
.map(funcionário -> {
DATABASE.put(employee.nome(), funcionário);
retornar "redirecionar:/";
});
}
• @PostMapping: anotação do Spring Web para mapear chamadas web POST/novos funcionários para
este método.
• @ModelAttribute: anotação do Spring Web para sinalizar que esse método se destina a consumir formulários
HTML (em vez de algo como um corpo de solicitação de aplicativo/json).
Mais uma vez, toda a ação realizada neste método é um fluxo do Reactor que começa com os dados recebidos
e resulta em uma transformação para uma ação de saída. A programação baseada em Reactor geralmente tem
esse estilo em comparação com a programação imperativa clássica, onde mexemos com variáveis intermediárias.
Nesta página, o usuário está inserindo um novo registro de funcionário. Depois de clicar em Enviar, o processador POST
entra em ação, armazena o usuário e direciona a página da web de volta para a página inicial.
Isso faz com que uma versão atualizada do DATABASE seja recuperada, conforme mostrado aqui:
Figura 9.3 – Após clicar em Enviar, a página redireciona de volta para a página inicial
Machine Translated by Google
Poderíamos nos aprofundar em todas as várias maneiras de misturar Spring WebFlux e Thymeleaf, mas este é o
Learning Spring Boot 3.0, não o Learning Thymeleaf 3.1. E com isso, vamos examinar outra habilidade valiosa: criar
APIs baseadas em hipermídia.
Hipermídia é o termo usado para se referir ao conteúdo e aos metadados servidos por uma API; esse conteúdo e
metadados indicam o que pode ser feito com os dados ou como encontrar outros dados relacionados.
A hipermídia é algo que vemos todos os dias. Pelo menos nas páginas da web. Isso inclui links de navegação para
outras páginas, links para folhas de estilo CSS e links para efetuar alterações. Isso é bastante comum. Quando
encomendamos algum produto da Amazon, não somos obrigados a fornecer o link para que isso aconteça. A página
da web nos dá isso.
Hipermídia em JSON é simplesmente o mesmo conceito, mas aplicado a APIs em vez de páginas visuais da web.
Para adicionar Spring HATEOAS a uma aplicação reativa, basta adicionar a seguinte dependência:
<dependência>
<groupId>org.springframework.hateoas</groupId>
<artifactId>primavera-ódioas</artifactId>
</dependency>
E mais uma vez, graças ao gerenciamento de dependências do Spring Boot, não há necessidade de especificar o
número da versão.
Com isso implementado, podemos começar a construir uma API baseada em hipermídia. Primeiro, crie uma
classe chamada HypermediaController.java, assim:
@RestController
@EnableHypermediaSupport(tipo = HAL)
classe pública HypermediaController {
}
• @RestController: anotação do Spring Web para marcar este controlador como focado na serialização
JSON em vez da renderização de modelo
Se tivéssemos usado Spring Boot Starter HATEOAS, o suporte HAL teria sido ativado automaticamente.
Mas como estamos conectando manualmente o Spring HATEOAS, devemos ativá-lo nós mesmos.
Importante
A anotação @EnableHypermediaSupport só precisa ser usada uma vez. Por uma questão de brevidade
neste livro, estamos instalando nosso controlador hipermídia. Em uma aplicação real, pode ser preferível
colocá-lo na mesma classe que possui a anotação @SpringBootApplication.
Com tudo isso implementado, vamos começar construindo um endpoint hipermídia para um recurso de item único, um
funcionário, conforme mostrado aqui:
@GetMapping("/hypermedia/employees/{chave}")
Mono<EntityModel<Employee>> funcionário(@PathVariable String
chave) {
Mono<Link> selfLink = linkTo( //
métodoOn(HypermediaController.class) //
Machine Translated by Google
.funcionário(chave)) //
.withSelfRel() //
.toMono();
Esta implementação para o Employee de item único é carregada com conteúdo, então vamos desmontá-la:
• @GetMapping: anotação do Spring Web para indicar que servirá HTTP GET /
métodos hipermídia/funcionário/{chave}.
• linkTo(): função auxiliar estática do Spring HATEOAS para extrair um link de um Spring WebFlux
invocação de método.
• methodOn(): função auxiliar estática do Spring HATEOAS para executar uma invocação fictícia de um
método web do controlador para coletar informações para construir links. No primeiro uso, estamos
apontando para o método Employee(String key) do HypermediaController. No segundo uso, estamos
apontando para o método Employees() do HypermediaController
(ainda não escrito).
• withSelfRel(): método do Spring HATEOAS para rotular selfLink com uma hipermídia própria
relação (que veremos em breve).
• toMono(): método do Spring HATEOAS para pegar todas as configurações de link building e transformá-las
em um Mono<Link>.
• Mono.zip(): Operador do Reactor para combinar duas operações Mono e processar os resultados
quando ambos estiverem completos. Existem outros utilitários para conjuntos maiores, mas esperar por
dois é tão comum que zip() é uma espécie de atalho.
• links.map(): mapeamos Tuple2 do objeto Mono<Link>, extraindo os links e agrupando-os com o funcionário
buscado em um objeto Spring HATEOAS EntityModel.
Ele combina dados (que usamos o tempo todo) com hiperlinks. Os hiperlinks são representados usando o
tipo Link do Spring HATEOAS. O kit de ferramentas está repleto de operações para facilitar a criação do Link
objetos e mesclá-los com dados. O bloco de código anterior mostrou como extrair links de um método Spring
WebFlux.
Para que o Spring HATEOAS renderize essa fusão de dados e links, precisamos que qualquer endpoint
com tecnologia hipermídia retorne um objeto Spring HATEOAS RepresentationModel ou um de seus subtipos.
A lista não é longa e é mostrada aqui:
• RepresentationModel: O tipo principal para dados e links. Uma opção para um tipo de hipermídia de item
único é estender essa classe e mesclar nossos valores de negócios com ela.
• EntityModel<T>: A extensão genérica de RepresentationModel. Outra opção é injetar nosso objeto de negócio
em seus métodos construtores estáticos. Isso nos permite manter os links e a lógica de negócios separados
uns dos outros.
• PagedModel<T>: A extensão de CollectionModel que representa uma página de objetos com reconhecimento
de hipermídia.
É importante entender que um objeto com reconhecimento de hipermídia de item único pode ter um conjunto de
links, enquanto uma coleção de objetos com reconhecimento de hipermídia pode ter um conjunto diferente de links.
Para representar adequadamente uma coleção rica de objetos com reconhecimento de hipermídia, ela pode ser
capturada como CollectionModel<EntityModel<T>>.
Isto implicaria que toda a coleção pode ter um conjunto de links, como um link para a raiz agregada. E cada entrada
da coleção pode ter um link personalizado apontando para seu método de recurso de item único, enquanto todos
eles têm um link de volta para a raiz agregada.
Machine Translated by Google
Para ver isso melhor, vamos implementar essa raiz agregada – a extremidade com reconhecimento de hipermídia mencionada
no bloco de código anterior:
@GetMapping("/hipermídia/funcionários")
Mono<CollectionModel<EntityModel<Employee>>> funcionários() {
Mono<Link> selfLink = linkTo( //
métodoOn(HypermediaController.class) //
.funcionários()) //
.withSelfRel() //
.toMono();
retornar selfLink //
.flatMap(self -> Flux.fromIterable(DATABASE.keySet()) //
.flatMap(chave -> funcionário(chave)) //
.collectList() //
.map(entityModels -> CollectionModel.of(entityModels,
auto)));
}
Parte deste método deve ser muito semelhante ao bloco de código anterior. Vamos nos concentrar nas diferenças:
• selfLink neste método aponta para este método, que é um ponto final fixo.
Se isso parece muito mais complexo do que os métodos anteriores deste capítulo (ou dos capítulos anteriores),
é porque é. Mas conectar os métodos do controlador da web diretamente à renderização da saída hipermídia
garante que futuros ajustes e ajustes em nossos métodos sejam ajustados adequadamente.
Machine Translated by Google
% curl -v localhost:8080/hipermídia/funcionários | jq {
"_embedded":
{ "employeeList": [
{
"name": "Frodo Bolseiro", "role":
"portador do anel", "_links":
{ "self": { "href":
"http://
localhost:8080/hypermedia/empregados/Frodo%20Baggins"
},
"funcionários": {
"href": "http://localhost:8080/hypermedia/
funcionários"
}
}
},
{
"nome": "Samwise Gamgee", "role":
"jardineiro", "_links": { "self":
{ "href": "http://
localhost:8080/
hypermedia/empregados/Samwise%20Gamgee"
},
"funcionários": {
"href": "http://localhost:8080/hypermedia/
funcionários"
}
} },
{
"nome": "Bilbo Bolseiro",
"papel": "ladrão",
Machine Translated by Google
"_links": { "self":
{ "href": "http://
localhost:8080/hypermedia/empregados/Bilbo%20Baggis"
},
"funcionários": {
"href": "http://localhost:8080/hypermedia/empregados"
}}
]
},
"_links": { "self":
{ "href": "http://
localhost:8080/hypermedia/
funcionários"
}
}
}
• _link: Formato HAL para mostrar um link hipermídia. Ele contém uma relação de link
(por exemplo, self) e um href (por exemplo, http://localhost:8080/hypermedia/employees)
• O auto-link da coleção está na parte inferior, enquanto os dois objetos Employee de item único têm, cada um,
um self apontado para si mesmo, bem como um link de funcionários apontado para a raiz agregada
Fica como exercício para você explorar a saída HAL de item único.
Imagine um sistema onde não só temos dados baseados nos funcionários, mas também outras operações diversas.
Por exemplo, poderíamos construir uma série de funções como takePTO, fileExpenseReport e
contactManager. Qualquer coisa, mesmo.
Construir uma coleção de links que cruzam vários sistemas e aparecem e desaparecem com base em quando são
válidos torna possível mostrar/ocultar botões em aplicativos da web com base em sua relevância.
Resumo
Neste capítulo, aprendemos várias habilidades importantes, incluindo a criação de um aplicativo reativo usando o
Project Reactor, a implementação de um método web reativo para servir e consumir JSON e como aproveitar o
Thymeleaf para gerar HTML de forma reativa e consumir um formulário HTML. Até usamos Spring HATEOAS para
gerar reativamente uma API com reconhecimento de hipermídia.
Todos esses recursos são os blocos de construção de aplicativos da web. Embora tenhamos usado um estilo
funcional Java 8 para encadear as coisas, fomos capazes de reutilizar as mesmas anotações Spring Web que
usamos ao longo deste livro.
E usando o estilo do Reactor, um paradigma bastante semelhante ao Java 8 Streams, podemos potencialmente ter
uma aplicação muito mais eficiente.
Isso conclui este capítulo! No próximo capítulo, encerraremos tudo mostrando como trabalhar de forma reativa com
dados reais.
Machine Translated by Google
10
Trabalhando com dados de forma reativa
No capítulo anterior, aprendemos como escrever um controlador web reativo usando Spring WebFlux. Nós o
carregamos com dados predefinidos e usamos um mecanismo de modelagem reativo, Thymeleaf, para criar um
frontend HTML. Também criamos uma API reativa com JSON puro e depois com hipermídia usando Spring
HATEOAS. No entanto, tivemos que usar dados enlatados. Isso porque não tínhamos um armazenamento de
dados reativos disponível, problema que resolveremos neste capítulo.
• Experimentando R2DBC
Existem poucos aplicativos que não usam um banco de dados para gerenciar seus dados. E nesta era de sites de comércio
eletrônico servindo comunidades globais, nunca houve uma escolha mais ampla de vários tipos de bancos de dados, sejam eles
relacionais, de valor-chave, de documentos ou qualquer outro.
Isso pode tornar difícil escolher o caminho certo para nossas necessidades. É ainda mais difícil considerando que até mesmo um
banco de dados precisa ser acessado de forma reativa.
Machine Translated by Google
Isso mesmo. Se não acessarmos nosso banco de dados usando as mesmas táticas reativas apresentadas no capítulo
anterior, todos os nossos esforços seriam em vão. Repetindo um ponto-chave: todas as partes do sistema devem ser
reativas. Caso contrário, corremos o risco de uma chamada de bloqueio amarrar um thread e prejudicar nosso rendimento.
O tamanho padrão do pool de threads do Project Reactor é o número de núcleos na máquina operacional. Isso porque vimos que a
troca de contexto é cara. Por não ter mais threads do que núcleos, garantimos que nunca teremos que suspender um thread, salvar
seu estado, ativar outro thread e restaurar seu estado.
Ao eliminar uma operação tão cara, os aplicativos reativos podem se concentrar na tática mais eficaz de
simplesmente voltar ao tempo de execução do Reactor para a próxima tarefa (também conhecido como roubo de
trabalho). No entanto, isso só é possível quando usamos os tipos Mono e Flux do Reactor junto com seus diversos operadores.
Se algum dia invocarmos alguma chamada de bloqueio em um banco de dados remoto, todo o thread será interrompido, aguardando
uma resposta. Imagine uma máquina de quatro núcleos com um de seus núcleos bloqueado dessa forma. Um sistema de quatro
núcleos usando apenas três núcleos apresentaria uma queda instantânea de 25% no rendimento.
Esta é a razão pela qual os sistemas de banco de dados em todo o espectro estão implementando drivers alternativos que usam a
especificação Reactive Streams: MongoDB, Neo4j, Apache Cassandra, Redis e muito mais.
Mas como é exatamente um driver reativo? Os drivers de banco de dados cuidam do processo de abertura de uma conexão com um
banco de dados, analisando consultas e transformando-as em comandos e, por fim, conduzindo os resultados de volta ao chamador.
A popularidade da programação baseada em fluxos reativos tem crescido, o que motivou vários fornecedores a criar drivers reativos.
Quando se trata de Java, todos os kits de ferramentas, drivers e estratégias passam pelo JDBC para se comunicarem com um banco
de dados relacional. jOOQ, JPA, MyBatis e QueryDSL usam JDBC nos bastidores. E como o JDBC está bloqueando, ele
simplesmente não funcionará em um sistema reativo.
Tem certeza?
As pessoas perguntaram de várias maneiras por que não podemos simplesmente criar um pool de threads JDBC e colocar
um proxy compatível com o reator na frente dele. A verdade é que, embora cada solicitação recebida possa ser despachada
para um pool de threads, você corre o risco de atingir o limite do pool. Nesse ponto, a próxima chamada reativa seria
bloqueada, aguardando a liberação de um thread, destruindo efetivamente todo o sistema. O objetivo dos sistemas reativos
é NÃO bloquear, mas sim ceder para que outro trabalho possa ser realizado. Um pool de threads simplesmente atrasa o
inevitável, ao mesmo tempo que custa a sobrecarga da troca de contexto. Um driver de banco de dados, até o ponto de
conversar com o próprio mecanismo de banco de dados, precisa falar Reactive Streams ou não funcionará.
JDBC sendo uma especificação e não simplesmente um driver torna isso praticamente impossível. Mas há esperança, como veremos
na próxima seção.
Machine Translated by Google
Percebendo que o JDBC não seria capaz de mudar o suficiente para oferecer suporte a fluxos reativos e precisando
atender à crescente comunidade de usuários do Spring que queriam se tornar reativos, a equipe do Spring embarcou
em uma nova solução em 2018. Eles elaboraram o Reactive Relational Database Connectivity ( especificação R2DBC) .
O R2DBC como especificação atingiu 1.0 no início de 2022 e, no restante deste capítulo, iremos usá-lo para
construir uma história de dados relacionais reativos.
Se você quiser mais detalhes sobre o R2DBC, confira a apresentação do ex-líder da equipe Spring Data,
Oliver Drotbohm, na conferência SpringOne 2018 em https://springbootlearning.com/r2dbc-2018.
Como queremos algo super simples, podemos usar H2 como nosso banco de dados relacional preferido. H2 é um
banco de dados incorporável na memória. É frequentemente usado para fins de teste, mas podemos usá-lo como
um aplicativo de produção por enquanto.
Junto com H2, também usaremos Spring Data R2DBC. Para obter ambos, vamos visitar nosso velho
amigo, https://start.spring.io. Se escolhermos a mesma versão do Spring Boot do capítulo anterior e
inserirmos os mesmos metadados, poderemos escolher as seguintes dependências:
• Banco de dados H2
<dependência>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependência>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>tempo de execução</scope>
</dependency>
<dependência>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-h2</artifactId>
<scope>tempo de execução</scope>
</dependency>
Machine Translated by Google
É importante entender que o R2DBC é de nível muito baixo. Seu objetivo fundamental é facilitar a
implementação dos autores de drivers de banco de dados. Certos aspectos do JDBC como interface de
driver foram comprometidos para facilitar o consumo pelos aplicativos. O R2DBC procurou remediar isso.
A consequência é que ter um aplicativo conversando diretamente através do R2DBC é bastante complicado.
É por isso que é recomendado usar um kit de ferramentas. Nesse caso, usaremos Spring Data R2DBC, mas você
pode escolher outro de sua preferência, como DatabaseClient do Spring Framework ou outro de terceiros.
Com nossas ferramentas configuradas, é hora de começar a construir um repositório de dados reativos!
ReactiveCrudRepository<Funcionário, Longo> {}
• ReactiveCrudRepository: Interface base do Spring Data Commons para qualquer repositório reativo.
Observe que isso não é específico para R2DBC, mas sim para QUALQUER módulo Spring Data reativo.
• Employee: O tipo de domínio deste repositório (que codificaremos mais adiante neste capítulo).
No capítulo anterior, reunimos um tipo de domínio Employee usando um registro Java 17.
Porém, para interagir com nosso banco de dados, precisamos de algo um pouco mais detalhado do que isso, então
vamos criar o seguinte:
• @Id: anotação do Spring Data Commons para denotar o campo que contém a chave primária.
Observe que esta NÃO é a anotação jakarta.persistence.Id do JPA, mas sim uma anotação específica
do Spring Data.
O restante dos métodos deste tipo de domínio podem ser gerados por qualquer IDE moderno usando seus utilitários.
Experimentando R2DBC
Antes de podermos buscar qualquer dado, precisamos carregar alguns dados. Embora isso normalmente seja algo
com que nosso DBA lida, neste capítulo teremos que fazer isso nós mesmos. Para fazer isso, precisamos criar um
componente Spring que será iniciado automaticamente assim que nosso aplicativo estiver ativo. Crie uma nova
classe chamada Startup e adicione o seguinte código:
@Configuração
classe pública Inicialização {
@Feijão
• @Configuration: anotação do Spring para sinalizar esta classe como uma coleção de definições de bean,
necessária para autoconfigurar nossa aplicação
• @Bean: anotação do Spring para transformar este método em um bean Spring, adicionado ao contexto da aplicação
• CommandLineRunner: interface funcional do Spring Boot para um objeto que é executado automaticamente
assim que o aplicativo é iniciado
• R2dbcEntityTemplate: injete uma cópia deste bean Spring Data R2DBC para que possamos carregar um
pequenos dados de teste
• args -> {}: uma função lambda do Java 8 que é coagida ao CommandLineRunner
O que colocamos dentro desta função lambda do Java 8? Bem, para Spring Data R2DBC, precisamos definir o
esquema nós mesmos. Se ainda não estiver definido externamente (o que não é para este capítulo), precisamos
escrever algo assim:
template.getDatabaseClient() //
.sql("CRIAR TABELA EMPREGADO (id IDENTIDADE NÃO NULO
CHAVE PRIMÁRIA , nome VARCHAR(255), função
VARCHAR(255))") //
.buscar() //
.rowsUpdated() //
.as(StepVerifier::create) //
.expectNextCount(1) //
.verifyComplete();
• sql(): Um método para alimentar uma operação SQL CREATE TABLE. Isso usa o dialeto do H2 para criar
uma tabela EMPLOYEE com um campo de identificação autoincrementável.
• rowsUpdate(): retorna o número de linhas afetadas para que possamos verificar se funcionou.
• as(StepVerifier::create): Operador do Reactor Test para converter todo esse fluxo reativo em StepVerifier.
StepVerifier é outra maneira de forçar convenientemente a execução de um fluxo reativo.
O método anterior nos permite executar um pouco de código SQL para criar nosso esquema básico. E
pode ter ficado um pouco confuso quando, no meio do caminho, mudamos para o StepVerifier do Reactor Test.
StepVerifier é muito útil para testar fluxos de reatores, mas também nos oferece um meio útil para forçar
pequenos fluxos de reatores, ao mesmo tempo que nos permite ver os resultados quando necessário. O único
problema é que não podemos usar isso porque, por padrão, reactor-test tem escopo de teste quando usamos o
Spring Initializr. Para fazer o código anterior funcionar, devemos acessar nosso arquivo pom.xml e remover a
linha <scope>test</scope>. Em seguida, atualize o projeto e ele deverá funcionar!
template.insert(Employee.class) //
.using(new Employee("Frodo Bolseiro", "portador do
template.insert(Employee.class) //
.using(new Employee("Samwise Gamgee",
template.insert(Employee.class) //
.using(new Employee("Bilbo Baggins",
Todas essas três chamadas têm o mesmo padrão. Cada um pode ser descrito da seguinte forma:
A operação insert() na verdade retorna Mono<Employee>. Seria possível inspecionar os resultados, até mesmo
obtendo o valor do id recém-criado. Mas como estamos apenas carregando dados, tudo o que queremos é
confirmar se funcionou.
Na próxima seção, veremos como conectar nosso fornecimento de dados reativos a um controlador de API.
O trabalho pesado foi feito. De agora em diante, podemos aproveitar o que aprendemos no capítulo anterior.
Para construir uma classe controladora de API, crie uma classe chamada ApiController, conforme mostrado aqui:
@RestController
• @RestController: anotação do Spring para sinalizar que esta classe não processa templates
mas em vez disso, todas as saídas são serializadas diretamente para a resposta HTML
• EmployeeRepository: estamos injetando o repositório que definimos anteriormente nesta seção por meio
de injeção de construtor
O mais simples de tudo é devolver todos os registros de Funcionários que temos. Isso pode ser feito facilmente
adicionando o seguinte método à nossa classe ApiController:
@GetMapping("/api/funcionários")
Flux<Funcionário> funcionários() {
retornar repositório.findAll();
}
Machine Translated by Google
O capítulo anterior tinha um mapa Java simples, que exigiu alguns ajustes para fazê-lo funcionar de forma reativa.
Como EmployeeRepository estende ReactiveCrudRepository, ele já possui os tipos reativos incorporados
aos tipos de retorno de seu método – sem necessidade de manipulação!
Isso também significa que a codificação de uma operação POST baseada em API pode ser feita assim:
@PostMapping("/api/funcionários")
Mono<Funcionário> add(@RequestBody Mono<Funcionário>
novo empregado) {
retornar novoEmployee.flatMap(e -> {
Funcionário funcionárioToLoad =
novo Funcionário(e.getName(), e.getRole());
retornar repositório.save(employeeToLoad);
});
}
Se você é novo na programação reativa, a cerimônia dos pontos anteriores pode ser um pouco confusa. Por
exemplo, por que estamos flatMapping? O mapeamento geralmente é usado ao converter de um tipo para
outro. Nessa situação, estamos tentando mapear de um Funcionário recebido para um tipo de Funcionário
recém-salvo. Então por que não mapeamos?
Isso porque o que recebemos de save() não era um objetoEmployee. Era Mono<Funcionário>. Se tivéssemos
mapeado, teríamos Mono<Mono<Employee>>.
A última etapa na montagem de nosso aplicativo web reativo é preencher nosso modelo Thymeleaf, que
abordaremos na próxima seção.
@Controlador
• @Controller: Indica que esta classe de controlador está focada na renderização de templates
Com isso implementado, podemos gerar o modelo da web servido na raiz do nosso domínio usando isto:
@GetMapping("/")
Mono<Renderização> index() { return
repository.findAll() // .collectList() // .map(employees
->
Renderização // .view("index") //
.modelAttribute("funcionários", funcionários) //
.modelAttribute
("novoFuncionário", novo Funcionário("", ""))
.construir());
}
É quase igual ao método index() do capítulo anterior, exceto pelo fragmento destacado:
Agora, para processar esse bean Employee baseado em formulário, precisamos de um método web baseado em POST
como este:
@PostMapping("/novo-funcionário")
Mono<String> newEmployee(@ModelAttribute Mono<Employee>
newEmployee) { return
newEmployee // .flatMap(e ->
{ Funcionário
funcionárioToSave =
novo Funcionário(e.getName(), e.getRole());
retornar repositório.save(employeeToSave); }) // .map(funcionário
->
"redirecionar:/");
}
Machine Translated by Google
Isso é muito semelhante ao método newEmployee() do capítulo anterior, exceto pelas partes destacadas:
• A seção anterior também mostrou como extraímos o nome e a função do objeto Employee recebido, mas ignoramos
qualquer valor de id possível, já que estamos inserindo uma nova entrada. Em seguida, retornamos os resultados
do método save() do nosso repositório.
Algo que é importante ressaltar, quando comparado ao capítulo anterior, é que no código
anterior dividimos as coisas. No capítulo anterior, mapeamos essencialmente o funcionário entrante
objeto em uma solicitação de redirecionamento. Isso porque nosso banco de dados simulado não era reativo e precisava apenas de uma
chamada imperativa para armazenar os dados.
Como nosso EmployeeRepository neste capítulo é reativo, precisamos dividi-lo, com uma operação focada em save(),
seguida pela próxima, para converter esse resultado na solicitação de redirecionamento.
Além disso, tivemos que usar flatMap porque a resposta de save() foi agrupada dentro de uma classe Reactor Mono.
Converter um funcionário para "redirect:/" não envolve nenhum tipo de Reactor, então um mapeamento simples é tudo que
precisamos.
Para obter o modelo index.html, você pode simplesmente copiá-lo do capítulo anterior. Como é igual ao capítulo anterior,
não há necessidade de escrevê-lo aqui – não são necessárias alterações! (Como alternativa, pegue-o na listagem de código-
fonte mostrada no início deste capítulo.)
Resumo
Neste capítulo, aprendemos o que significa buscar dados de forma reativa. Com base nisso, selecionamos um
armazenamento de dados reativo e aproveitamos o Spring Data para ajudar a gerenciar nosso conteúdo. Depois de conectar
as coisas ao nosso controlador web reativo, demos uma olhada no R2DBC, um driver reativo para bancos de dados relacionais.
E com isso, conseguimos construir um aplicativo Spring Boot reativo introdutório.
As mesmas táticas usadas nos capítulos anteriores para implantação funcionarão igualmente bem. Além disso, muitos dos
recursos que usamos ao longo deste livro também funcionam.
Com tudo abordado neste livro, você deve estar preparado para assumir seu próximo (ou talvez atual) projeto usando Spring
Boot 3.0. Sinceramente, espero que o Spring Boot 3.0 deixe você tão animado quanto eu com a criação de novos aplicativos.
Machine Translated by Google
Resumo 239
Enquanto isso, se você quiser explorar ainda mais conteúdo sobre Spring Boot, confira os
seguintes recursos:
Boa codificação!
Machine Translated by Google
Machine Translated by Google
Índice
Símbolos
401 Não autorizado 148 políticas de configuração automática
403 Proibido 148 explorando, no Spring Boot 7-9
A B
Enfileiramento avançado de mensagens contrapressão 204
Conjunto de ferramentas autenticação básica 86 design
Apache Cassandra 56
C
ApacheTomcat11
Dados reativos do Linguagem de consulta Cassandra (CQL) 74
controlador API , fornecendo para liberação Alterar Substituir Atualização Excluir (CRUD) 62
de aplicativos CommandLineRunner 83
242 Índice
86 frontend-maven-plugin 44
programação funcional 101 214
D
busca
G
de dados , carregamento reativo
227, 228, com R2dbcEntityTemplate 233, teste de
repositórios de Aproveitando o Google para autenticar usuários 102
dados 234 , com bancos de dados incorporados Aplicativo Google OAuth 2.0 criando
135-139 testes, com testes 103, 104 aplicativos
simulados 130-134, com Testcontainers 142-146 GraalVM 190, 200 ,
acessando 179
H
aplicações, liberando para 179-182 tags
181 testes troca de usuários
Índice 243
Verbos HTTP
K
protegendo 85-89
Hipermídia como motor de aplicação Kubernetes 186
State (HATEOAS) 9 APIs
adicionando, ao aplicativo 122, 123 repositórios de dados simulados , testando com 130-135
compilador just-in-time (JIT) 191 MongoDB 56
MongoQL 74
Bigode 90
Machine Translated by Google
244 Índice
N coisas de
O
OAuth 102
P
contras 103 Consultar por exemplo
API OAuth2
R
invocando, remotamente 108-112
Objetos Java simples e antigos (POJOs) 61 usado, para servir dados 208, 209
portfólio, iniciadores Spring Boot usados 10, 11 testes para consumir dados recebidos 210
70
Machine Translated by Google
Índice 245
(SOAP) 9 método abstrato único para projetar 58 tarefas Spring HATEOAS 222 Spring Initializr
(SAM) 83 princípio de responsabilidade única link de referência 192
(SRP) 60 teste de fumaça 145 URL 173
246 Índice
usando, benefícios
criando 26
T
protegendo 85-89
57 dados de fiação 7
repositórios de dados, testando com 142-146 YAML não é linguagem de marcação (YAML) 11 mudando
testes para 164, 166
Packt.com
Assine nossa biblioteca digital on-line para ter acesso total a mais de 7.000 livros e vídeos, bem como ferramentas
líderes do setor para ajudá-lo a planejar seu desenvolvimento pessoal e avançar em sua carreira. Para mais
informações, visite nosso site.
• Melhore seu aprendizado com Planos de Habilidades criados especialmente para você
Você sabia que a Packt oferece versões em e-book de todos os livros publicados, com arquivos PDF e ePub
disponíveis? Você pode atualizar para a versão do e-book em packt.com e, como cliente do livro impresso, tem direito
a um desconto na cópia do e-book. Entre em contato conosco em customercare@packtpub.
com para mais detalhes.
Em www.packt.com, você também pode ler uma coleção de artigos técnicos gratuitos, inscrever-se em uma variedade
de boletins informativos gratuitos e receber descontos e ofertas exclusivas em livros e e-books da Packt.
Machine Translated by Google
Se você gostou deste livro, pode estar interessado nestes outros livros de Packt:
ISBN: 978-1-80324-321-4
• Entenda como o uso do Redis para armazenamento em cache pode melhorar o desempenho do seu aplicativo •
Descubra o CORS e como adicionar a política CORS no aplicativo Spring Boot para obter melhor segurança
Juha Hinkula
ISBN: 978-1-80181-678-6
• Crie serviços web rápidos e RESTful com tecnologia Spring Data REST
• Crie e gerencie bancos de dados usando ORM, JPA, Hibernate e muito mais
Empregue React Hooks, props, states e muito mais para criar seu frontend
250
Agora que você terminou o Learning Spring Boot 3.0, adoraríamos ouvir sua opinião! Se você
comprou o livro na Amazon, clique aqui para ir direto para a resenha da Amazon página deste
livro e compartilhe seus comentários ou deixe um comentário no site onde você o comprou.
Sua avaliação é importante para nós e para a comunidade de tecnologia e nos ajudará a garantir que estamos entregando
conteúdo de excelente qualidade.
Machine Translated by Google
251
Você gosta de ler em qualquer lugar, mas não consegue levar seus livros impressos para qualquer lugar?
Não se preocupe, agora com cada livro Packt você obtém uma versão em PDF sem DRM desse livro, sem nenhum custo.
Leia em qualquer lugar, em qualquer lugar, em qualquer dispositivo. Pesquise, copie e cole códigos de seus livros técnicos
favoritos diretamente em seu aplicativo.
As vantagens não param por aí, você pode obter acesso exclusivo a descontos, newsletters e ótimo conteúdo gratuito em sua
caixa de entrada diariamente
https://packt.link/free-ebook/9781803233307
3. É isso! Enviaremos seu PDF grátis e outros benefícios diretamente para seu e-mail
Machine Translated by Google