Você está na página 1de 141

Machine Translated by Google

112 Protegendo um aplicativo com Spring Boot

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:

gravar SearchId(String kind, String videoId, String channelId, String playlistId)


{
}
gravar SearchSnippet (String publicadoAt, String channelId,
Título da string, descrição da string,
Map<String, SearchThumbnail> miniaturas, String
título do canal) {
}

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:

registrar SearchThumbnail (URL da string, largura inteira, número inteiro


altura) {
}

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.

Criando um aplicativo da web com tecnologia OAuth2

Agora é hora de criar um controlador web para começar a renderizar as coisas:

@Controlador

classe pública HomeController {


Machine Translated by Google

Aproveitando o Google para autenticar usuários 113

final privado YouTube YouTube;

public HomeController(YouTube YouTube) {


isto.YouTube = YouTube;
}

@GetMapping
Índice de string (modelo modelo) {
model.addAttribute("canalVídeos", //
youTube.channelVideos("UCjukbYOd6pjrMpNMFAOKYyw",
10, YouTube.Sort.VIEW_COUNT));
retornar "índice";
}
}

O controlador da web anterior tem alguns pontos principais:

• @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

de 10 e usa contagens de visualizações como forma de classificar os resultados da pesquisa.

• O nome do modelo a ser renderizado é index.

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>

<link href="style.css" rel="stylesheet" type="text/css"/>

</head>
<corpo>
<h1>Saudações, fãs do Learning Spring Boot 3.0!</h1>
Machine Translated by Google

114 Protegendo um aplicativo com Spring Boot

<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

Aproveitando o Google para autenticar usuários 115

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:

gravar SearchSnippet (String publicadoAt, String channelId,


Título da string, descrição da string,
Map<String, SearchThumbnail> miniaturas, String
título do canal) {

String shortDescription() {
if (this.description.length() <= 100) {
retorne esta.descrição;
}
retorne esta.descrição.substring(0, 100);
}

Miniatura de miniatura de pesquisa() {


retornar this.thumbnails.entrySet().stream()
.filter(entrada -> entrada.getKey().equals("padrão"))
.findPrimeiro()
.map(Map.Entry::getValue)
.ouElse(nulo);
}
}
Machine Translated by Google

116 Protegendo um aplicativo com Spring Boot

No código anterior, podemos ver que ocorrerá o seguinte:

• O método shortDescription retornará o campo de descrição diretamente ou um


Substring de 100 caracteres

• 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

Aproveitando o Google para autenticar usuários 117

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:

Figura 4.5 – 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

118 Protegendo um aplicativo com Spring Boot

Figura 4.6 – Página de seleção de canais do YouTube

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

Aproveitando o Google para autenticar usuários 119

Figura 4.7 – Modelo Spring Boot com sabor do YouTube

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

120 Protegendo um aplicativo com Spring Boot

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!

Neste capítulo, abordaremos os seguintes tópicos:

• Adicionando JUnit e outros kits de ferramentas de teste ao nosso aplicativo

• Criação de casos de teste baseados em domínio

• Testando controladores web usando MockMVC

• Testando repositórios de dados com simulações

• Testando repositórios de dados com bancos de dados incorporados

• Testar repositórios de dados usando bancos de dados em contêineres

• Testando políticas de segurança com Spring Security Test


Machine Translated by Google

122 Testando com Spring Boot

Onde encontrar o código deste capítulo

O código-fonte deste capítulo pode ser encontrado em https://github.com/PacktPublishing/


Learning-Spring-Boot-3.0/tree/main/ch5.

Adicionando JUnit 5 ao aplicativo


A primeira etapa para escrever casos de teste é adicionar os componentes de teste necessários. A ferramenta de
teste mais amplamente aceita é JUnit. JUnit 5 é a versão mais recente com integrações profundas com Spring
Framework e Spring Boot. (Consulte https://springbootlearning.com/junit-history para obter mais informações sobre
a história das origens do JUnit.)

O que é necessário para adicionar JUnit ao nosso aplicativo?

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:

• Spring Boot Test: utilitários de teste orientados ao Spring Boot

• JSONPath: a linguagem de consulta para documentos JSON

• AssertJ: API fluente para declaração de resultados

• Hamcrest: Biblioteca de matchers

• JUnit 5: Biblioteca fundamental para escrever casos de teste

• Mockito: estrutura de simulação para construção de casos de teste

• JSONassert: Biblioteca de assertivas voltadas para documentos JSON

• Spring Test: utilitários de teste do Spring Framework

• XMLUnit: Kit de ferramentas para verificação de documentos XML

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

Criando testes para seus objetos de domínio 123

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.

O teste é muito importante para a equipe Spring.

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.

Criando testes para seus objetos de domínio


Anteriormente, mencionamos que o teste é uma abordagem multifacetada. Uma das coisas mais críticas em qualquer
sistema são os tipos de domínio. Testá-los é vital. Essencialmente, qualquer coisa publicamente visível para os usuários
é candidata a escrever casos de teste.

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:

classe pública CoreDomainTest {

@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");
}
}

Este código pode ser descrito da seguinte forma:

• 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

124 Testando com Spring Boot

O pacote é JUnit 5, enquanto o último pacote é JUnit 4 (ambos estão no caminho de classe para suportar
compatibilidade com versões anteriores).

• newVideoEntityShouldHaveNullId: O nome do método de teste é importante porque deve transmitir


a essência do que ele verifica. Este não é um requisito técnico, mas sim uma oportunidade de
capturar informações. Este método verifica se quando criamos uma nova instância de
VideoEntity, seu campo id deve ser nulo.

• A primeira linha do método cria uma instância de VideoEntity.

• assertThat(): um método auxiliar estático AssertJ que recebe um valor e o verifica com uma
coleção de cláusulas.

• isNull(): Verifica se o campo id é nulo.

• 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

Criando testes para seus objetos de domínio 125

Ao executar o conjunto de testes, você verá os resultados:

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'}");
}

Este método de teste pode ser descrito da seguinte forma:

• @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

126 Testando com Spring Boot

Combinar asserções ou não combinar asserções?

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.

Para completar, vamos verificar os setters do nosso objeto de domínio:

@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");

Este código pode ser descrito da seguinte forma:

• 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

• O método de teste então passa a exercer todos os métodos setter da entidade

• 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

Testando controladores web com MockMVC 127

Figura 5.3 – Executando classe de teste com análise de cobertura

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.

Testando controladores web com MockMVC


Como as páginas da web são um componente-chave de um aplicativo da web, o Spring vem carregado com ferramentas para verificar facilmente

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

128 Testando com Spring Boot

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)

classe pública HomeControllerTest {

@Autowired MockMvc mvc;

@MockBean VideoService videoService;

@Teste

@WithMockUser

void indexPageHasSeveralHtmlForms() lança exceção {


String html = mvc.perform( //

get("/")) // .andExpect(status().isOk()) // .andExpect( //


content().string( //
containsString("Nome de usuário:
usuário "))) // .andExpect( //

conteúdo().string( //
contémString("Autoridades: [ROLE_USER]"))) // .andReturn() //

getResponse().getContentAsString();

assertThat(html).contains( //
"<form action=\"/logout\"", // "<form action=\"/
search\"", // "<form action=\"/new-video\"");

}
}

Esta pequena classe de teste pode ser explicada da seguinte forma:

• @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

Testando controladores web com MockMVC 129

• @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.

• @MockBean VideoService videoService: Este bean é um componente obrigatório do HomeController.


Usar a anotação @MockBean do Spring Boot Test cria uma versão simulada do bean e a adiciona
ao contexto do aplicativo.

• @Test: denota este método como um caso de teste JUnit 5.

• @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).

• A primeira linha usa MockMvc para executar um get("/").

• As cláusulas subsequentes executam uma série de asserções, incluindo a verificação se o resultado é


um código de resposta HTTP 200 (OK) e se o conteúdo contém um nome de usuário de usuário e
uma autoridade de ROLE_USER. Em seguida, ele encerra a chamada MockMVC capturando a
resposta inteira como uma string.

• 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

void postNewVideoShouldWork() lança exceção {


mvc.perform( //
post("/novo-vídeo") //
.param("nome", "novo vídeo") //
.param("descrição", "nova descrição") //
.com(csrf())) //
.andExpect(redirectedUrl("/"));

verificar(videoService).create( //
novo NovoVídeo( //
"Novo vídeo", //
"nova descrição"), //
"do utilizador");

}
Machine Translated by Google

130 Testando com Spring Boot

Este método de teste pode ser descrito da seguinte forma:

• @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.

• redireccionadoUrl("/"): Verifica se o controlador emite um redirecionamento HTTP.

• 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:

Figura 5.4 – Resultados do teste HomeControllerTest

Esta captura de tela dos resultados do teste nos mostra exercitando com sucesso alguns métodos do controlador em menos de
um segundo.

Fica como exercício para você testar os outros métodos do controlador.

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.

Testando repositórios de dados com simulações


Depois de executar nosso controlador web por meio de alguns testes automatizados, é hora de voltar nossa atenção para outra
peça-chave do nosso sistema: a camada de serviço que o controlador web invoca.

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

Testando repositórios de dados com simulações 131

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.

Teste de unidade versus teste de integração


Existem várias estratégias de teste que podemos aproveitar. Um dos principais são os testes de unidade versus
integração. Em princípio, um teste unitário destina-se a testar apenas uma classe. Quaisquer serviços externos
devem ser ridicularizados ou eliminados. A estratégia de teste de contrapartida, teste de integração, envolve a
criação de variantes reais ou simuladas desses diversos colaboradores. Naturalmente, existem benefícios e
custos associados a ambos. O teste de unidade tende a ser mais rápido, pois todas as influências externas são
substituídas por respostas prontas. Mas existe o risco de que um determinado caso de teste não teste nada além
da própria simulação. Os testes de integração podem aumentar a confiança, pois tendem a ser mais reais, mas
também tendem a exigir mais design e configuração. E quer você esteja usando bancos de dados incorporados
ou contêineres Docker para emular serviços de produção, os serviços não são tão rápidos. É por isso que
qualquer aplicativo genuíno tende a ter uma mistura de ambos. Uma certa quantidade de testes de unidade pode
verificar a funcionalidade principal. Mas também precisamos de ter a noção de que, quando os nossos
componentes estão ligados, funcionam corretamente em conjunto.

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)

classe pública VideoServiceTest {

Serviço de VideoService;
Repositório @Mock VideoRepository;

@BeforeEach

void configuração() {
this.service = novo VideoService (repositório);
}
}

Esta classe de teste pode ser descrita da seguinte forma:

• @ExtendWith(MockitoExtension.class): gancho JUnit 5 do Mockito para simular qualquer


campos com a anotação @Mock

• VideoService: a classe em teste

• VideoRepository: um colaborador necessário para VideoService é marcado para simulação


Machine Translated by Google

132 Testando com Spring Boot

• @BeforeEach: anotação do JUnit 5 para fazer esse método de configuração ser executado antes de cada método de teste

• O método setUp() mostra o VideoService sendo criado com o VideoRepository simulado


injetado através de seu construtor

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);
}

Este método de teste possui algumas partes principais, como segue:

• @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.

• A próxima linha é onde o método getVideos() do VideoService é chamado.

• A última linha usa AssertJ para verificar o resultado.

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

Testando repositórios de dados com simulações 133

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

Não há exigência de inclusão de comentários, mas esta convenção facilita a leitura.


E não são apenas os comentários. Às vezes, podemos escrever casos de teste que estão por toda parte.
Fazer com que um método de teste siga o dado, quando e então pode ajudar a torná-los mais
convincentes e focados. Por exemplo, se um caso de teste parece ter muitas asserções e se desvia
em muitas direções, pode ser um sinal de que ele deve ser dividido em vários métodos de teste.

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");
}

