Olá! Eu sou o Rodrigo Vieira, instrutor da Alura e professor do curso de Análise e Desenvolvimento de Sistemas da FIAP.
Neste curso, nós vamos estudar as principais mudanças do Spring Boot 3 para o Spring Boot 4. Para isso, conheceremos uma aplicação de e-commerce (comércio eletrônico) que se integra a outras duas aplicações. Essa aplicação foi escrita com Spring Boot 3 e Spring Framework 6. Sim, o Spring Boot agrega outros projetos do Spring, entre eles o Spring Framework. Ela também utiliza diversos recursos, como Spring Security — ao fazermos um pedido no e-commerce (comércio eletrônico), é necessário nos autenticarmos previamente.
Além disso, essa aplicação se integra ao Kafka, utiliza Flyway e explora vários recursos do Spring. Trabalharemos com resiliência usando o padrão Circuit Breaker (disjuntor de circuito), banco de dados PostgreSQL, e trata-se de uma solução o mais próxima possível do mundo real. Também teremos registros de log e integração com o Prometheus, ou seja, informações expostas pelo Actuator em conjunto com o Prometheus, trazendo informações da aplicação.
Nosso objetivo não é simplesmente atualizar a versão. Precisamos entender o que mudou nas bibliotecas do Spring: modificações de performance, substituição de bibliotecas, abandono de determinadas classes ou interfaces e adoção de outras, entre outros pontos.
Por que migrar para o Spring Boot 4? Com a migração, nós passamos a trabalhar com versões mais modernas dos frameworks — por exemplo, do Spring Framework 6 para o 7 —, obtemos melhorias de performance e avançamos para o Jakarta EE 11. É importante modernizarmos com critério. Veremos como garantir essa modernização de forma segura, reduzindo problemas. É comum, ao atualizar aplicações Spring Boot, surgir uma série de incompatibilidades de bibliotecas ou versões. Apresentaremos técnicas que nos ajudam a modernizar minimizando esses impactos.
Quanto à estrutura, estamos falando da solução como um todo: é relativamente simples, mas reúne vários recursos que o Spring Boot oferece. Teremos um client (cliente) — que pode ser um front-end (interface) ou o Postman —, utilizaremos o PostgreSQL como banco de dados, e a API do e-commerce (comércio eletrônico) será uma aplicação Spring Boot. Também teremos aplicações auxiliares responsáveis por informações de produto, como precificação e frete, executando em portas diferentes, todas desenvolvidas com Java e Spring Boot. Ao lado, haverá um tópico do Kafka: sempre que criarmos um pedido no e-commerce (comércio eletrônico), esse pedido será enviado ao tópico para consumo por outras aplicações. Portanto, é uma estrutura simples, porém com muitos recursos do Spring.
Quanto ao fluxo, o client (cliente) — seja o Postman, outro client (cliente) de sua preferência ou o front-end (interface) — interage com a nossa API do e-commerce (comércio eletrônico), que executa várias tarefas. Ela persiste produtos e pedidos no PostgreSQL, utilizando o Flyway para controle de versão do banco de dados. Além disso, a API acessa uma API de precificação e outra de frete para calcular o valor total do pedido. Por fim, publica um evento de “pedido criado” em um tópico do Kafka com nome equivalente, para ser consumido por outras aplicações.
A stack inicial busca trazer o que há de mais moderno no Spring Boot 3 e disponibilizar diversos recursos do ecossistema Spring. Nós vamos atualizar todos esses componentes, realizando as modificações necessárias ao longo do projeto.
Trabalharemos com Spring Data JPA para persistir em PostgreSQL as informações de pedido e produto.
Teremos Spring Security, pois algumas das nossas APIs utilizarão autenticação. A proposta é demonstrar Spring Security em endpoints específicos, não em todos, implementando uma infraestrutura de autenticação para pessoas usuárias apenas onde for pertinente.
Além da integração com Kafka, adotaremos duas formas de integrar o e-commerce aos serviços auxiliares de precificação e de frete. Utilizaremos RestTemplate para o cálculo de frete e HttpInterface para precificação. O HttpInterface foi introduzido no Spring Framework 6 (trazido pelo Spring Boot 3) como uma alternativa moderna em relação ao Feign, trabalhando com interfaces de maneira similar ao que o Feign faz.
Implementaremos resiliência nas integrações com as demais aplicações utilizando Resilience4j.
Nos testes, trabalharemos com JUnit 5 e AssertJ, e também com testes assíncronos por meio da biblioteca Awaitility. Essa biblioteca é muito útil quando precisamos aguardar o resultado de um processamento para, em seguida, validar se o teste passa ou não. Ao publicar uma mensagem em um tópico Kafka, por exemplo, é necessário aguardar até que a mensagem esteja disponível no broker; portanto, não podemos verificar imediatamente após a publicação. Esse processo leva um tempo até o Kafka confirmar a disponibilidade da mensagem. Interromper a aplicação com Thread.sleep, por exemplo, não é uma boa prática. Para evitar esse tipo de abordagem, utilizamos o Awaitility, que fornece recursos adequados para sincronização em testes.
Para testes mais pesados, utilizaremos H2 em cenários rápidos que envolvem banco de dados. Quando precisarmos validar toda a parte de conexão com recursos reais — banco de dados, Kafka e demais serviços —, trabalharemos com Testcontainers. Para alguns testes locais relacionados a Kafka, sem necessidade de integração externa, utilizaremos Embedded Kafka.
Quanto às escolhas da stack: PostgreSQL é um banco de dados amplamente utilizado no mercado, razão pela qual o adotamos. Kafka também é bastante difundido. O RestTemplate ainda é muito usado; contudo, no contexto do Spring Boot 3, há alternativas modernas, e vamos migrar gradualmente para RestClient, demonstrando o processo de migração. Já o HttpInterface surge como um padrão que substitui o Feign em diversos cenários, entre outras melhorias que abordaremos.
Adotamos as seguintes convenções no projeto:
records, por serem mais simples e objetivos para esse propósito.Agora que já vimos a parte teórica da nossa aplicação e toda a infraestrutura, vamos executá-la na prática. Vamos criar pedidos, criar produtos para que possamos comprar, visualizar os pedidos na base de dados e também no Kafka.
Para isso, inicialmente subimos a infraestrutura da aplicação. Em seguida, vamos subir a aplicação usando o comando mvn spring-boot:run.
Aguardamos um momento. Pronto: a aplicação já está no ar e se conectando ao Kafka.
Vamos verificar o estado após a conexão, acessando a interface do Kafka (UI, interface). Já temos o tópico de pedido criado, provisionado junto com a aplicação, e ainda não há nenhum pedido publicado.
Confirmamos: não há nenhum pedido, pois ainda não criamos nada. O tópico está vazio.
Vamos abrir o PostgreSQL e, para acessá-lo, vamos utilizar o DBeaver.
O DBeaver é um aplicativo cliente de banco de dados que permite conectar-se a diversos bancos de dados relacionais e também a alguns não relacionais. A grande maioria é relacional, e boa parte é não relacional.
Vamos criar uma nova conexão com banco de dados, selecionando PostgreSQL.
Avançamos e verificamos o nome do banco de dados. Consultando no Docker, identificamos que o banco está rodando na porta 5432 no localhost (máquina local). O endereço já sabemos: localhost:5432. Agora precisamos do nome do banco, usuário e senha.
Para descobrir essas informações, consultamos o Docker Compose da solução de e-commerce (comércio eletrônico). Constatamos que o banco se chama e-commerce e que tanto o usuário quanto a senha são postgres.
Vamos inserir esses dados no DBeaver: banco de dados e-commerce, usuário postgres e senha postgres. Copiamos as credenciais para evitar erro de digitação e conectamos. Conectou com sucesso.
No banco e-commerce, no esquema public, já há as tabelas de clientes, itens de pedido, pedidos e produtos. O produto é cadastrado e, em seguida, podemos fazer pedidos com ele. Cada pedido possui itens de pedido, e cada item de pedido está relacionado a um produto e a uma quantidade desse produto.
Com a infraestrutura e a aplicação de e-commerce (comércio eletrônico) no ar, vamos realizar algumas chamadas à API (interface de programação de aplicações) do e-commerce. Para isso, vamos utilizar o Postman.
Inicialmente, vamos explorar a API de produtos, que permite criar e listar produtos, entre outras operações relacionadas.
Inserimos propositalmente duas chamadas de criação de produtos na API de produtos para facilitar os testes, evitando ter que alterar uma única chamada com informações diferentes. Assim, mantemos duas chamadas separadas para criação.
Como acabamos de subir a aplicação, se executarmos a chamada de listagem de produtos, ainda não haverá nenhum produto. Portanto, vamos criar dois produtos no e-commerce. Disparamos a primeira chamada de criação e o retorno indica sucesso. Em seguida, executamos a criação do segundo produto, também com sucesso.
Listando os produtos, verificamos que os dois foram cadastrados.
Podemos buscar um produto por ID (identificador). Vamos utilizar o ID 1. A resposta retorna o produto notebook, sem problemas.
Temos também a API de pedidos. Ela é autenticada: é necessário gerar antes um token (token de acesso) no Keycloak para utilizá-la. Sem isso, não é possível efetuar as operações.
Vamos tentar criar um pedido para verificar o comportamento. A criação não ocorreu: acesso não autorizado. Isso aconteceu porque não fornecemos o token de autenticação gerado no Keycloak.
Ainda não criamos o token. Para criá-lo, precisamos acessar a API de autenticação para gerar esse token. Vamos executar a chamada do serviço, isto é, o endpoint (ponto de extremidade). Obtivemos o access token (token de acesso) corretamente.
Este projeto no Postman foi configurado para armazenar o token automaticamente nas variáveis do próprio Postman. O script (roteiro) associado já guarda o token nas variáveis que podem ser usadas neste projeto do Postman.
Em seguida, vamos executar a API de criação de pedido. Vamos conferir o nosso body (corpo): o cliente possui id igual a 1, e os itens incluem produtoId igual a 1, com quantidade 2. Ou seja, vamos comprar 2 notebooks no mesmo pedido para o cliente de id 1.
Como já criamos o token, podemos conferir na aba Authorization o esquema Bearer Token. Trata-se do token que acabamos de gerar. Vamos utilizar a autenticação OAuth 2. Ao executar, o pedido foi criado; temos o id do pedido corretamente retornado.
Se pesquisarmos o pedido por id, por exemplo, pedido 1, ao enviar a requisição o pedido é localizado sem problemas.
Também realizamos um teste importante: criação de pedido sem token. Nesse caso, deve ocorrer falha de segurança. Ao executar, recebemos “não autorizado”, indicando a falha de segurança conforme esperado.
Agora que vimos a criação de pedidos e produtos, vamos apresentar um pouco do código. Antes disso, precisamos verificar o tópico do Kafka para confirmar se o evento de criação de pedido foi publicado. No tópico, constatamos a mensagem de “pedido criado”. A mensagem foi gerada corretamente.
Com a mensagem já confirmada, vamos conhecer a integração do e-commerce com outras aplicações. Após testarmos a nossa aplicação, vamos ver como está sendo feita a integração do e-commerce com os serviços de frete e de precificação.
Inicialmente, vamos observar o cliente de precificação. Ele utiliza o Spring HTTP Interface (Interface Client). É muito parecido com o Feign; inclusive indicamos que é o substituto nativo do Feign. Não utiliza as classes do Feign, e sim classes do próprio Spring. Faz uma chamada simples para obter os ajustes de preço junto ao serviço de precificação.
Para ilustrar esse cliente HTTP Interface, segue a interface utilizada para obter o ajuste de preço:
@HttpExchange("/api/v1/ajuste")
/*
* HTTP Interface Client (Spring 6.1+) - substituto nativo do Feign.
*
* O proxy é criado via HttpServiceProxyFactory na classe de configuração.
*/
public interface ClientePrecificacao {
@GetExchange("/api/v1/ajuste")
BigDecimal obterAjustePreco(@RequestParam("cliente") String emailCliente);
}
Já no caso de frete, a implementação utiliza o RestTemplate. Recebemos o RestTemplate como dependência no construtor, conforme já mencionamos anteriormente sobre injeção de dependências via construtor. Em seguida, fazemos a chamada com o RestTemplate para obter a cotação do frete a partir do e-mail do cliente. Com isso, obtemos um BigDecimal com a cotação, registramos no log e retornamos a cotação. Assim, deixamos claro que, em uma integração, usamos o HTTP Interface Client do Spring, e, na outra, usamos o RestTemplate. Dessa forma, conseguimos exemplificar o que precisa ser feito em cada caso.
Primeiro, veja a classe do cliente de frete com a injeção do RestTemplate e a configuração da URL base:
public class ClienteFrete {
private final RestTemplate restTemplate;
private final String uriBase;
public ClienteFrete(RestTemplate restTemplate,
@Value("${ecommerce.frete.url:http://localhost:8082}") String uriBase) {
this.restTemplate = restTemplate;
this.uriBase = uriBase;
}
Em seguida, o método que realiza a chamada HTTP para obter a cotação:
public BigDecimal obterCotacaoFrete(String emailCliente) {
logger.info("evento=frete-chamada-inicio cliente={} endpoint={}", emailCliente, uriBase);
var uri = uriBase + "/api/v1/cotacao?cliente=" + emailCliente;
var cotacao = restTemplate.getForObject(uri, BigDecimal.class);
logger.info("evento=frete-chamada-sucesso cliente={} cotacao={}", emailCliente, cotacao);
return cotacao;
}
Agora que vimos a aplicação em execução, vamos verificar se os testes estão todos corretos e funcionando, sem problemas. Embora a aplicação esteja funcionando e tenhamos testado alguns poucos cenários, dependendo do caso pode haver falhas. Vamos executar mvn test. Isso pode demorar um pouco, porque estamos com o Docker em execução. Não é por estarmos ocupando muito espaço em disco — ou melhor, muita memória — que os testes ficam lentos. Quando o Docker está no ar, os testes do Testcontainers também são executados, graças a uma configuração que fizemos em uma classe específica, na qual adicionamos uma anotação que verifica se o Docker está disponível. Se o Docker estiver disponível, a suíte de testes executa os testes do Testcontainers; caso contrário, não os executa, o que torna a execução dos testes mais rápida, pois esses testes passam a ser ignorados.
A anotação personalizada que controla a execução com base na disponibilidade do Docker é a seguinte:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@ExtendWith(DockerAvailableCondition.DockerAvailableExecutionCondition.class)
public @interface EnabledIfDockerAvailable {
String reason() default "Docker não está disponível - teste de Testcontainers pulado";
}
Se verificarmos quem utiliza essa anotação, veremos que ela está presente nos testes de integração com PostgreSQL, indicando que só devem rodar se o Docker estiver disponível. Além disso, também a adicionamos na classe abstrata de integração de testes. Quando formos detalhar o Testcontainers, vamos revisar com mais calma essa anotação e esse código, então não há motivo para preocupação.
Aqui estão as classes abstratas de teste anotadas para habilitar/ignorar com base no Docker:
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("integration")
@Testcontainers
@EnabledIfDockerAvailable
public abstract class AbstractPostgresIntegrationTest {
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("integration")
@Testcontainers
@EnabledIfDockerAvailable
public abstract class AbstractIntegrationTest {
Vamos conferir se os testes terminaram. Sim, finalizaram: 85 testes executados. O tempo foi de 40 segundos, mesmo com o Testcontainers e o Docker no ar. Vamos agora encerrar o Docker: docker compose down -v (para limpar também os volumes). Com tudo limpo, vamos rodar novamente os testes. A expectativa é que agora sejam mais rápidos, pois não haverá o Testcontainers subindo os containers. Estimamos algo em torno de 30 segundos, já que da outra vez levou 40 segundos.
Observamos que, ao tentar conectar no Kafka e não conseguir, o teste é ignorado. O tempo total foi de 33 segundos. A diferença não foi tão grande porque, nesta máquina, a subida dos containers estava relativamente rápida; dependendo do ambiente, os containers podem demorar mais para subir.
Nós vimos os testes rodando na aplicação de e-commerce (comércio eletrônico), mas ainda faltam os testes relacionados aos outros serviços: o serviço de precificação e o serviço de frete. Ambos executam em uma única aplicação, isto é, em um único repositório de código, que é o serviço parceiro. Nesse serviço, temos o controlador de frete (um controlador) e o controlador de precificação (outro controlador).
Tanto o controlador de frete quanto o de precificação não possuem banco de dados e não têm camadas adicionais de serviço; foram criados apenas para fins de teste. Eles retornam respostas pré-definidas, codificadas de forma fixa, de acordo com os valores passados aos controladores. Quando a aplicação de e-commerce envia os valores para os controladores, uma resposta específica é retornada conforme a entrada. No controlador de frete ocorre o mesmo: para facilitar o exemplo, conforme algumas informações recebidas, ele retorna os dados correspondentes da tabela de frete.
A seguir, os controladores com as tabelas estáticas em memória:
@RestController
public class ControladorFrete {
private static final Map<String, BigDecimal> TABELA_FRETE = Map.of(
"joao@alura.com", new BigDecimal("10.00"),
"maria@alura.com", new BigDecimal("20.00"),
"pedro@alura.com", new BigDecimal("30.00")
);
@RestController
public class ControladorPrecificacao {
private static final Map<String, BigDecimal> TABELA_AJUSTE = Map.of(
"joao@alura.com", new BigDecimal("0.90"),
"maria@alura.com", new BigDecimal("0.95"),
"pedro@alura.com", new BigDecimal("1.10")
);
E os endpoints que retornam os valores fixos conforme a entrada do cliente:
@GetMapping("/api/v1/ajuste")
public ResponseEntity<BigDecimal> obterAjuste(@RequestParam("cliente") String cliente) {
// ...
var ajuste = TABELA_AJUSTE.getOrDefault(cliente, new BigDecimal("1.00"));
return ResponseEntity.ok(ajuste);
}
@GetMapping("/api/v1/cotacao")
public ResponseEntity<BigDecimal> obterCotacao(@RequestParam("cliente") String cliente) {
// ...
var frete = TABELA_FRETE.getOrDefault(cliente, new BigDecimal("100.00"));
return ResponseEntity.ok(frete);
}
Vamos verificar se os testes estão executando.
Para finalizar, o serviço parceiro roda em dois contêineres Docker, cada um em uma porta: um executa o controlador de frete e o outro executa o controlador de precificação. No Docker Compose do e-commerce, observamos os seguintes serviços: PostgreSQL, ZooKeeper, Kafka, a interface gráfica do Kafka e Keycloak (para criação de tokens e autenticação). Nesse contexto:
No Docker Compose do e-commerce, os serviços do parceiro aparecem configurados assim:
servico-frete:
build:
context: ../servico-parceiro
dockerfile: Dockerfile
container_name: servico-frete
ports:
- "8082:8080"
servico-precificacao:
build:
context: ../servico-parceiro
dockerfile: Dockerfile
container_name: servico-precificacao
ports:
- "8081:8080"
Quanto ao arquivo de criação, o Dockerfile não está no e-commerce, e sim no serviço parceiro. Ele é simples: geramos o JAR; em seguida, copiamos o JAR para dentro do contêiner; expomos a porta 8080. Poderíamos ter exposto também as portas 8081 e 8082, mas optamos por rotear dentro do Docker Compose. Depois disso, executamos a aplicação.
Segue o Dockerfile utilizado no serviço parceiro:
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY target/servico-parceiro-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Vamos conferir se os testes estão passando. Executamos todos os testes e há rigor na sua criação. Todos os testes passaram.
Apresentaremos alguns frameworks de teste do Spring, que não foram mencionados no início desta aula, e que garantem que tanto o servidor (o serviço parceiro) quanto o cliente (no caso, o e-commerce) funcionem corretamente. Nós vamos ver como utilizá-los nas próximas aulas. Até a próxima.
Agora que concluímos a etapa de testes da aplicação e confirmamos que ela funciona corretamente — inclusive criamos pedidos e cadastramos produtos — vamos iniciar o nosso processo de migração.
Antes de mais nada, é fundamental entendermos qual é a versão do Spring Boot que estamos utilizando atualmente e, em seguida, avaliarmos qual foi a última versão da série 3.x.x antes de chegarmos à versão 4. Dessa forma, conseguimos migrar sem muitos problemas, especialmente os de compatibilidade de bibliotecas.
No nosso projeto de e-commerce (comércio eletrônico), a versão em uso é a 3.5.10. Para verificar isso, observe o parent configurado no pom.xml:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.10</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
Essa não é a última versão da linha 3.x.x antes do Spring Boot 4. A última versão 3.x.x antes do Spring Boot 4 é a 3.5.16. Portanto, antes de irmos para a versão 4.1.0 (apontada como estável do Spring Boot), vale a pena primeiro atualizarmos para a 3.5.16 para verificarmos se algo quebra ou se ocorre algum problema — por exemplo, falhas em testes.
Vamos, então, atualizar o e-commerce para a 3.5.16. No pom.xml, a alteração do parent fica assim:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.16</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
Solicitamos ao IntelliJ IDEA o recarregamento das dependências dessa versão e repetimos o procedimento nos serviços de frete e de precificação. Identificamos que o serviço parceiro já está na 3.5.16; ele não precisa ser alterado por enquanto. Já o testamos anteriormente e verificamos que, ao subir o e-commerce, ele estava no ar e entregava as informações necessárias sem problemas. Como está atualizado, seguimos apenas com o e-commerce.
Após definirmos a versão 3.5.16, executamos a compilação. Aparentemente, nada quebrou. Para confirmar, executamos mvn clean test. Mantivemos, desde a última aula, todos os contêineres em execução, então serão executados também os testes que utilizam Testcontainers. Como comentamos anteriormente, eles demoram mais para rodar, o que depende bastante do desempenho do seu equipamento (hardware). Em cenários nos quais precisamos de uma resposta mais rápida dos testes, é recomendável separar essa fase das demais. Já quando for necessário executar a suíte completa, vale a pena subir o Testcontainers.
Os testes foram executados com sucesso. Agora que temos segurança sobre o estado das aplicações na versão 3, vamos prosseguir com a atualização para a 4.1.0, tanto do e-commerce quanto dos serviços de precificação e de frete.
Como conduzir essa migração? Em geral, esse processo pode ser trabalhoso. As mensagens e orientações fornecidas pelo Spring Boot nem sempre são as melhores, então vale a pena basearmos a migração fortemente em testes. Nós temos uma suíte de testes cobrindo diversos cenários: testamos controladores do e-commerce e até o fluxo completo de um pedido; cobrimos a integração de produtores e consumidores do Kafka, utilizando tanto o Embedded Kafka quanto o Testcontainers; validamos repositórios utilizando Spring Data JPA com H2 Database e também com Testcontainers. Além disso, temos testes de circuit breaker (disjuntor de circuito) nas integrações. Assim, caso o serviço de precificação ou o serviço de frete apresente problemas, o circuito é aberto, e toda a lógica do circuit breaker se aplica: abre-se o circuito, tenta-se novas requisições após um intervalo e, se a comunicação voltar a funcionar, o circuito é fechado; caso contrário, permanece aberto.
Com essa cobertura de testes, temos uma boa garantia de que a aplicação continuará funcionando sem maiores problemas. Caso alterações no pom.xml e nos imports das classes causem algum impacto, veremos erros de compilação ou falhas apontadas pelos testes.
Dito isso, vamos ao pom.xml para começar as alterações. Antes, vale a pena anotar quais são os frameworks utilizados no e-commerce, pois muito provavelmente precisaremos deles novamente nas versões compatíveis com o Spring Boot 4.1.0. Confirmando: a última versão estável da linha 4 é a 4.1.0 (a chamada major version — versão principal).
Verifiquemos as bibliotecas declaradas no pom.xml: Java 25, Spring Cloud 2025.0.0, SpringDoc 2.8.9, Testcontainers 1.21.3, Logstash 8, além do serviço parceiro na versão 0.0.1. Essas versões ficam centralizadas nas propriedades:
<properties>
<java.version>25</java.version>
<spring-cloud.version>2025.0.0</spring-cloud.version>
<springdoc.version>2.8.9</springdoc.version>
<testcontainers.version>1.21.3</testcontainers.version>
<logstash.logback.encoder.version>8.0</logstash.logback.encoder.version>
<servico-parceiro.version>0.0.1-SNAPSHOT</servico-parceiro.version>
</properties>
Quanto às dependências, utilizamos o BOM de Spring Cloud (Spring Cloud Dependencies), Testcontainers, bem como os starters do Spring Boot. Os starters do Spring Boot funcionam como agrupadores de dependências necessárias às nossas aplicações. Ao adicionarmos um starter, não precisamos buscar manualmente várias bibliotecas: o starter já traz tudo o que é necessário para que aquele conjunto de funcionalidades opere corretamente. E, claro, contamos também com as demais dependências necessárias ao projeto. No pom.xml, o gerenciamento via BOM aparece assim:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>${testcontainers.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
No caso do Spring Data JPA, incluímos o starter (inicializador) de Spring Data JPA. Ele traz todas as dependências necessárias para trabalhar com JPA. No pom.xml, fica assim:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Temos também dependências do Flyway, tanto do seu core (núcleo) quanto do suporte a PostgreSQL. Há uma diferença: o Flyway Core serve para qualquer banco de dados, e há o componente específico para PostgreSQL, para que possamos executar migrações na nossa base PostgreSQL. No pom.xml, representamos assim:
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
Temos o Spring Boot Starter Web, para disponibilizar endpoints (pontos de extremidade) REST para nossos clientes:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Temos o Spring Boot Starter Validation; utilizamos Bean Validation para verificar se os objetos estão válidos:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Temos o Spring Boot Starter Security e o OAuth2 Resource Server. Ou seja, dispomos de uma camada de segurança e declaramos que utilizamos OAuth2 como camada de segurança:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
Temos também o módulo OAuth2 JOSE, uma implementação para trabalharmos com JWT:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
Incluímos ainda Spring Kafka, Spring Boot Starter Actuator, SpringDoc OpenAPI (que já vimos), ferramentas para geração de métricas para Prometheus e o padrão Circuit Breaker. Para Kafka e Actuator:
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Para o SpringDoc OpenAPI:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
Para métricas com Prometheus:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>
Para Circuit Breaker, utilizamos a implementação do Resilience4j:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
Além disso, configuramos Logstash, PostgreSQL e as dependências de teste.
Temos o Spring Boot Starter Test, JUnit, PostgreSQL, Testcontainers, Spring Kafka Test, H2 e Spring Security Test, além do Spring Cloud Contract. No pom.xml, as dependências de teste aparecem, por exemplo, assim:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>kafka</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
O Spring Cloud Contract é uma ferramenta muito interessante do Spring Boot. Para quem não conhece, vale a explicação: no nosso cenário de e-commerce (comércio eletrônico), temos os serviços de frete e de precificação, além do próprio e-commerce. Quando testamos — e espera-se que testemos — os controladores desses serviços, precisamos criar a integração entre eles (frete e precificação) e o e-commerce. Esses endpoints precisam ser testados.
Com o uso do Spring Cloud Contract tanto pelo e-commerce quanto pelos serviços, ocorre o seguinte: quando os testes dos serviços de frete e de precificação são executados, é gerado um stub (simulador de serviço) para o serviço de precificação e outro stub para o serviço de frete. Em um cenário com bases de código diferentes para cada serviço, teríamos dois stubs; no nosso caso, como a base de código é a mesma, é gerado um único stub.
Esse stub, gerado a partir da base de código dos serviços, contém dados de teste e os mesmos dados de entrada utilizados nos testes dos endpoints dos serviços, além de um mock (simulação) dos endpoints. Trata-se de uma biblioteca que simula exatamente as entradas e saídas dos serviços.
Esse stub pode ser importado como dependência no nosso e-commerce. Com algumas anotações e configurações — veremos esses testes nas próximas aulas — e com a importação do stub no arquivo pom.xml do e-commerce, conseguimos testar o e-commerce acionando os endpoints, sem a necessidade de os serviços estarem em execução. Detalhe: utilizamos os mesmos dados de entrada dos testes que foram feitos nos dois serviços. Isso é bastante vantajoso. No pom.xml de testes, uma dependência comum para esse cenário é:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
Se esses serviços alterarem seus contratos ou seus endpoints, um novo stub será gerado. Quando esse stub atualizado for importado pelo e-commerce, os testes falharão, pois o e-commerce ainda estará trabalhando com contratos antigos. Mesmo que ninguém avise o time do e-commerce sobre a alteração dos contratos dos serviços, a falha nos testes já nos permitirá corrigir e alinhar com o time dos serviços de frete e precificação.
Em resumo, o Spring Cloud Contract é baseado no conceito de testes de contrato, ou seja, em contract driven development (desenvolvimento orientado a contratos), em que testamos sobre contratos. A expressão é redundante, mas a ideia é exatamente essa: testamos com base em contratos.
No e-commerce, além do Spring Cloud Contract, incluímos o stub do serviço parceiro.
Por fim, utilizamos Awaitility para lidar com testes assíncronos. É uma ferramenta muito útil; veremos seu código em breve.
O build (processo de construção) é simples, o padrão que já vem com o Spring Boot, normalmente quando geramos o projeto no Spring Initializr (start.spring.io).
Temos, portanto, todas as dependências mapeadas.
Existem várias abordagens para iniciar esta migração. A abordagem que vamos utilizar não é a melhor nem a pior; é uma abordagem útil para o nosso caso, dada a quantidade de testes que temos cobrindo diversos cenários.
Reforçamos: toda vez que vamos fazer uma refatoração, é importante nos preocuparmos com os testes da aplicação. É possível realizar a refatoração de forma que, caso algo seja quebrado, os testes sinalizem? Se não houver segurança nessa resposta, escrevemos mais testes.
Robert C. Martin (Uncle Bob), em Clean Architecture (Arquitetura Limpa), não define exatamente até onde devemos ir com os testes, mas deixa claro: enquanto houver insegurança, escrevemos mais testes. Permanece a insegurança? Escrevemos mais testes. Quando a insegurança cessar, ótimo; ainda assim, o objetivo é que o QA (garantia da qualidade) não encontre problemas. Se houver risco de o QA encontrar algo, escrevemos mais testes. O QA não deve encontrar nada.
Eventualmente, um profissional de QA com forte perfil investigativo identificará algum problema, isto é, encontrará um bug.
Cabe a nós compreender o bug, analisar como ocorreu e adotar todas as medidas possíveis para que não volte a acontecer.
O curso Spring Boot 4: atualize suas APIs com o novo ecossistema Spring possui 187 minutos de vídeos, em um total de 32 atividades. Gostou? Conheça nossos outros cursos de Java em Back-end, ou leia nossos artigos de Back-end.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Todos os cursos, trilhas e certificados para evoluir.
2 anos de Alura
Matricule-se no plano PLUS 24 e garanta:
Jornada de estudos progressiva que te guia desde os fundamentos até a atuação prática. Você acompanha sua evolução, entende os próximos passos e se aprofunda nos conteúdos com quem é referência no mercado.
Back-end, Dados, Front-end, DevOps, Mobile, Gestão & Negócios, UX & Design, Cibersegurança, Cloud, Inteligência Artificial
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
Acesso à inteligência artificial da Alura.
Modo entrevista técnica e comportamental
Aprenda mais rápido com IA, idiomas e imersões.
2 anos de Alura
Todos os benefícios do PLUS 24 e mais vantagens exclusivas:
Acesso ao catálogo da Casa do Código e leitura dentro da plataforma
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Acesso ao maior portal de vagas tech do Brasil
Desafios de código
System design
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Aceleração de carreira: mentoria, vagas e prática guiada.
2 anos de Alura
Todos os benefícios do PRO 24 e mais vantagens exclusivas:
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais de forma ilimitada.
Lives CareerUp: eventos exclusivos com foco em empregabilidade e carreira
2 mentorias individuais com foco na sua carreira