As partes principais a serem observadas são as seguintes:

• dado(): Este método de teste usa o operador BDDMockito.given do Mockito, um sinônimo do operador
quando() do Mockito

• any(VideoEntity.class): operador do Mockito para corresponder quando a operação saveAndFlush()


do repositório for chamada

• O meio do método de teste nos mostra invocando VideoService.create()

• O método de teste é finalizado, confirmando os resultados


Machine Translated by Google

134 Testando com Spring Boot

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!

Testando dados versus comportamento de teste

Um determinado caso de teste geralmente consiste em verificar os dados de teste ou verificar se os


métodos corretos foram chamados. Até agora, usamos when(something).thenReturn(value), que é
conhecido como stub. Estamos configurando um conjunto de dados de teste predefinidos a serem
retornados para uma chamada de função específica. E mais tarde, podemos esperar afirmar esses
valores. A alternativa é usar o operador verify() do Mockito, que veremos no próximo caso de teste. Este
operador, em vez de confirmar pelos dados, verifica qual método foi chamado no objeto simulado. Não
precisamos nos comprometer com uma estratégia ou outra. Às vezes, com o código que estamos
testando, é mais fácil capturar sua intenção por meio de stub. Outras vezes, é mais claro capturar o
comportamento com zombaria. De qualquer forma, o Mockito facilita o teste.

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

Testando repositórios de dados com bancos de dados incorporados 135

• Este teste invoca a operação delete() do VideoService.

• 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.

Testando repositórios de dados com bancos de dados incorporados


Testar bancos de dados reais sempre foi caro em termos de tempo e recursos. Isso ocorre porque tradicionalmente é
necessário iniciar nosso aplicativo, pegar uma espécie de script manuscrito e clicar em várias páginas do aplicativo para
garantir que ele funcione.

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.

Todos os bancos de dados não são executados 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

136 Testando com Spring Boot

<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 {

Repositório @Autowired VideoRepository;

@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."),

new VideoEntity("alice", // "Não faça ISTO 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!", //
"Descubra maneiras de não apenas depurar seu código")));
}
}

Esta classe de teste pode ser descrita da seguinte forma:

• @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

Testando repositórios de dados com bancos de dados incorporados 137

• @Autowired VideoRepository: Injeta automaticamente uma instância do nosso objeto VideoRepository


para testar.

• @BeforeEach: Esta é a anotação do JUnit 5 e garante que este método seja executado antes de cada
método de teste.

• repositório.saveAll(): Usando VideoRepository, salva um lote de dados 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.

Então, vamos escrever o primeiro teste:

@Teste

void findAllShouldProduceAllVideos() {
List<VideoEntity> vídeos = repositório.findAll();
assertThat(vídeos).hasSize(3);
}

Este método de teste faz o seguinte:

• Exercita o método findAll().

• 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

138 Testando com Spring Boot

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?

• Ele usa findByNameContainsIgnoreCase() e conecta uma substring confusa.

• Utilizando AssertJ, verifica o tamanho dos resultados como 1.

• Usando o operador extracting() do AssertJ e uma referência de método Java 8 , podemos extrair o
campo de nome de cada entrada.

• A última parte desta afirmação é containsExactlyInAnyOrder(). Se a ordem não importa, mas o


conteúdo específico importa, então este é um operador perfeito para confirmar resultados.

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

Adicionando Testcontainers ao aplicativo 139

Este método de teste pode ser descrito da seguinte forma:

• Aqui, estamos exercitando o findByNameContainsOrDescription do repositório


ContémAllIgnoreCase(). As entradas são de fato strings parciais e o caso é alterado em relação ao que foi armazenado
no método setUp().

• 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.

Adicionando Testcontainers ao aplicativo


Vimos que, com a zombaria, podemos substituir um serviço real por um falso. Mas o que acontece quando você precisa verificar
um serviço real, o que envolve conversar com um banco de dados real?
Machine Translated by Google

140 Testando com Spring Boot

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>

testcontainers.version especifica a versão de Testcontainers a ser usada. Esta configuração de


propriedade deve ser colocada dentro do elemento <properties/>, o mesmo local onde você pode
encontrar a propriedade java.version já existente.

Com isso implementado, as seguintes dependências também devem ser adicionadas:

<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

Adicionando Testcontainers ao aplicativo 141

Essas dependências adicionais podem ser descritas da seguinte forma:

• 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.

• org.testcontainers:postgresql: A biblioteca Testcontainers que traz suporte de primeira classe para


containers PostgreSQL (que exploraremos mais adiante nesta seção).

• org.testcontainers:junit-jupiter: A biblioteca Testcontainers que traz profundidade


integração com JUnit 5 – ou seja, JUnit Júpiter.

É 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>

Esta entrada da BOM pode ser descrita da seguinte forma:

• org.testcontainers:testcontainers-bom: Esta lista técnica do Testcontainers contém todas as informações


importantes sobre cada módulo suportado. Ao especificar a versão aqui, todas as outras dependências
do Testcontainers podem ignorar a configuração de uma versão.

• 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

142 Testando com Spring Boot

Testando repositórios de dados com Testcontainers


A primeira etapa quando se trata de usar Testcontainers é configurar o caso de teste. Para conversar com um banco de dados
Postgres, faça o seguinte:

@Testcontainers

@DataJpaTest
@AutoConfigureTestDatabase(substituir = Substituir.NONE)
classe pública VideoRepositoryTestcontainersTest {

Repositório @Autowired VideoRepository;

@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:

• @Testcontainers: a anotação do módulo Testcontainers junit-jupiter


que se conecta ao ciclo de vida de um caso de teste JUnit 5.

• @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 ).

• @Autowired VideoRepository: injeta o repositório Spring Data da aplicação. Nós


quero a coisa real e não uma simulação porque é isso que estamos testando!

• @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

Testando repositórios de dados com Testcontainers 143

No entanto, ainda não chegamos lá.

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:

classe estática DataSourceInitializer //


implementa ApplicationContextInitializer
<ConfigurableApplicationContext> {
@Sobrepor

public void inicializar(ConfigurableApplicationContext applicationContext) {

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");
}
}

Este código pode ser explicado da seguinte forma:

• 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.

• TestPropertySourceUtils.addInlinedPropertiesToEnvironment: Este método estático do Spring Test


nos permite adicionar configurações de propriedade adicionais ao contexto do aplicativo. As
propriedades fornecidas aqui são da instância PostgreSQLContainer criada na seção anterior.
Estaremos acessando um contêiner já iniciado por Testcontainers para que possamos aproveitar
seu URL JDBC, nome de usuário e senha.
Machine Translated by Google

144 Testando com Spring Boot

• spring.jpa.hibernate.ddl-auto=create-drop: Ao conversar com um banco de dados incorporado, o Spring Boot


configura automaticamente as coisas com a política create-drop do JPA, onde o esquema do banco de
dados é criado do zero. Como estamos usando uma conexão real para se comunicar com um banco de
dados PostgreSQL, ele muda para nenhum, onde nenhum comportamento incorporado do Spring Boot
acontece. Em vez disso, o Spring Boot tentará não fazer alterações no banco de dados em relação ao
esquema e aos dados. Como este é um ambiente de teste, precisamos substituir isso e voltar para create-drop.

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)

classe pública VideoRepositoryTestcontainersTest {


A anotação @ContextConfiguration adiciona nossa classe DataSourceInitializer ao contexto do aplicativo. E devido


ao registro de um ApplicationContextInitializer, ele será invocado precisamente no momento certo após
Testcontainers lançar um contêiner Postgres e antes que a configuração automática do Spring Data JPA seja
aplicada.

A única coisa que resta a fazer é escrever alguns testes reais!

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!", //

"Descubra maneiras de não apenas depurar seu código")));


}
Machine Translated by Google

Testando repositórios de dados com Testcontainers 145

Este método pode ser descrito da seguinte forma:

• @BeforeEach: A anotação JUnit que executa esse código antes de cada método de teste.

• repositório.saveAll(): armazena uma lista completa de objetos VideoEntity no banco de dados.

• List.of(): Um operador Java 17 para montar uma lista de forma rápida e fácil.

• Cada instância de VideoEntity possui um usuário, um nome e uma descrição.

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

146 Testando com Spring Boot

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:

Figura 5.5 – Testes baseados em Testcontainers

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.

Testando políticas de segurança com Spring Security Test


Algo passou pela sua cabeça? Não verificamos as questões de segurança quando escrevemos a classe
HomeControllerTest no início deste capítulo?

Sim e não.
Machine Translated by Google

Testando políticas de segurança com Spring Security Test 147

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 (controladores = HomeController.class)

classe pública SecurityBasedTest {

@Autowired MockMvc mvc;

@MockBean VideoService videoService;

Esperançosamente, as coisas estão começando a parecer familiares:

• @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.

• @MockBean VideoService: o colaborador do HomeController será substituído por um


Zombaria de Mockito.

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

148 Testando com Spring Boot

.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

void unauthUserShouldNotAccessHomePage() lança exceção {


mvc //
.perform(get("/")) //
.andExpect(status().isUnauthorized());
}

Este método de teste tem alguns aspectos principais:

• 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.

• mvc.perform(get("/")): Use MockMVC para realizar uma chamada GET/.


• status().isUnauthorized(): afirma que o resultado é um HTTP 401
Código de erro não autorizado.

Além disso, observe o nome do método do teste: unauthUserShouldNotAccessHomePage. Afirma muito


claramente a expectativa. Dessa forma, se quebrar, saberemos exatamente qual foi o objetivo do teste.
Esperançosamente, isso nos colocará no caminho para consertar as coisas mais rapidamente.

status().isUnauthorized() para um usuário não autenticado?


Em segurança, provar quem você é é chamado de autenticação. O que você tem permissão para fazer é
conhecido como autorização. No entanto, o código de status HTTP para um usuário não
autenticado é 401 Não Autorizado. Quando alguém é autenticado, mas tenta acessar algo para
o qual não está autorizado, o código de status HTTP é 403 Proibido. Uma mistura bastante
peculiar de terminologia, mas algo a ter em conta.

Acabamos de escrever um caso de teste de caminho incorreto, um requisito crítico ao testar políticas de segurança. Precisamos também

escrever um bom caso de teste de caminho, conforme mostrado aqui:

@Teste

@WithMockUser(nomedeusuário = "alice", funções = "USUÁRIO")

void authUserShouldAccessHomePage() lança exceção {


mvc //
Machine Translated by Google

Testando políticas de segurança com Spring Security Test 149

.perform(get("/")) //
.andExpect(status().isOk());
}

Este método de teste é muito semelhante, exceto pelo seguinte:

• @WithMockUser: esta anotação insere um token de autenticação no servlet MockMVC

context com um nome de usuário alice e uma autoridade ROLE_USER.


• Ele faz a mesma chamada get("/") que o método de teste anterior fez, mas espera um resultado diferente.
Com status().isOk(), procuramos um código de resultado HTTP 200 Ok.

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.

O código a seguir é quase igual ao código anterior:

@Teste

@WithMockUser(nomedeusuário = "alice", funções = "ADMIN")


void adminShouldAccessHomePage() lança exceção {
mvc //
.perform(get("/")) //
.andExpect(status().isOk());
}

A única diferença é que @WithMockUser possui alice e ROLE_ADMIN armazenados no contexto do servlet.

Esses três testes devem verificar adequadamente o acesso à página inicial.

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

void newVideoFromUnauthUserShouldFail() lança exceção {


mvc.perform( //
post("/novo-vídeo") //
.param("nome", "novo vídeo") //
.param("descrição", "nova descrição") //
.com(csrf())) //
.andExpect(status().isUnauthorized());
}
Machine Translated by Google

150 Testando com Spring Boot

Este método de teste pode ser descrito da seguinte forma:

• 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.

• mvc.perform(post("/new-video")): Usa MockMVC para executar uma ação POST /new-video. Os


argumentos param("key", "value") nos permitem fornecer os campos normalmente inseridos através
de um formulário HTML.

• with(csrf()): Temos proteções CSRF habilitadas. Essa configuração adicional nos permite conectar
o valor CSRF, simulando uma tentativa legítima de acesso.

• status().isUnauthorized(): Garante que recebamos uma resposta HTTP 4 0 1 Unauthorized.

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

@WithMockUser(nomedeusuário = "alice", funções = "USUÁRIO")

void newVideoFromUserShouldWork() lança exceção {


mvc.perform( //
post("/novo-vídeo") //
.param("nome", "novo vídeo") //
.param("descrição", "nova descrição") //
.com(csrf())) //
.andExpect(status().is3xxRedirection()) //
.andExpect(redirectedUrl("/"));
}

Este código pode ser resumido da seguinte forma:

• @WithMockUser: Este usuário possui ROLE_USER.


• Ele executa o mesmo POST/new-video com os mesmos valores e tokens CSRF, mas obtemos um
conjunto diferente de códigos de resposta.
Machine Translated by Google

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.

• redrededUrl("/"): Permite verificar se o caminho redirecionado é /.

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

não autorizados quanto com usuários totalmente autorizados.

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.

Ainda mais livros sobre Java em nosso canal de telegram:


https://t.me/javalib
Machine Translated by Google
Machine Translated by Google

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.

Esta parte inclui os seguintes capítulos:

• Capítulo 6, Configurando um aplicativo com Spring Boot

• Capítulo 7, Liberando um aplicativo com Spring Boot

• Capítulo 8, Tornando-se nativo com Spring Boot


Machine Translated by Google
Machine Translated by Google

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!

Neste capítulo, abordaremos os seguintes tópicos:

• Criação de propriedades personalizadas

• Criação de arquivos de propriedades baseados em perfil

• Mudando para YAML

• Definir propriedades com variáveis de ambiente

• Ordem de substituições de propriedade


Machine Translated by Google

156 Configurando um aplicativo com Spring Boot

Onde encontrar o código deste capítulo


O código-fonte deste capítulo pode ser encontrado em https://github.com/PacktPublishing/
Learning-Spring-Boot-3.0/tree/main/ch6.

Criando propriedades customizadas


Já nos ocupamos com propriedades de aplicativos em alguns lugares deste livro. Lembra de configurar
spring.mustache.servlet.expose-request-attributes=true no arquivo application.properties no Capítulo
4, Protegendo um aplicativo com Spring Boot?

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) {
}

Este registro Java 17 pode ser descrito da seguinte forma:

• @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.

Este pequeno registro declara essencialmente três propriedades: app.config.header, app.config.intro


e app.config.users. Podemos preenchê-los imediatamente adicionando o seguinte ao application.
propriedades:

app.config.header=Saudações, fãs do Spring Boot 3.0!


app.config.intro=Neste capítulo, estamos aprendendo como fazer uma aplicação web usando
Spring Boot 3.0
app.config.users[0].username=alice
app.config.users[0].password=senha
app.config.users[0].authorities[0]=ROLE_USER
app.config.users[1].username=bob
app.config.users[1].password=senha
Machine Translated by Google

Criando propriedades customizadas 157

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

Este lote de propriedades pode ser descrito da seguinte forma:

• app.config.header: Um valor de string para inserir na parte superior do nosso modelo (o que
faremos em breve).

• app.config.intro: Uma string de saudação para colocar no modelo.

• app.config.users: uma lista de entradas UserAccount com cada atributo dividido em um


linha separada. A notação de colchetes é usada para preencher a lista Java.

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:

• @EnableConfigurationProperties(AppConfig.class): Esta anotação ativa a configuração desta


aplicação, possibilitando a injeção em qualquer bean Spring.

O resto do código é o mesmo que vimos nos capítulos anteriores.

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

158 Configurando um aplicativo com Spring Boot

Graças ao @EnableConfigurationProperties, um bean Spring do tipo AppConfig será registrado


automaticamente no contexto da aplicação, vinculado aos valores aplicados dentro da aplicação.
propriedades.

Para aproveitá-lo em nosso HomeController, precisamos apenas fazer as seguintes alterações no topo
da classe:

@Controlador

classe pública HomeController {

VideoService final privado videoService; final privado AppConfig


appConfig;

public HomeController(VideoService videoService,


AppConfig appConfig) {
this.videoService = videoService;
this.appConfig = appConfig;
}
…resto da aula…

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";

Essas mudanças podem ser descritas da seguinte forma:

• O atributo "header" do modelo é preenchido com appConfig.header()

• O atributo "intro" do modelo é preenchido com appConfig.intro()


Machine Translated by Google

Criando propriedades customizadas 159

Isso pegará os valores de string que colocamos em application.properties e os encaminhará para que
renderizem index.mustache.

Para completar o loop, precisamos fazer as seguintes alterações no modelo:

<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.

Esse NÃO está pronto para ir. Por que?

É 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

public GrantedAuthority convert(String fonte) {


retornar novo SimpleGrantedAuthority(fonte);
}
};
}

Este código pode ser descrito da seguinte forma:

• @Bean: Este conversor deve ser registrado no contexto da aplicação para que seja escolhido
corretamente.

• @ConfigurationPropertiesBinding: Devido à conversão de propriedades do aplicativo acontecer


muito cedo no ciclo de vida do aplicativo, o Spring Boot SOMENTE aplica conversores que
possuem essa anotação aplicada. Tente evitar puxar outras dependências.
Machine Translated by Google

160 Configurando um aplicativo com Spring Boot

• 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:

interface GrantedAuthorityCnv estende Converter<String,


Autoridade concedida> {}
@Feijão

@ConfigurationPropertiesBinding
Conversor GrantedAuthorityCnv() {
retornar SimpleGrantedAuthority::new;
}

Esta solução de eliminação de tipo pode ser descrita da seguinte forma:

• 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

• Usando esta nova interface em vez de Converter<String, GrantedAuthority>, nós


pode mudar para a referência de método simplificada

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

Criando arquivos de propriedades baseados em perfil 161

Criando arquivos de propriedades baseados em perfil

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!”

Para verificar isso, crie outro arquivo de propriedades. Chame-o de application-test.properties e


carregue-o, assim:

app.config.header=Saudações Equipe de Teste!


app.config.intro=Se você tiver problemas durante o teste, me avise!

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

Esses conjuntos alternativos de propriedades podem ser descritos da seguinte forma:

• application-test.properties: quando você anexa –test ao nome base do


arquivo de propriedades, você pode ativá-lo usando o perfil de teste Spring

• Os bits da web são sintonizados com o público

• 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:

• Adicionar -Dspring.profiles.active=test à JVM


Machine Translated by Google

162 Configurando um aplicativo com Spring Boot

• Em um ambiente Unix, use export SPRING_PROFILES_ACTIVE=test

• Alguns IDEs até suportam isso diretamente! Confira a seguinte captura de tela do IntelliJ
IDEA (Ultimate Edition, não Community Edition):

Figura 6.1 – Caixa de diálogo Executar do IntelliJ

Olhe na parte inferior para perfis ativos e veja onde entramos no teste.

Quer conferir o IntelliJ IDEA Ultimate Edition?


JetBrains oferece um teste gratuito de 30 dias para o IntelliJ IDEA Ultimate Edition. Eles também têm
opções se você estiver trabalhando em determinados projetos de código aberto. Pessoalmente, este é o
meu IDE favorito que usei ao longo da minha carreira. Ele tem uma visão incrível de todos os tipos de
coisas, desde código Java até strings SQL em anotações Spring Data @Query, arquivos de propriedades,
upgrades do Spring Boot (e muito mais). Sinta-se à vontade para verificar suas opções em https://springbootlearning.
com/intellij-idea-experimente.
Machine Translated by Google

Criando arquivos de propriedades baseados em perfil 163

Quando ativamos um perfil Spring, o Spring Boot adicionará application-test.properties à nossa configuração.

Dica

Os perfis são aditivos. Quando ativamos o perfil de teste, application-test.properties


é adicionado à configuração. Ele não substitui application.properties. Porém, se determinado
imóvel for encontrado em ambos, vence o último perfil aplicado. Considerando a aplicação.
propriedades é considerado o perfil padrão, application-test.properties seria aplicado
posteriormente. Conseqüentemente, as propriedades de cabeçalho, introdução e usuários seriam substituídas.
Também é possível aplicar mais de um perfil, separados por vírgula.

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.

Mas essa não é a única escolha que nos é apresentada.

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

164 Configurando um aplicativo com Spring Boot

Mudando para YAML


Parte do caminho da Primavera são as opções. Os desenvolvedores têm necessidades variadas com base nas circunstâncias, e o
Spring tenta oferecer diferentes maneiras de fazer as coisas com eficácia.

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:

header: Saudações das configurações baseadas em YAML!


introdução: Confira esta página hospedada no YAML
Usuários:

nome de usuário: yaml1


senha: senha
autoridades:

- ROLE_USER
-

nome de usuário: yaml2


senha: senha
autoridades:

- ROLE_USER
-

nome de usuário: yaml3


senha: senha
autoridades:

- ROLE_ADMIN

Essas configurações baseadas em YAML podem ser descritas da seguinte forma:

• A natureza aninhada do YAML evita entradas duplicadas e deixa claro onde cada propriedade
está localizado

• Os hífens abaixo dos usuários indicam entradas da matriz

• Como o campo de usuários do AppConfig é um tipo complexo (List<UserAccount>), cada campo de


cada entrada é listado em uma linha separada
Machine Translated by Google

Mudando para YAML 165

• 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.

arquivos de propriedades e arquivos application.yaml!

Confira a seguinte captura de tela:

Figura 6.2 – Conclusão de código do IntelliJ para configurações de propriedades


Machine Translated by Google

166 Configurando um aplicativo com Spring Boot

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.

Configurando propriedades com variáveis de ambiente


Um aplicativo configurável não estaria completo se não houvesse uma maneira de configurá-lo diretamente na linha
de comando. Isso é de fundamental importância porque não importa quanto pensamento e design coloquemos em
nossos aplicativos, algo sempre aparecerá.

Ficar preso a um aplicativo agrupado e não ter como substituir os vários arquivos de propriedades contidos nele
seria um empecilho.

Não faça isso!

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:

$ SPRING_PROFILES_ACTIVE=alternativa ./mvnw spring-boot:run

Este comando pode ser explicado da seguinte forma:

• SPRING_PROFILES_ACTIVE: A forma alternativa de referenciar propriedades em um sistema baseado em Mac/


Linux . Os pontos normalmente não funcionam tão bem, então tokens maiúsculos com sublinhados funcionam.
Machine Translated by Google

Solicitando substituições de propriedade 167

• 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!).

• spring-boot:run: O comando Maven que ativa o objetivo de execução do spring-boot-


plugin maven.

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:

$ SPRING_PROFILES_ACTIVE=teste,alternativo ./mvnw spring-boot:run

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.

Solicitando substituições de propriedade

De volta ao Capítulo 1, Recursos principais do Spring Boot, obtivemos um resumo da ordem das configurações de propriedades.

Vamos ver essa lista de opções novamente:

• As propriedades padrão são fornecidas pelo SpringApplication do Spring Boot.


método setDefaultProperties().

• Classes @Configuration anotadas em @PropertySource.

• Dados de configuração (como arquivos application.properties).

• Um RandomValuePropertySource que possui propriedades apenas em random.*.

• Variáveis de ambiente do SO.

• Propriedades do sistema Java (System.getProperties()).

• Atributos JNDI de java:comp/env.


Machine Translated by Google

168 Configurando um aplicativo com Spring Boot

• Parâmetros de inicialização do ServletContext.

• Parâmetros de inicialização do ServletConfig.

• Propriedades de SPRING_APPLICATION_JSON (JSON embutido incorporado em uma variável de ambiente ou


propriedade de sistema).

• Argumentos de linha de comando, conforme mostrado na seção anterior.

• 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)

• Anotações @TestPropertySource em seus testes.

• Propriedades de configurações globais do DevTools (o diretório $HOME/.config/spring-boot quando


Spring Boot DevTools está ativo).

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.

Além disso, os arquivos de configuração são considerados na seguinte ordem:

• Propriedades do aplicativo empacotadas dentro do seu arquivo JAR

• Propriedades de aplicativo específicas do perfil dentro do seu arquivo JAR

• Perfis de aplicativos fora do seu arquivo JAR

• Propriedades de aplicativo específicas do perfil fora do seu arquivo JAR

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.

Neste capítulo, abordaremos os seguintes tópicos:

• Criando um superJAR

• Preparando um contêiner Docker

• Liberando seu aplicativo para Docker Hub

• Ajustando coisas na produção

Onde encontrar o código deste capítulo


Este capítulo não tem muito em termos de código original. Em vez disso, o código do
Capítulo 6, Configurando um aplicativo com Spring Boot, foi copiado para o seguinte
repositório para que possamos lidar com várias formas de implantação: https://github.com/PacktPublishing/
Learning-Spring-Boot-3.0/tree/main/ch7.
Machine Translated by Google

172 Liberando um aplicativo com Spring Boot

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.

O seguinte comando Maven é suficiente:

% ./mvnw pacote limpo

Este comando Maven tem duas partes:

• 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

Criando um superJAR 173

Quando usamos Spring Initializr (https://start.spring.io), como fizemos em vários capítulos


anteriores, uma das entradas incluídas em nosso arquivo pom.xml foi spring-boot-maven-
plugin, conforme mostrado aqui :

<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.

2. Em seguida, ele renomeia o JAR original para colocá-lo de lado (target/ch7-0.0.1-SNAPSHOT.jar.


original neste caso).

3. Ele cria um novo arquivo JAR com o nome original.

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:

% java -jar destino/ch7-0.0.1-SNAPSHOT.jar


. ____ _ __ _

_
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \\\\
\\/ ___)| |_)| | | | || (_| | |____| .__|_| |_|_|| |_\__, | / / / / ) ) ) )
'

=========|_|=============|___/=/_/_/_/
:: 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

174 Liberando um aplicativo com Spring Boot

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

Assando um contêiner Docker 175

E se a sua máquina alvo não tiver uma?

Confira a próxima seção!

Assando um contêiner Docker


Uma das tecnologias mais rápidas a varrer o mundo da tecnologia foi o Docker. Se você ainda não
ouviu falar, o Docker é como uma máquina virtualizada, mas mais leve.

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.

E o Spring Boot vem com suporte Docker integrado.

O Docker não é experimental?

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

176 Liberando um aplicativo com Spring Boot

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.

Construindo o tipo “certo” de contêiner


O que queremos dizer com recipiente “certo”?

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:

[INFO] --- spring-boot-maven-plugin:3.0.0:build-image (default-cli) @ ch7 ---

[INFO] Construindo imagem 'docker.io/library/ch7:0.0.1-SNAPSHOT'


[INFORMAÇÕES]

[INFORMAÇÕES] > Imagem do construtor extraída 'paketobuildpacks/builder@


sha256:9fb2c87caff867c9a49f04bf2ceb24c87bde504f3fed88227e9ab5d9
a572060c'
Machine Translated by Google

Assando um contêiner Docker 177

[INFORMAÇÕES] > Extraindo a imagem de execução 'docker.io/paketobuildpacks/


execute:base-cnb' 100%
[INFO] > Imagem de execução extraída 'paketobuildpacks/run@sha256:
fed727f0622994807560102d6a2d37116ed2e03dddef5445119eb0172
12bbfd7'
[INFORMAÇÕES] > Executando a versão do ciclo de vida v0.14.2
[INFORMAÇÕES] > Usando o volume de cache de construção 'pack-cache-564d5464b59a.
construir'

[INFO] Imagem construída com sucesso 'docker.io/library/ch7:0.0.1-


INSTANTÂNEO'

Este pequeno fragmento da saída total para o console revela o seguinte:

• 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.

• Termina com um container montado com sucesso.

Pacotes de construção Paketo

Paketo Buildpacks (https://paketo.io/) é um projeto focado em transformar código-fonte de aplicativos em


imagens de contêiner. Em vez de fazer isso diretamente, o Spring Boot delega a conteinerização ao
Paketo. Essencialmente, ele baixa contêineres que fazem todo o trabalho braçal, facilitando o processo
de preparação de um contêiner.

Quer ver mais detalhes?

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:

% docker run -p 8080:8080 docker.io/library/ch7:0.0.1-SNAPSHOT


Calculando a memória JVM com base em 7163580K de memória disponível
Para obter mais informações sobre esse cálculo, consulte https://paketo.
io/docs/reference/java-reference/#memory-calculator
Machine Translated by Google

178 Liberando um aplicativo com Spring Boot

. ____ _ __ _

_
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \\\\
\\/ ___)| |_)| | | | || (_| | |____| .__|_| |_|_| |_\__,
| |//// ) ) ) )
'

=========|_|=============|___/=/_/_/_/
:: Bota Primavera :: (v3.0.0)

2022-11-18T23:32:30.711Z 1 --- INFORMAÇÕES

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)

Esta parte da saída do console pode ser descrita da seguinte forma:

• docker run: o comando para executar um contêiner Docker

• -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

• docker.io/library/ch7:0.0.1-SNAPSHOT: o nome da imagem do contêiner

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

ID DO RECIPIENTE IMAGEM COMANDO


CRIADA STATUS
PORTOS NOMES
5e4fb7fdead2 cap7:0.0.1-INSANTÂNEO "/cnb/processo/web"
5 minutos atrás Até 5 minutos 0.0.0.0:8080->8080/tcp
irritado_cray

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:

• 5e4fb7fdead2: o ID com hash do contêiner.

• ch7:0.0.1-SNAPSHOT: O nome da imagem do contêiner (sem o prefixo docker.io)

• /cnb/process/web: O comando que o Paketo Buildpack está usando para executar nosso Spring
Aplicativo de inicialização.
Machine Translated by Google

Liberando seu aplicativo para Docker Hub 179

• "5 minutos atrás" e "Até 5 minutos": quando o contêiner foi iniciado e por quanto tempo
está em alta.

• 0.0.0.0:8080->8080/tcp: O mapeamento de rede interna para externa.

• 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.

A partir daqui, podemos desligá-lo:

% docker parar com raiva_cray

irritado_cray
% janela de encaixe ps

ID DO RECIPIENTE IMAGEM COMANDO CRIADA STATUS


PORTOS NOMES

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.

Liberando seu aplicativo para Docker Hub


Construir um contêiner é uma coisa. Liberar esse contêiner para produção é fundamental. E como o defensor da
Primavera, Josh Long, gosta de dizer: “A produção é o lugar mais feliz do planeta!”

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.

Você tem acesso ao 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

180 Liberando um aplicativo com Spring Boot

No console, podemos fazer login diretamente em nossa conta Docker Hub:

% docker login -u <seu_id>


Senha: *********

Supondo que tudo isso foi feito, agora podemos enviar nosso contêiner para o Docker Hub executando os
seguintes comandos:

tag ch7:0.0.1-SNAPSHOT boot-3rd-edition- <user_id>/learning-spring-% docker


ch7:0.0.1-SNAPSHOT

% docker push <seu_id>/learning-spring-boot-3rd-edition-


cap7:0.0.1-INSANTÂNEO

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!

• O contêiner marcado também tem um nome, learning-spring-boot-3rd-edition-ch7.

• O próprio contêiner marcado possui uma tag, 0.0.1-SNAPSHOT.

O que acabou de acontecer?

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

Liberando seu aplicativo para Docker Hub 181

E as tags mais recentes?


Uma convenção comum vista em todo o Docker Hub é usar um nome de tag mais recente. Isso implica que
pegar um contêiner com essa tag lhe daria, bem, a versão mais recente. Ou pelo menos a versão estável
mais recente. Mas é importante compreender que isto é simplesmente uma convenção. As tags são
dinâmicas e podem ser movidas. Portanto, 0.0.1-SNAPSHOT também pode ser uma tag dinâmica que você
envia sempre que atualiza as versões de snapshot do seu aplicativo, assim como as mais recentes. A
adoção do Docker Hub por editores de software que gerenciam vários lançamentos levou ao gerenciamento
de várias tags para indicar qual versão está sendo buscada. Antes de adotar QUALQUER tag de contêiner,
certifique-se de verificar sua estratégia de marcação para que você possa entender exatamente o que está obtendo.

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:

Figura 7.1 – Contêiner Docker enviado para Docker Hub


Machine Translated by Google

182 Liberando um aplicativo com Spring Boot

Onde está meu contêiner Docker no Docker Hub?


A captura de tela anterior é do meu repositório Docker Hub. Qualquer contêiner que você enviar deve estar
em seu próprio repositório. É importante usar seu próprio Docker Hub ID ao marcar e enviar!

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.

Ajustando as coisas na produção


Um aplicativo não está realmente em produção até que precisemos começar a ajustá-lo, mexer nele e fazer ajustes
após o lançamento.

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:

% java -jar destino/ch7-0.0.1-SNAPSHOT.jar

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:

% SERVER_PORT=9000 java -jar alvo/ch7-0.0.1-SNAPSHOT.jar


2022-11-20T15:36:55.748-05:00 INFO 90544 --- [principal] osbwembedded.tomcat.

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

Ajustando as coisas na produção 183

A melhor maneira de oferecer configurações personalizadas é criar um aplicativo adicional.


arquivo de propriedades em nossa pasta local.

Primeiro, crie um novo arquivo application.properties, assim:

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.

Agora podemos executar o uber JAR como fizemos da primeira vez:

% java -jar destino/ch7-0.0.1-SNAPSHOT.jar


2022-11-20T15:41:09.239-05:00 INFORMAÇÕES 91085 ---

[ 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!

Temos um aplicativo da web em execução na porta 9000.

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.

Dimensionando com Spring Boot

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.

Primeiro, renomeie o arquivo local application.properties para application-instance1.


propriedades.

Em seguida, faça uma cópia do arquivo e nomeie o novo como application-instance2.properties.


Edite o arquivo para que server.port receba 9001.

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

184 Liberando um aplicativo com Spring Boot

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:

% SPRING_PROFILES_ACTIVE=instance1 java -jar target/ch7-0.0.1-


INSTANTÂNEO.jar

2022-11-20T15:52:30.195-05:00 INFORMAÇÕES 94504 ---

[ 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.

Abra outra aba do console e execute instance2, assim:

% SPRING_PROFILES_ACTIVE=instance2 java -jar target/ch7-0.0.1-


INSTANTÂNEO.jar

2022-11-20T15:53:36.403-05:00 INFORMAÇÕES 94734 ---

[ 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:

% SPRING_PROFILES_ACTIVE=instance3 java -jar target/ch7-0.0.1-


INSTANTÂNEO.jar

2022-11-20T15:55:53.062-05:00 INFORMAÇÕES 96783 ---

[ 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

94504 --- principal]


[cslChapter7Application seguinte 1 perfil :O
está ativo: "instance1"
Machine Translated by Google

Ajustando as coisas na produção 185

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:

% docker run -d -p 5432:5432 --name meu-postgres -e POSTGRES_


SENHA=minhasenha secreta postgres:9.6.12

Este comando irá gerar uma cópia do PostgreSQL com as seguintes características:

• -d: A instância será executada como um processo daemon em segundo plano.

• -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.

• -e POSTGRES_PASSWORD=mysecretpassword: O container será executado com uma variável de


ambiente que, de acordo com as notas do Postgres, irá configurar a senha.

• postgres:9.6.12: As mesmas coordenadas do contêiner encontradas no arquivo baseado em Testcontainers


teste de integração.

Com isso instalado e funcionando, podemos atualizar application-instance1.properties com as seguintes


propriedades adicionais:

#Configurações da fonte de dados JDBC


spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=minhasenha secreta
#Configurações JPA
spring.jpa.hibernate.ddl-auto=atualização
spring.jpa.hibernate.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.
dialeto.PostgreSQLDialect
Machine Translated by Google

186 Liberando um aplicativo com Spring Boot

As propriedades JDBC podem ser resumidas da seguinte forma:

• spring.datasource.url: Este é o URL de conexão JDBC para acessar o


instância baseada em contêiner

• spring.datasource.username: contém o nome de usuário padrão do postgres sob o qual o contêiner é executado

• spring.datasource.password: contém a senha que escolhemos anteriormente nesta seção

Estas são todas as propriedades necessárias para o Spring Boot montar um bean JDBC DataSource.

As propriedades JPA podem ser descritas da seguinte forma:

• spring.jpa.hibernate.ddl-auto: Esta é a configuração do Spring Data JPA que será atualizada


o esquema, se necessário, mas não descarte ou exclua nada

• spring.jpa.hibernate.show-sql: Isso ativará a capacidade do Spring Data JPA de imprimir instruções SQL geradas

• spring.jpa.properties.hibernate.dialect: Esta é a propriedade do Hibernate que


sinaliza que estamos conversando com um banco de dados baseado em PostgreSQL

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.

Aviso de dados de produção!

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.

Existem várias opções, incluindo Kubernetes e Spinnaker. Kubernetes é um orquestrador de


contêineres Docker . Ele nos permite gerenciar os contêineres e também os balanceadores de
carga de cima para baixo. Confira https://springbootlearning.com/kubernetes para mais detalhes.
Machine Translated by Google

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. .

Neste capítulo, abordaremos os seguintes tópicos:

• O que é GraalVM e por que nos importamos?

• Retrofit da nossa aplicação para GraalVM

• Executando nosso aplicativo Spring Boot nativo dentro do GraalVM

• Preparando um contêiner Docker com GraalVM

Onde encontrar o código deste capítulo


O código deste capítulo pode ser encontrado em https://github.com/PacktPublishing/
Learning-Spring-Boot-3.0/tree/main/ch8.

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

190 Tornando-se nativo com Spring Boot

O que é GraalVM e por que nos importamos?


Durante anos, Java sofreu muitas críticas. Uma de suas maiores falhas desde o início foi seu
desempenho. Embora seja verdade até certo ponto, o Java deu saltos quânticos ao adotar táticas que
lhe permitiram competir com outras plataformas em um nível bruto de desempenho.

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.

E assim surgiu o GraalVM.

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.

Tudo com o mínimo de barulho para o usuário final.

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.

Adaptando nosso aplicativo para GraalVM


Sempre há duas maneiras de abordar a construção de um aplicativo nativo: criar um aplicativo totalmente novo ou pegar um existente
e atualizá-lo. Graças ao Spring Boot 3.0 e à adoção do suporte nativo a aplicações, é muito fácil atualizar uma aplicação existente
para usar GraalVM em vez da JVM.
Machine Translated by Google

Adaptando nosso aplicativo para GraalVM 191

O que é código da máquina virtual Java?

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?

Por causa de compensações.

GraalVM, para fazer algumas das coisas que faz, exige que abandonemos alguns recursos principais:

• Apoio limitado à reflexão

• Suporte limitado para proxies dinâmicos

• Tratamento especial de recursos externos

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

192 Tornando-se nativo com Spring Boot

Cuidado com informações erradas


Alguns artigos que capturam os detalhes do Spring Boot 3.0 e seu suporte para imagens nativas podem
mencionar que reflexão e proxies simplesmente não são suportados. Isto é falso. Há suporte para reflexão,
mas exige que o código na outra extremidade dessas chamadas reflexivas seja devidamente registrado. Em
relação aos proxies, as imagens nativas não conseguem gerar e interpretar bytecode em tempo de execução.
Todos os proxies dinâmicos devem ser gerados no momento da construção da imagem nativa. Estas são
limitações no uso de reflexão e proxies, mas não uma completa falta de suporte.

É 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.

A partir daqui, vamos começar com um novo conjunto de coordenadas:

• Projeto: Maven

• Grupo: com.springbootlearning.learningspringboot3
• Artefato: capítulo 8

• Nome: Capítulo 8

• Descrição: Tornando-se nativo com Spring Boot

• Nome do pacote: com.springbootlearning.learningspringboot3

• Embalagem: Frasco

• Java: 17

• Dependências:

Primavera Web

Bigode

Banco de dados H2

Spring Data JPA

Segurança Primavera

Suporte nativo GraalVM


Machine Translated by Google

Adaptando nosso aplicativo para GraalVM 193

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.

Os iniciadores do Spring Boot encontrados incluem o seguinte:

• spring-boot-starter-data-jpa

• spring-boot-starter-web

• bigode de inicialização de mola

• spring-boot-starter-segurança
• h2

• teste inicial de inicialização por mola

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

194 Tornando-se nativo com Spring Boot

</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.

GraalVM e Spring Boot


Estamos entrando em uma nova área de desenvolvimento de código. Não estamos falando apenas sobre
construir aplicativos Spring Boot, mas também sobre construí-los com ferramentas alternativas, como GraalVM.
Você pode ler a seção do Spring Boot sobre suporte de imagem nativa GraalVM em https://
springbootlearning.com/graalvm.

Executando nosso aplicativo Spring Boot nativo dentro


GraalVM
A convenção comum ao construir um aplicativo para Spring Boot é executar o pacote ./mvnw clean. Isso limpa
o lixo antigo e cria um novo JAR, algo que já vimos no Capítulo 7, Liberando um aplicativo com Spring Boot.

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

Executando nosso aplicativo Spring Boot nativo dentro do GraalVM 195

Para construir aplicações nativas no GraalVM, precisamos instalar uma versão do Java 17 que inclua
ferramentas GraalVM digitando o seguinte comando:

% SDK instala Java 22.3.r17-grl

Depois de instalado, podemos mudar para ele digitando o seguinte:

% SDK usa Java 22.3.r17-grl

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:

% ./mvnw -Pnative limpo nativo:compilar


Machine Translated by Google

196 Tornando-se nativo com Spring Boot

Tentando construir uma imagem nativa no Windows?

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.

Parte da saída pode ser vista aqui:

Figura 8.1 – Saída de mvnw -Pnative clean native:compile


Machine Translated by Google

Executando nosso aplicativo Spring Boot nativo dentro do GraalVM 197

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.

Para executar nosso aplicativo nativo, basta fazer isso:

% alvo/ch8
. ____ _ __ _

_
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \\\\
|//// ___)| |_)| | | | || (_| | \\/ |____|
| .__|_| |_|_| |_\__, ) ) ) )
'

=========|_|=============|___/=/_/_/_/
:: Spring Boot :: …… (v3.0.0)

omitido por questões de brevidade……


2022-11-06T14:35:40.717-06:00 INFORMAÇÕES 12263 ---

[principal] osbwembdedded.tomcat.
TomcatWebServer: Tomcat iniciado na(s) porta(s): 8080 (http) com caminho de contexto ''

2022-11-06T14:35:40.717-06:00 12263 --- INFORMAÇÕES

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

198 Tornando-se nativo com Spring Boot

É possível obter um pop-up como este:

Figura 8.2 – Aplicação nativa solicitando permissão para aceitar conexões de rede

Foi muito esforço. Então, por que fazer tudo isso?

Por que queremos o GraalVM novamente?

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

traduzidas em 20.000 segundos ou 5,6 horas.

5,6 horas de tempo faturável na nuvem.

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

de tempo na nuvem. Uau! Isso representa alguma economia de custos.


Machine Translated by Google

Preparando um contêiner Docker com GraalVM 199

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?

Felizmente, há uma solução para isso na próxima seção!

Preparando um contêiner Docker com GraalVM


No início deste capítulo, instalamos a distribuição OpenJDK do GraalVM e construímos nossa aplicação nativa
localmente. Mas esse não é o único caminho, nem sempre o ideal.

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.

Basta executar o seguinte comando:

% ./mvnw -Pnative spring-boot:build-image

Isso combina o comando spring-boot:build-image do capítulo anterior com o comando nativo


Perfil Maven.

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

200 Tornando-se nativo com Spring Boot

Spring Boot 3.0 versus Spring Boot 2.7 e Spring Native


Você já deve ter ouvido falar do Spring Native. Houve muitos artigos em blogs sobre isso. Você pode até
encontrar vídeos no YouTube falando sobre o uso do Spring Native (até no meu canal!). Mas como você
deve ter notado, não mencionamos o Spring Native até agora.

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.

GraalVM e outras bibliotecas

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.

Esta parte inclui os seguintes capítulos:

• Capítulo 9, Escrevendo controladores web reativos

• Capítulo 10, Trabalhando com dados de forma reativa


Machine Translated by Google
Machine Translated by Google

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?

Bem-vindo ao Spring Boot e à programação reativa!

Neste capítulo, abordaremos os seguintes tópicos:

• Descobrir exatamente o que é programação reativa e por que devemos nos preocupar

• Criando um aplicativo Spring Boot reativo

• Servindo dados com um método GET reativo

• Consumindo dados recebidos com um método POST reativo

• Servindo um modelo reativo

• Criando hipermídia de forma reativa

Onde encontrar o código deste capítulo

O código deste capítulo pode ser encontrado em https://github.com/PacktPublishing/


Learning-Spring-Boot-3.0/tree/main/ch9.
Machine Translated by Google

204 Escrevendo controladores Web reativos

O que é reativo e por que nos importamos?


Por literalmente décadas, vimos várias construções destinadas a ajudar a dimensionar aplicativos. Isso inclui pools de
threads, blocos de código sincronizados e outros mecanismos de troca de contexto destinados a nos ajudar a executar
mais cópias de nosso código com segurança.

E, em geral, todos falharam.

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.

Continuamos usando esse termo, reativo. O que isso significa?

Neste contexto, estamos falando de Fluxos Reativos. Isto é da documentação oficial:

“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.”

– Site oficial do Reactive Stream. (https://www.reactive-streams.org/)

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.

A contrapressão substitui o paradigma tradicional de publicação-assinatura por um sistema baseado em pull.


Os consumidores downstream entram em contato com os editores e têm o poder de solicitar 1, 10 ou quantas
unidades estiverem prontas para processar. O mecanismo de comunicação em fluxos reativos é chamado de sinais.

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

O que é reativo e por que nos importamos? 205

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.

O que a contrapressão permite?

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.

Detalhes do fluxo reativo


Reactive Streams é uma especificação muito simples, tão simples que possui apenas quatro interfaces: Publisher, Subscriber,
Assinatura e Processador:

• 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

• Processador: um componente que implementa tanto o Assinante quanto o Publicador

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

206 Escrevendo controladores Web reativos

Confira o seguinte trecho de código:

Flux<String> amostra = Flux.just("aprendizado", "primavera",


"bota") //
.filter(s -> s.contains("primavera")) //
.map(s -> {
System.out.println(s);
return s.toUpperCase();
});

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.

• just(): maneira do Reactor de criar uma coleção inicial de elementos Flux.

• filter(): Semelhante ao método filter() do Java 8 Stream, ele só permite a passagem de


elementos de dados do Flux anterior se eles satisfizerem a cláusula predicada. Neste caso,
contém a string "spring"?

• 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.

onSubscribe é o primeiro e mais importante sinal em Fluxos Reativos. É a indicação de que um


componente downstream está pronto para começar a consumir esses eventos upstream.

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 Assinante pode continuar a invocar o método de solicitação da Assinatura, solicitando mais.


Alternativamente, o Assinante pode cancelar sua Assinatura.

O editor pode continuar enviando conteúdo ou pode sinalizar que não há mais por meio do sinal
onComplete.
Machine Translated by Google

Criando um aplicativo Spring Boot reativo 207

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.

Criando um aplicativo Spring Boot reativo


Para começar a escrever aplicativos web reativos, precisamos de um aplicativo completamente novo. E para fazer
isso, vamos revisitar nosso velho amigo, https://start.spring.io.

Escolheremos as seguintes configurações:

• Projeto: Maven

• Linguagem: Java

• Inicialização Spring: 3.0.0

• Grupo: com.springbootlearning.learningspringboot3

• Artefato: capítulo 9

• Nome: Capítulo 9

• Descrição: Escrevendo controladores web reativos

• Nome do pacote: com.springbootlearning.learningspringboot3

• 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:

• Web Reativa Spring (Spring WebFlux)

É 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:

• Spring-boot-starter-webflux: inicializador do Spring Boot que extrai Spring WebFlux,


Jackson para serialização/desserialização JSON e Reactor Netty como nosso servidor web reativo

• spring-boot-starter-test: iniciador do Spring Boot para testes, inclusive incondicionalmente


para todos os projetos
Machine Translated by Google

208 Escrevendo controladores Web reativos

• 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.

Servindo dados com um método GET reativo


Os controladores da Web normalmente fazem uma de duas coisas: fornecer dados ou fornecer HTML. Para grocar a forma
reativa, vamos escolher a primeira, pois é muito mais simples.

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.

Podemos usá-lo em um controlador web como este:

@RestController

classe pública ApiController {


@GetMapping("/api/funcionários")
Flux<Funcionário> funcionários() {
retornar Flux.just( //
new Employee("alice", "gestão"), //
new Employee("bob", "folha de pagamento"));
}
}

Este controlador web RESTful pode ser descrito da seguinte forma:

• @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<Employee>: O tipo de retorno é um Fluxo de registros de Funcionários

Flux é como combinar uma lista Java clássica com um futuro. Mas não realmente.
Machine Translated by Google

Servindo dados com um método GET reativo 209

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:

Flux<String> a = Flux.just("alfa", "bravo");


Flux<String> b = Flux.just("charlie", "delta");
a.concatWith(b);
a.mergeWith(b);

Este código pode ser descrito da seguinte forma:

• aeb: Duas instâncias do Flux pré-carregadas

• 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

210 Escrevendo controladores Web reativos

Consumindo dados recebidos com um método POST reativo


Qualquer site que forneça registros de funcionários certamente deve ter uma forma de inserir novos, certo?
Então, vamos criar um método web que faça exatamente isso adicionando à classe ApiController que
iniciamos na seção anterior:

@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;
});
}

Este controlador Spring WebFlux possui os seguintes detalhes:

• @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

• Mono<Employee>: alternativa do Reactor ao Flux para um único item

• DATABASE: Um armazenamento de dados temporário (um mapa Java)

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.

mapa versus flatMap


Vimos map usado duas vezes na parte inicial do código no início deste capítulo e também neste
método mais recente. O mapeamento é uma operação um-para-um. Se mapeássemos um Flux
com 10 itens, o novo Flux também teria 10 itens. E se um único item, como uma string, fosse
mapeado em uma lista de suas letras? O tipo transformado seria uma lista de listas. Em muitas
dessas situações, gostaríamos de reduzir o aninhamento e simplesmente ter um novo Flux com todas as letras.
Isso é achatamento! flatMap é a mesma coisa, feito em uma única etapa!
Machine Translated by Google

Consumindo dados recebidos com um método POST reativo 211

Dimensionando aplicativos com Project Reactor


Então, como exatamente o Project Reactor dimensiona nosso aplicativo? Até agora, vimos como o Project Reactor nos
oferece um estilo de programação funcional. Mas pode não estar claro exatamente onde a escalabilidade entra em jogo.

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.

Histórico rápido sobre programação simultânea Java


Nos primeiros dias da programação simultânea Java, as pessoas criavam conjuntos gigantes de threads. Mas
aprendemos que quando você tem mais threads do que núcleos, a troca de contexto fica cara.

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

212 Escrevendo controladores Web reativos

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.

Enquanto isso, vamos aprender como implementar um modelo reativo.

Servindo um modelo reativo


Até agora, construímos um controlador reativo que fornece JSON serializado. Mas a maioria dos sites precisa renderizar
HTML. E isso nos leva aos modelos.

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.

Desta vez, insira as seguintes dependências:

• Web Reativa Spring

• 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:

1. Destaque a dependência do Maven.

2. Copie-o para a área de transferência.

3. Cole-o no arquivo pom.xml do nosso IDE.


Machine Translated by Google

Servindo um modelo reativo 213

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.

Criando um controlador web reativo


A próxima etapa é criar um controlador web focado em servir modelos. Para fazer isso, crie um novo arquivo
chamado HomeController.java e adicione o seguinte código:

@Controlador

classe pública HomeController {


@GetMapping("/")
public Mono<Renderização> index() {
retornar Flux.fromIterable(DATABASE.values()) //
.collectList() //
.map(funcionários -> Renderização //
.view("índice") //
.modelAttribute("funcionários", funcionários) //
.construir());
}
}

Há muito neste método de controlador, então vamos desmontá-lo:

• @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.

• collectList(): Este método Flux nos permite reunir um fluxo de itens em


Mono<Lista<Funcionário>>.
Machine Translated by Google

214 Escrevendo controladores Web reativos

• 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>.

Programação funcional 101


É programação funcional básica ter algum container como um Flux ou um Mono e você mapear o que está
dentro, o tempo todo, mantendo-o dentro do Flux ou Mono funcional. Você não precisa se preocupar em criar
novas instâncias do Mono. As APIs do Reactor foram projetadas para lidar com isso para você. Você se
concentra em transformar os dados ao longo do caminho. Contanto que as coisas permaneçam perfeitamente
encapsuladas nesses tipos de contêineres reativos, a estrutura irá descompactá-los adequadamente no
momento certo e renderizá-los adequadamente.

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 única coisa que resta é codificar esse modelo usando Thymeleaf!

Criando um modelo 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>

<title>Escrevendo um controlador web reativo</title>


</head>
Machine Translated by Google

Servindo um modelo reativo 215

<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>

Este modelo pode ser descrito da seguinte forma:

• xmlns:th=http://www.thymeleaf.org: Este namespace XML nos permite usar as diretivas de


processamento HTML do Thymeleaf.

• 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/>.

Prós e contras do Thymeleaf


Se servir de consolo, sempre que eu usar o Thymeleaf, devo ter a página de referência aberta para eu
ver. Se eu codificasse o Thymeleaf todos os dias, talvez não precisasse disso. Apesar deste aspecto, o
Thymeleaf é bastante poderoso. Existem extensões como suporte para Spring Security e a capacidade
de escrever verificações de segurança em seus modelos, permitindo que certos elementos sejam
renderizados (ou não) com base nas credenciais e autorizações do usuário. O Thymeleaf como um todo
provavelmente pode fazer qualquer coisa que você precisar quando se trata de criar HTML, às custas de ter que assumir
sua notação.

Se você iniciar nosso aplicativo reativo e navegar até http://localhost:8080, verá uma pequena página
da web renderizada:
Machine Translated by Google

216 Escrevendo controladores Web reativos

Figura 9.1 – Template reativo renderizado com Thymeleaf

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.

Para fazer isso, precisamos entrar no mundo da vinculação de formulá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.

No modelo index.html que criamos na seção anterior, precisamos adicionar o seguinte:

<form th:action="@{/new-employee}" th:object=


"${newEmployee}" método="post">
<input type="text" th:field="*{nome}" />
Machine Translated by Google

Servindo um modelo reativo 217

<input type="text" th:field="*{role}" />


<input type="enviar" />
</form>

Este modelo Thymeleaf pode ser descrito da seguinte forma:

• 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

• th:field="*{name}": diretiva do Thymeleaf para conectar o primeiro <input> ao


Nome do registro do funcionário

• th:field="*{role}": diretiva do Thymeleaf para conectar o segundo <input> ao


Função do registro do funcionário

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:/";

});
}

Esta operação pode ser descrita da seguinte forma:

• @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).

• Mono<Employee>: São os dados de entrada do formulário HTML, agrupados em um tipo Reactor.

• map(): Ao mapear os resultados recebidos, podemos extrair os dados, armazená-los no DATABASE e


transformá-los em uma operação de redirecionamento HTTP de volta para /. Isso resulta em um Mono<String>
tipo de retorno do método.
Machine Translated by Google

218 Escrevendo controladores Web reativos

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.

Se executarmos tudo novamente, veremos o seguinte:

Figura 9.2 – Inserção de novo funcionário no formulário

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

Criando hipermídia de forma reativa 219

O funcionário recém-inserido agora aparece na página da web.

O Spring WebFlux vale a pena?


Esse tipo de fluxo pode realmente parecer um pouco assustador, até mesmo desafiador no início. Mas com
o tempo, torna-se um estilo consistente que muitas vezes nos obriga a pensar em cada passo. Para um
aplicativo da web padrão, você pode questionar se vale a pena ou não. Mas se você tem um aplicativo que
exige a execução de centenas, senão milhares de cópias, e sua conta da nuvem está disparando , então
pode haver um motivo válido e econômico para considerar o uso do Spring WebFlux. Confira meu artigo, Por
que os fluxos reativos são o SEGREDO para CORTAR sua conta mensal da nuvem, em https://
springbootlearning.com/cloud-bill.

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.

Criando hipermídia de forma reativa


No início deste capítulo, criamos uma API muito simples. Serviu algum conteúdo JSON bastante básico. Uma coisa
que estava faltando em uma API tão simples eram quaisquer controles.

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.

E isso é fácil se adicionarmos Spring HATEOAS à nossa aplicação!

Spring Boot Starter HATEOAS versus Spring HATEOAS


Se você acessar start.spring.io e solicitar Spring HATEOAS, você terá spring- boot-starter-
hateoas adicionado ao seu aplicativo. Mas esta versão está errada ao usar Spring WebFlux.
Por muito tempo, Spring HATEOAS suportou apenas Spring MVC, mas cerca de 4 anos atrás,
você realmente adicionou suporte WebFlux. Infelizmente, o módulo Spring Boot Starter
HATEOAS obtém suporte para Spring MVC e Apache Tomcat, que é o oposto do que queremos
para um aplicativo Spring WebFlux rodando em cima do Reactor Netty. A abordagem mais
simples é apenas adicionar Spring HATEOAS diretamente, conforme mostrado aqui.
Machine Translated by Google

220 Escrevendo controladores Web reativos

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 {
}

Esta classe de controlador barebones pode ser descrita da seguinte forma:

• @RestController: anotação do Spring Web para marcar este controlador como focado na serialização
JSON em vez da renderização de modelo

• @EnableHypermediaSupport: anotação do Spring HATEOAS para ativar hipermídia


suporte – neste caso, suporte HAL

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

Criando hipermídia de forma reativa 221

.funcionário(chave)) //
.withSelfRel() //
.toMono();

Mono<Link> agregadaRoot = linkTo( //


métodoOn(HypermediaController.class) //
.funcionários()) //
.withRel(LinkRelation.of("funcionários"))//
.toMono();

Mono<Tuple2<Link, Link>> links = Mono.zip(selfLink,


raizAgregada);

retornar links.map(objetos ->


EntityModel.of(DATABASE.get(chave),objetos.getT1(),
objetos.getT2()));
}

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}.

• O tipo de retorno é Mono<EntityModel<Employee>>. EntityModel é o contêiner do Spring HATEOAS


para um objeto que inclui links. E já vimos como o Mono é o wrapper do Reactor para programação
reativa.

• 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).

• withRel(LinkRelation.of("employees")): método do Spring HATEOAS a ser aplicado


uma relação hipermídia arbitrária entre funcionários.
Machine Translated by Google

222 Escrevendo controladores Web reativos

• 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.

Então, o que o Spring HATEOAS faz?

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.

• CollectionModel<T>: A extensão genérica de RepresentationModel. Representa uma coleção de objetos T em


vez de apenas um.

• 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

Criando hipermídia de forma reativa 223

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:

• @GetMapping: Este método mapeia GET /hypermedia/employees para este método,


a raiz agregada.

• selfLink neste método aponta para este método, que é um ponto final fixo.

• FlatMap() sobre selfLink e, em seguida, extraímos cada entrada do DATABASE, aproveitando


o método Employee(String key) para converter cada entrada em um EntityModel<Employee>
com links de item único.

• Usamos collectList() para agrupar tudo isso em um Mono<List<EntityModel<Employee>>>.

• Por fim, mapeamos sobre ele, convertendo-o em Mono<CollectionModel<EntityModel


<Employee>>> com o selfLink da raiz agregada conectado.

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

224 Escrevendo controladores Web reativos

Se executarmos o aplicativo, veremos facilmente aonde isso nos leva:

% 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

Criando hipermídia de forma reativa 225

"_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"
}
}
}

Isso é muita informação. Vamos destacar algumas partes principais:

• _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.

O que é um link “próprio”?

Na hipermídia, praticamente qualquer representação incluirá o que é chamado de link “próprio”. É


esse conceito. Essencialmente, é um ponteiro para o registro atual. É importante entender o contexto.
Por exemplo, a saída HAL mostrada anteriormente possui três links próprios diferentes. Apenas o último é
o próprio deste documento. Os outros são os links canônicos para consultar esse registro individual.
E como os links são essencialmente opacos, você pode usá-los para navegar até esses registros.

Qual é o sentido de fazer tudo isso?


Machine Translated by Google

226 Escrevendo controladores Web reativos

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.

Para hipermídia ou não para hipermídia, eis a questão


A hipermídia possibilita conectar usuários de forma dinâmica com operações relacionadas e
dados relevantes. Como não há espaço suficiente neste capítulo para nos aprofundarmos em
todos os prós e contras da hipermídia, confira Spring Data REST: Data Meets Hypermedia em https://
springbootlearning.com/hypermedia, onde você pode ver eu e meu colega de equipe Roy Clarkson entrarmos
em detalhes sobre hipermídia e Spring.

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.

Neste capítulo, abordaremos os seguintes tópicos:

• Aprender o que significa buscar dados de forma reativa

• Escolher um armazenamento de dados reativo

• Criação de um repositório de dados reativos

• Experimentando R2DBC

Onde encontrar o código deste capítulo

O código deste capítulo pode ser encontrado neste repositório: https://github.com/


PacktPublishing/Learning-Spring-Boot-3.0/tree/main/ch10.

Aprendendo o que significa buscar dados de forma reativa


No capítulo anterior, cobrimos muitos dos princípios básicos necessários para construir uma página web de forma reativa, exceto
que faltava um ingrediente vital: dados reais.

Os dados reais vêm de bancos de dados.

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

228 Trabalhando com dados de forma reativa

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.

Mas uma área que está estagnada é o JDBC.

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

Escolhendo um armazenamento de dados reativo 229

Escolhendo um armazenamento de dados reativo

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.

R2DBC? Me diga mais!

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

• Dados Spring R2DBC

Se clicarmos no botão EXPLORE e rolarmos até a metade do arquivo pom.xml, veremos


as três entradas a seguir:

<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

230 Trabalhando com dados de forma reativa

Essas três dependências podem ser descritas da seguinte forma:

• spring-boot-starter-data-r2dbc: iniciador do Spring Boot para Spring Data R2DBC

• h2: o banco de dados incorporável de terceiros

• r2dbc-h2: o driver R2DBC da equipe Spring para H2

É 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!

Criando um repositório de dados reativos


Anteriormente no Capítulo 3, Consultando dados com Spring Boot, construímos um repositório de dados fácil de ler
estendendo JpaRepository do Spring Data JPA. Para Spring Data R2DBC, vamos escrever algo assim:

interface pública EmployeeRepository estende //

ReactiveCrudRepository<Funcionário, Longo> {}

Este código pode ser descrito da seguinte forma:

• EmployeeRepository: O nome do nosso repositório Spring Data.

• 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).

• Long: O tipo da chave primária.

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:

classe pública Funcionário {


private @Id ID longo;
nome da string privada;
Machine Translated by Google

Experimentando R2DBC 231

função String privada;

funcionário público (String nome, String função) {


este.nome = nome;
this.role = função;
}
// getters, setters, equals, hashCode e toString omitidos por questões de brevidade

Este código pode ser descrito da seguinte forma:

• Employee: O tipo do nosso domínio, conforme exigido na declaração EmployeeRepository.

• @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.

• nome e função: Os outros dois campos que usaremos.

O restante dos métodos deste tipo de domínio podem ser gerados por qualquer IDE moderno usando seus utilitários.

Com tudo isso, estamos prontos para começar a falar R2DBC!

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

CommandLineRunner initDatabase(modelo R2dbcEntityTemplate) {

retornar argumentos -> {


// Em breve!
}
}
}
Machine Translated by Google

232 Trabalhando com dados de forma reativa

Este código pode ser descrito da seguinte forma:

• @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();

Este pedaço de código pode ser descrito da seguinte forma:

• template.getDatabaseClient(): Para SQL puro, precisamos de acesso ao DatabaseClient subjacente do módulo


R2DBC do Spring Framework que faz todo o trabalho.

• 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.

• fetch(): A operação que executa a instrução SQL.

• 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.

• expectNextCount(1): Verifica se recuperamos uma linha, indicando que a operação funcionou.

• verifyComplete(): Garante que recebemos um sinal onComplete de Fluxos Reativos.


Machine Translated by Google

Experimentando R2DBC 233

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!

Agora, com isso instalado, vamos carregar alguns dados.

Carregando dados com R2dbcEntityTemplate


Até agora, configuramos o esquema para nosso tipo de domínio Employee. Com isso implementado, estamos
prontos para adicionar mais algumas chamadas R2dbcEntityTemplate dentro desse CommandLineRunner initDatabase():

template.insert(Employee.class) //
.using(new Employee("Frodo Bolseiro", "portador do

anel")) .as(StepVerifier::create) // .expectNextCount(1) // .verifyComplete();

template.insert(Employee.class) //
.using(new Employee("Samwise Gamgee",

"gardener")) // .as(StepVerifier::create) // .expectNextCount(1) //


.verifyComplete();

template.insert(Employee.class) //
.using(new Employee("Bilbo Baggins",

"burglar")) // .as(StepVerifier::create) // .expectNextCount(1) //


.verifyComplete();

Todas essas três chamadas têm o mesmo padrão. Cada um pode ser descrito da seguinte forma:

• insert(Employee.class): Define uma operação de inserção. Ao fornecer um parâmetro de tipo,


as operações subsequentes são seguras.

• using(new Employee(…)): Aqui é onde os dados reais são fornecidos.


Machine Translated by Google

234 Trabalhando com dados de forma reativa

• as(StepVerifier::create): O mesmo padrão de uso do Reactor Test para forçar o


execução do nosso fluxo reativo.

• expectNextCount(1): Para uma única inserção, esperamos uma única resposta.

• verifyComplete(): Verifica se recebemos o sinal onComplete.

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.

Retornando dados de forma reativa para 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

classe pública ApiController {


repositório final privado EmployeeRepository;
public ApiController(repositório EmployeeRepository) {
este.repositório = repositório;
}
}

Esta classe de controlador de API pode ser descrita da seguinte forma:

• @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

Experimentando R2DBC 235

Este método web é bastante simples:

• @GetMapping: mapeia chamadas HTTP GET /api/employees para esse método

• Flux<Employee>: Indica que retorna um (ou mais) registros de Funcionários

• repositório.findAll(): Ao usar o método findAll pré-construído da interface ReactiveCrudRepository do


Spring Data Commons, já temos um método que irá buscar todos os dados

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);
});
}

Este método web possui os seguintes recursos:

• @PostMapping(): mapeia chamadas HTTP POST /api/employees para esse método.

• Mono<Employee>: Este método retorna no máximo uma entrada.

• @RequestBody(Mono<Employee>): Este método desserializará o corpo da solicitação recebida em um


objeto Employee, mas como está empacotado como Mono, esse processamento só acontecerá
quando o sistema estiver pronto.

• newEmployee.flatMap(): É assim que acessamos o objeto Employee recebido.


Dentro da operação flatMap, criamos um objeto Employee totalmente novo, descartando deliberadamente
qualquer valor de id fornecido na entrada. Isso garante que uma entrada completamente nova será feita
no banco de dados.

• repositório.save(): Nosso EmployeeRepository executará uma operação de salvamento e retornará


Mono<Employee>, com um objeto Employee recém-criado dentro dele. Este novo objeto terá tudo,
incluindo um novo campo de identificação.
Machine Translated by Google

236 Trabalhando com dados de forma reativa

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>>.

flatMap é o martelo de ouro do Reactor


Muitas vezes, quando você não tem certeza do que fazer ou as APIs do Reactor parecem estar
trabalhando contra você, o segredo geralmente é flatMap(). Todos os tipos de Reactor estão fortemente
sobrecarregados para suportar flatMap, de modo que Flux<Flux<?>>, Mono<Mono<?>> e cada
combinação deles funcionarão bem quando você simplesmente aplicar flapMap(). Isso também se
aplica se você usar o operador then() do Reactor. Usar flatMap() antes de then() geralmente garantirá
que a etapa anterior seja executada!

A última etapa na montagem de nosso aplicativo web reativo é preencher nosso modelo Thymeleaf, que
abordaremos na próxima seção.

Lidar reativamente com dados em um modelo


Para finalizar, precisamos criar uma classe HomeController, assim:

@Controlador

classe pública HomeController {


repositório final privado EmployeeRepository;
public HomeController(repositório EmployeeRepository) {
este.repositório = repositório;
}
}

Esta aula tem alguns aspectos principais:

• @Controller: Indica que esta classe de controlador está focada na renderização de templates

• EmployeeRepository: Nosso querido EmployeeRepository também é injetado neste controlador usando


injeção de construtor
Machine Translated by Google

Experimentando R2DBC 237

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:

• repositório.findAll(): Em vez de converter os valores de um mapa em Flux, EmployeeRepository já nos


fornece um com seu método findAll()

Todo o resto é igual.

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

238 Trabalhando com dados de forma reativa

Isso é muito semelhante ao método newEmployee() do capítulo anterior, exceto pelas partes destacadas:

• flatMap(): Como mencionado na seção anterior, porque save() retorna um


Mono<Employee>, precisamos flatMap os resultados.

• 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.

• map(employee -> "redirect:/"): Aqui, traduzimos o objeto Employee salvo


em uma solicitação de redirecionamento.

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.)

E com isso temos um armazenamento de dados reativos totalmente armado e operacional!

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:

• http://bit.ly/3uSPLCz: Confira meu canal no YouTube, repleto de vídeos gratuitos


sobre a Bota Spring

• https://springbootlearning.com/medium: escrevo artigos semanais sobre Spring Boot e também sobre


engenharia de software em geral

• https://springbootlearning.com/podcast: publico periodicamente episódios apenas de áudio onde entrevisto


líderes da comunidade Spring ou compartilho pedaços de conhecimento sobre o mundo do Spring

• https://twitter.com/springbootlearn: Se você quiser se conectar, siga-me no Twitter

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

de compilação antecipada do protocolo orientado a comportamento (BDD) 132


(AMQP) 8 (AOT) 194
bytecode 191, 197

Compilação AOT 196

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

234-236 , para retrofit do Docker Hub Vulnerabilidades comuns e

179-182, para configuração de aplicativos Exposições (CVE) 19

GraalVM 190-194 externalizando propriedades de configuração

14-17 dependências de usadas, para personalizar a configuração 11, 12

aplicativos gerenciando 18, 19 injeção de construtor 113, 236

aplicativos, Falsificação de solicitação entre sites (CSRF) 90-92

protegendo aspectos-chave curl 86

79 assembly 206 localizadores personalizados

autenticação 85, resultados da consulta, limitando 69, 70


148 autoridades 88 resultados, classificando
autorização 85, 68 usando 63-68
148
Machine Translated by Google

242 Índice

JPA personalizado função de filtro de troca 109


usando
propriedades
F
personalizadas 74-76 criando 13, 14, 156-160
política de segurança flushing 59
personalizada usada, para criação de usuários 79-81 autenticação de formulário

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 ,

objetos de transferência de dados modernizando 190-194 benefícios 190


(DTOs) 60 contêiner

injeção de dependências, por meio de Docker, preparando 199 aplicativos Spring


chamadas de construtor 36 injeção de dependência (DI) 6 Boot nativos, executando 194-197
Contêiner Docker necessidade de 198,

assando 175, 176 199 acessibilidade

assando, com GraalVM 199 191 GraalVM


construindo 176-179 Community Edition (CE) 195
DockerHub

acessando 179
H
aplicações, liberando para 179-182 tags
181 testes troca de usuários

de objetos de codificados , com Spring


Conjunto de usuários com base em dados 81-85
domínio , criando 123-127
DTOs versus link de Dados de
referência de entidades 61 formulários HTML , modificando até 37-39
HTTP205
HTTP 302 encontrado 96
E
Mecanismo de autenticação HTTP Básico 86

entidades 59, 60 Proxies de cliente HTTP 109


variáveis de ambiente Mecanismo de autenticação de formulário HTTP 86

usadas, para definir propriedades 166, 167


Machine Translated by Google

Índice 243

Verbos HTTP
K
protegendo 85-89
Hipermídia como motor de aplicação Kubernetes 186
State (HATEOAS) 9 APIs

baseadas em hipermídia criando


eu
219-225
expressão lambda 83
função lambda 211
J.
Acesso leve ao diretório
Jackson 60 Protocolo (LDAP) 9

Link de referência Linux 196


11 de Jacarta EE 11 suporte de longo prazo (LTS) 45
Java 17 registra 230
Feijão Java 6
M
História de programação simultânea
Java 211 mapa
Conectividade de banco de dados Java versus flatMap 210
(JDBC) 7, 58, 228 Lista de materiais (BOM) Maven 19
Java Development Kit (JDK) 173 práticas de segurança baseadas em métodos,
Expressão Java lambda 160 Java Acesso a dados

Object Oriented Querying (jOOQ) 9 Pacote Spring , bloqueando o proprietário do botão de


JavaScript, exclusão de dados 97, adicionando
com Node.js 46, 47 Java Virtual segurança em nível de método 95-97, habilitando
Machine (JVM) 191, 197 JPA Query Language o modelo 97, 98, atualizando
(JPQL) 65 Criação de APIs baseadas a propriedade dos dados 92-94, obtendo
em JSON 40-44 detalhes do usuário 94, 95, exibindo no site
98-102 zombando 122
JSqlParser 75 Controladores

Anotação JSR-250 98 web MockMVC , testando com 127-130


JUnit 5

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

produção , ajustando 182, 183

aplicativo Spring Boot nativo em arquivos de propriedades baseados

execução, em GraalVM 194-197 Nickel/ em perfil criando

Couchbase Query Language 161-163 perfis


(N1QL) 74 Node.js hooking, 15 tipos de projeção 63

para Aplicativos do Project

Spring Boot web appNode.js 44-46 Reactor 205 , escalonamento com


JavaScript, empacotando configuração

com 46, 47 de 211 propriedades , com variáveis de ambiente 166, 167

execução do pacote de nó (npx) 46 beans baseados em


nonce 90 propriedades

não apenas SQL (NoSQL) 56 configurando 17, 18

substituições de propriedades ordenando 167-169

O
OAuth 102
P
contras 103 Consultar por exemplo

prós 103 usando, para pesquisar respostas complicadas


URL 102 70-74 derivação de consulta 62
OAuth2 Consulta 57
URL 108

API OAuth2
R
invocando, remotamente 108-112

Aplicativo da web com tecnologia Dados R2dbcEntityTemplate ,

OAuth2 criando 112-120 carregando com 233, 234


Cliente OAuth Reativo 204

adicionando, ao projeto Spring Boot 105-108 importância 204

OpenJDK versão 17, 195 dados reativos

repositório, criando 230, 231

P fornecendo, para o controlador API 234-236


armazenamento de dados reativo

Pacotes de construção Paketo selecionando 229, 230


URL 177 método GET reativo

Objetos Java simples e antigos (POJOs) 61 usado, para servir dados 208, 209

adição de componentes de método POST reativo usado,

portfólio, iniciadores Spring Boot usados 10, 11 testes para consumir dados recebidos 210

70
Machine Translated by Google

Índice 245

Banco de dados relacional reativo Spring beans 6


Conectividade (R2DBC) 9, 229 contexto de aplicativo 6, 7
trabalhando com aplicativo autoconfiguração 6
Spring Boot reativo 231-233 políticas de
criando 207, 208 configuração automática do Spring Boot ,
Fluxo Reativo 205 explorando o link de
Processador 205 referência 7-9 196
Editora 205 dimensionamento
Assinante 205 com 183-187 Spring Boot 2.7
Assinatura 205 versus Spring Boot 3.0 200
URL 204 versus Spring
modelo reativo Native 200 Spring Boot 3.0
atendendo ao versus Spring Boot 2.7 200 versus Spring Native 200
controlador web reativo 212 Aplicativo Spring Boot
criando 213, 214 Spring Data, somando-se aos 56 existentes
aplicativo Projeto Spring Boot
React.js criando Cliente OAuth, somando 105-108
48-54 Reactor Netty Spring Boot Starter HATEOAS
208 Redis versus Spring HATEOAS 219
56 Spring Boot starters
funções 88 RSocket 205 usados, para adicionar
RxJava 3 205 componentes de
portfólio 10, 11 Spring Boot web
appNode.js Node.js,
S
conectando-se
escopos 102 a 44 Spring Data adicionando,
link de ao aplicativo Spring
referência sdkman 194 Boot existente 56
Teste de políticas de segurança métodos, protegendo 92
Secure Sockets repositórios, criando 62, 63
Layer (SSL) 11 , com Spring Security Test usando, para
146-151 self gerenciar dados 57
link 225 sinais 204 Spring Data JPA
Protocolo Simples de Acesso a Objetos adicionando,

(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

Spinnaker 186 Integração Primavera 19


Machine Translated by Google

246 Índice

Spring MVC 80 versus Thymeleaf 99, 212 prós e

Spring Web 10 Controlador contras 215

web Spring MVC criando 27, 28 Modelo Thymeleaf criando

Spring Native 200 dados reativos 214-219,

versus Spring Boot 2.7 e lidando com 236-238

3.0 200 Spring Security 10, 77 adicionando, Link de referência do aplicativo

ao projeto 78 link de referência de doze fatores 168 168

102 digite apagamento 160

Spring Security Teste


EM
políticas de segurança, testando com 146-151

Spring Web uber JAR

versus Spring MVC 10 Spring criando 172-174

WebFlux 80, 207, 219 Biblioteca de Usuários

análise SQL 75 start.spring.io criando, com política de segurança personalizada 79-81

usando, benefícios

25, 26 usando, para aumentar


EM
um projeto existente 28-30 usando, para construir aplicativos

24, 25 StepVerifier 233 Structured construção de

Query Language aplicativos web 34, 35

(SQL) 70 stub 134 controladores da web

criando 26

testes, com rotas web MockMVC 127-130

T
protegendo 85-89

Kit de compatibilidade de tecnologia (TCK) 195 modelos WebSockets/Aeron 205

57 dados de fiação 7

demonstração, somando 31-33 trabalho roubando 211, 228

aproveitamento para criar conteúdo 30, 31


Contêineres de teste
E
adicionando, ao aplicativo 139, 141

repositórios de dados, testando com 142-146 YAML não é linguagem de marcação (YAML) 11 mudando
testes para 164, 166

criando, para objetos de domínio 123


Machine Translated by Google

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.

Por que assinar?


• Gaste menos tempo aprendendo e mais tempo codificando com e-books e vídeos práticos de mais de 4.000
profissionais do setor

• Melhore seu aprendizado com Planos de Habilidades criados especialmente para você

• Receba um e-book ou vídeo grátis todo mês

• Totalmente pesquisável para fácil acesso a informações vitais

• Copiar e colar, imprimir e marcar conteúdo

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

Outros livros que você pode gostar

Se você gostou deste livro, pode estar interessado nestes outros livros de Packt:

Bota Spring e Angular

Devlin Basilan Duldulao, Seiji Ralph Villafranca

ISBN: 978-1-80324-321-4

• Explorar como arquitetar Angular para desenvolvimento de aplicativos de nível empresarial •

Criar um projeto Spring Boot usando Spring Initializr

• Crie APIs RESTful para desenvolvimento de aplicativos de nível empresarial

• 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

• Escreva testes para manter um aplicativo Java Spring Boot saudável

• Implementar testes e implantações modernas de aplicativos de front-end e back-end


Machine Translated by Google

Outros livros que você pode gostar 249

Desenvolvimento Full Stack com Spring Boot e React - Terceira Edição

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

• Explore o uso de testes unitários e JWTs com Spring Security •

Empregue React Hooks, props, states e muito mais para criar seu frontend

• Descubra uma ampla variedade de componentes avançados do React e de terceiros

• Crie aplicativos de alto desempenho completos com funcionalidade CRUD • Aproveite o

MUI para personalizar seu front-end

• Teste, proteja e implante seus aplicativos com alta eficiência


Machine Translated by Google

250

Packt está procurando autores como você

Se você estiver interessado em se tornar um autor da Packt, visiteauthors.packtpub.com e inscreva-se hoje.


Trabalhamos com milhares de desenvolvedores e profissionais de tecnologia, assim como você, para ajudá-
los a compartilhar suas ideias com a comunidade global de tecnologia. Você pode fazer uma inscrição geral,
candidatar-se a um tópico específico para o qual estamos recrutando um autor ou enviar sua própria ideia.

Compartilhe seus pensamentos

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

Baixe uma cópia gratuita em PDF deste livro


Obrigado por adquirir este livro!

Você gosta de ler em qualquer lugar, mas não consegue levar seus livros impressos para qualquer lugar?

A compra do seu e-book não é compatível com o dispositivo de sua escolha?

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

Siga estas etapas simples para obter os benefícios:

1. Digitalize o código QR ou visite o link abaixo

https://packt.link/free-ebook/9781803233307

2. Envie seu comprovante de compra

3. É isso! Enviaremos seu PDF grátis e outros benefícios diretamente para seu e-mail
Machine Translated by Google

Você também pode gostar