Blogg

Här finns tekniska artiklar, presentationer och nyheter om arkitektur och systemutveckling. Håll dig uppdaterad, följ oss på Twitter

Callista medarbetare Magnus Larsson

Upgrade to Spring Boot 3.1

// Magnus Larsson

Spring Boot 3.1 was released in May 2023 during the wrap-up of writing the 3rd ed of my book, so there was no time to upgrade the code before releasing the book. Instead, in this blog post, I will describe how to upgrade the source code examples in the book from Spring Boot 3.0 to Spring Boot 3.1, use some of the new features like Testcontainers at development time, and also cover some new and resolved bugs.

Overview

The blog post is divided into the following sections:

To access the source code of this book that is updated to Spring Boot 3.1, run the following commands:

git clone https://github.com/PacktPublishing/Microservices-with-Spring-Boot-and-Spring-Cloud-Third-Edition.git
cd Microservices-with-Spring-Boot-and-Spring-Cloud-Third-Edition
git checkout SB3.1

You can find the changes applied to upgrade the source code to Spring Boot 3.1 by comparing two folders that contain a clone of the Git-repo, one with the main branch checked out and one with the SB3.1 branch checked out.

Let’s start by looking at the changes applied to the source code.

1. Upgrading to Spring Boot 3.1

To upgrade the source code of this book to Spring Boot 3.1, the following three steps have been performed:

  1. Upgrade dependencies in the ‘gradle.build’ files
  2. Update runtime properties for readiness probes to comply with Spring Boot 3.1 semantics
  3. Add some build properties for native compile to work properly

Each step is described in detail in the subsections below.

1.1. Upgrading dependencies

The following dependencies have been upgraded in the gradle.build files:

Dependency Before After
Spring Boot 3.0.4 3.1.4
Spring Cloud 2022.0.1 2022.0.4
Spring Dep Mgm plugin 1.1.0 1.1.3
springdoc-openapi 2.0.2 2.1.0
GraalVM Native plugin 0.9.18 0.9.27

Note: The GraalVM Native plugin only concerns Chapter 23, Native-Complied Java Microservices.

In the main branch for Spring Boot 3.0, these dependencies look as the following in the build.gradle files:

plugins {
    id 'org.springframework.boot' version '3.0.4'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'org.graalvm.buildtools.native' version '0.9.18'
}

ext {
    springCloudVersion = "2022.0.1"
}
dependencies {
    implementation 'org.springdoc:springdoc-openapi-starter-webflux-ui:2.0.2'

In the SB3.1 branch for Spring Boot 3.1, these dependencies look as the following in the build.gradle files:

plugins {
    id 'org.springframework.boot' version '3.1.4'
    id 'io.spring.dependency-management' version '1.1.3'
    id 'org.graalvm.buildtools.native' version '0.9.27'
}

ext {
    springCloudVersion = "2022.0.4"
}

dependencies {
    implementation 'org.springdoc:springdoc-openapi-starter-webflux-ui:2.1.0'

1.2. Updating runtime properties

Starting with Chapter 16, Deploying Our Microservices to Kubernetes, a Readiness Health Group is used to configure Kubernetes readiness probes. The configuration uses the property management.endpoint.health.group.readiness.include to specify what Health Indicators should be executed to determine the readiness state. In Spring Boot 3.0, unavailable Health Indicators were ignored. Starting with Spring Boot 3.1, unavailable Health Indicators will cause a microservice to fail during startup.

For Spring Boot 3.0, one Readiness Health Group is declared in the common configuration file, config-repo/application.yml, like this:

management.endpoint.health.group.readiness.include: readinessState, rabbit, db, mongo

For Spring Boot 3.1, each microservice needs to specify what Health Indicators to use per Spring Boot profile. For example, the Product microservice declares, in the file config-repo/product.yml, the following is specified in the default profile, where both RabbitMQ and MongoDB are used:

management.endpoint.health.group.readiness.include: readinessState, rabbit, mongo

While only MongoDB is specified in the kafka - profile since no out-of-the-box Health Indicators exists for Kafka:

---
spring.config.activate.on-profile: kafka

management.endpoint.health.group.readiness.include: readinessState, mongo

1.3. Adding build properties for native compile

Spring Boot 3.1 requires a few more build time properties to ensure that required Spring beans are reachable during the AOT processing at build time. See Chapter 23, Native-Complied Java Microservices for details.

The build properties have been added to each microservices build time property file located at src/main/resources/application.yml.

For the Product Composite microservice, properties have been added to make Resilience4J and springdoc-openapi work when native compiled. The properties are prefixed with resilience4j.circuitbreaker and api.common.

For the Product and Recommendation microservices, the following property has been added to be able to get correct error messages from Spring Rest tests:

	# Native Compile: Required to get a proper error message from Spring Rest tests
	server.error.include-message: always

With an understanding of how to upgrade the source code to Spring 3.1, let’s move on to look at new features in Spring Boot 3.1 and how to use them with the source code of this book!

2. Using new features

Spring Boot 3.1 has some interesting new features within the book’s scope. They are the following:

  1. Improved support for Testcontainers
  2. A new concept, SSL Bundles, for simplifying SSL and TLS configurations
  3. Auto-configuration of the Spring Authorization Server

For other news not directly related to the material in the book, see Spring Boot 3.1 Release Notes.

We will go through each feature in the following subsections.

2.1. Improved support for Testcontainers

Testcontainers make it simple to run automated integration tests with databases, queue managers, etc, that you plan to use in production. Before Testcontainers, we typically used some in-memory database instead of the one used in production. The in-memory database works similarly but not exactly the same as the production database, opening up for quality issues with the integration tests. Testcontainers provide an elegant and easy-to-use abstraction over Docker, making it easy to configure that a specific version of a database engine should be started as a Docker container before the integration tests start and also to be shut down once the tests are done.

One concern with configuring Testcontainers has been to figure out what Spring properties to map to the Testcontainer’s properties to establish connectivity from the Spring libraries to the underlying database, etc. For example, for a MongoDB database, we have to supply a configuration that looks like:

  @DynamicPropertySource
  static void setProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.data.mongodb.host", database::getContainerIpAddress);
    registry.add("spring.data.mongodb.port", () -> database.getMappedPort(27017));
    registry.add("spring.data.mongodb.database", () -> "test");
  }

Starting with Spring 3.1, a new concept called Service Connections is introduced. Service Connections will automate this configuration for us for the most commonly used resources, making use of Testcontainers even easier.

With Spring 3.1, we also get a simplified setup of the dependency on Testcontainers and a new option to use Testcontainers at development time.

For more information on these improvements, see https://spring.io/blog/2023/06/23/improved-testcontainers-support-in-spring-boot-3-1.

Let’s look through each enhancement one by one.

2.1.1. Simplified dependency setup

From SpringBoot 3.1 onwards, the Testcontainer version is already configured in the BOM, so we don’t need to specify either the Testcontainer’s BOM or its version.

Using Spring Boot 3.0, we specify the following in the build.gradle- file:

    implementation platform('org.testcontainers:testcontainers-bom:1.17.6')
    testImplementation 'org.testcontainers:testcontainers'

Starting with Spring Boot 3.1, this can be simplified to:

    testImplementation 'org.springframework.boot:spring-boot-testcontainers'

2.1.2. Service Connections simplifies configuration

With Spring boot 3.1 comes a new annotation, @ServiceConnection, that understands how to configure Spring properties for a specific type of test container. For a list of supported resources, see https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.testcontainers.service-connections.

In Spring 3.0, we must specify what Spring properties to set using a property setter method annotated with @DynamicPropertySource. For a MongoDB test container, it looks like this:

public abstract class MongoDbTestBase {
  private static MongoDBContainer database = new MongoDBContainer("mongo:6.0.4");
  
  static {
    database.start();
  } 
  
  @DynamicPropertySource
  static void setProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.data.mongodb.host", database::getContainerIpAddress);
    registry.add("spring.data.mongodb.port", () -> database.getMappedPort(27017));
    registry.add("spring.data.mongodb.database", () -> "test");
  }
}

In Spring 3.1 it can be simplified to this:

public abstract class MongoDbTestBase {

  @ServiceConnection
  private static MongoDBContainer database = new MongoDBContainer("mongo:6.0.4");

  static {
    database.start();
  }

}

For details, see the classes MongoDbTestBase and MySqlTestBase in the src/test/java folder of the Product, Review, and Recommendation microservices, starting from Chapter 6, Adding Persistence.

2.1.3. Testcontainers at development time

Starting with Spring Boot 3.1, we can now use Testcontainers during development time and not only when we run integration tests! This allows us to configure Testcontainers for the external dependencies a Spring Boot application requires, for example, the microservices in this book. Based on this configuration, we can create a Test Application that will start the external resources as test containers when the application is started. By default, the test container stops when the application is stopped. This can be inconvenient since you, for example, might have inserted test data in a database running as a test container. We can enable an experimental feature in Testcontainers for Reusable containers to prevent the test container from stopping. Then the container will run until we kill the container explicitly. This can be very convenient if we start and stop an application multiple times while we, for example, test new code and fix errors before establishing solid integration tests.

To use Testcontainers during development, we need to write a special Test Application class, a configuration of Testcontainers to be used by the test application, and we probably also want to keep the test containers up and running if we need to restart our application to correct some code.

For more information, see https://spring.io/blog/2023/06/23/improved-testcontainers-support-in-spring-boot-3-1.

Let’s first create a test application and then learn how to run it either in a terminal or in an IDE such as Visual Studio Code.

2.1.3.1. Creating a Test Application

A test application is created in the source folder for test classes, and its primary purpose is to point out the real Main Application and a configuration class that describes what Testcontainers it needs.

The test application for the Product microservice looks like this:

public class TestApplication {
  public static void main(String[] args) {
    SpringApplication.from(ProductServiceApplication::main)
      .with(TestApplicationConfiguration.class)
      .run(args);
  }
}

The test application’s configuration class defines a Testcontainer for MongoDB, required by the Product microservice. It looks like:

@TestConfiguration(proxyBeanMethods = false)
public class TestApplicationConfiguration {

  @Bean
  @ServiceConnection
  MongoDBContainer mongoDBContainerContainer() {
    return new MongoDBContainer("mongo:6.0.4").withReuse(true);
  }

}

Note that the MongoDBContainer is configured to support reuse by calling the method withReuse(true). This configures the test container not to stop the container once the application stops. The support for this feature is currently experimental and must therefore be enabled.

The reuse feature can be enabled by adding the line testcontainers.reuse.enable=true in the file ~/.testcontainers.properties. In my case, the property file looks like the following:

$ cat ~/.testcontainers.properties
#Modified by Testcontainers
#Sun Dec 05 15:33:25 CET 2021
docker.client.strategy=org.testcontainers.dockerclient.UnixSocketClientProviderStrategy
testcontainers.reuse.enable=true

Note: Once you are done with trying out the reuse - feature in Testcontainers, I suggest you disabled it again, since the support currently only is experimental.

For more information, see https://java.testcontainers.org/features/reuse/.

Let’s try out Testcontainers at development time using a terminal!

2.1.3.2. Trying out Testcontainers at development time using a terminal!

A test application can be started using the new Gradle task bootTestRun.

Test applications have been added to the microservices that use databases in Chapter 6, Adding Persistence. Head over to the source folder for Chapter 6 with the command cd Chapter06 and start each microservice in a separate terminal window.

Once they have started, you can run the end-to-end test script, test-em-all.bash, to verify that the microservice landscape works as expected. Since the microservices run without Docker, we need to specify the port where the Product Composite is listening when not running as a Docker container, port 7000. After the tests have been executed successfully, you can see the Docker containers running with a docker ps command.

To summarize, you need to run the following commands, each in a separate terminal:

./gradlew microservices:product-composite-service:bootTestRun
./gradlew microservices:product-service:bootTestRun
./gradlew microservices:recommendation-service:bootTestRun
./gradlew microservices:review-service:bootTestRun
HOST=localhost PORT=7000 ./test-em-all.bash
docker ps

Here is a screenshot from my tests:

SB31-terminal-test-em-all

After stopping all four microservices, you can still find the data in the MongoDB and MySQL databases.

For MongoDB, you can run a command like:

docker exec a1f96d2c41c1 mongosh product-db --quiet --eval "db.products.find({}, {_id:0, productId:1})"

The response should look like:

[ { productId: 113 }, { productId: 213 }, { productId: 1 } ]

For MySQL, you can run a command like:

docker exec 279e6fb64a0e mysql -uroot -ptest test -e "select product_id, review_id from reviews"

The response should look like:

product_id	review_id
1	        1
...
113	        3

Once you are done with the test containers, you can stop them with a regular docker rm command like:

docker rm -f a1f96d2c41c1 279e6fb64a0e

Finally, let’s learn how to run the test applications in an IDE.

2.1.3.3. Trying out Testcontainers at development time using an IDE

You can also run the test application within an IDE that supports launching Spring Boot applications such as Visual Studio Code with the Spring Boot Extension Pack extension installed.

After launching the test application in the Spring Boot Dashboard, you can run the end-to-end test script, test-em-all.bash, in a terminal window in Visual Studio Code. It can look like:

SB31-VSC-test-em-all

One additional benefit of using an IDE instead of a plain terminal is using its built-in debugger while running the test applications. For example, debugging the Review microservice can look like:

SB31-VSC-debug-review-svc

That’s all about the improved support for Testcontainers in Spring Boot 3.1. Next, let’s learn about SSL Bundles!

2.2. Introducing SSL Bundles

Setting up secure communication using SSL and TLS is complex by nature, and the fact that different libraries (and even major versions of the same library) require setting up the configuration in different ways, makes it even more complex and time-consuming.

To ease this burden, Spring Boot 3.1 introduces the concept of SSL bundles. SSL bundles provide a standardized way for configuring SSL and TLS trust artifacts, such as keystores, certificates, and private keys. An SSL bundle can be used by many communication libraries, such as REST clients using either a RestTemplate or a WebClient instance, a Web server, or a database client using, for example, MongoDB or Redis.

For more information, see https://spring.io/blog/2023/06/07/securing-spring-boot-applications-with-ssl

In the book, starting with Chapter 11, Securing Access to APIs, the Spring Cloud Gateway server uses an SSL configuration that looks like this:

server.ssl:
  key-store-type: PKCS12
  key-store: classpath:keystore/edge.p12
  key-store-password: password
  key-alias: localhost

Using Spring Boot 3.1, an SSL bundle for the Gateway server can be configured like this:

spring.ssl.bundle.jks.gateway:
  key:
    alias: localhost
  keystore:
    type: PKCS12
    location: classpath:keystore/edge.p12
    password: password

The Gateway server can specify what SSL bundle to use, i.e. the gateway-bundle declared above, by declaring:

server.ssl.bundle: gateway

The password is stored in plain text in Chapter 11, as shown above. In chapters 12 to 16, passwords are stored in an encrypted format using the Spring Cloud Configuration server’s encryption service. The encrypted version of the passwords looks like the following in the configuration of the SSL bundle:

    password: '{cipher}d59274473b196cebe288f849c442eba34aa497d14a13e10a23f824489e1c23ec'

Finally, in chapters 17 to 23, they are provided at runtime using environment variables to better fit in a Kubernetes environment. The Docker Compose files have been adapted to passwords provided as environment variables and looks like this:

      - SPRING_SSL_BUNDLE_JKS_GATEWAY_KEYSTORE_PASSWORD=${GATEWAY_TLS_PWD}

With SSL Bundles covered, let’s finally learn about the last new feature, support for auto-configuration of the Spring Authorization Server.

2.3. Auto-configuring the Spring Authorization Server

Spring Boot 3.1 adds support for auto-configuring the Spring Authorization Server.

Using Spring Boot 3.0, the dependency on the Spring Authorization Server is configured by the following:

   implementation 'org.springframework.boot:spring-boot-starter-web'
   implementation "org.springframework.security:spring-security-oauth2-authorization-server:1.0.0"

With Spring Boot 3.1 comes a starter dependency for the Spring Authorization Server that can be used to auto-configure the server like:

   implementation 'org.springframework.boot:spring-boot-starter-oauth2-authorization-server'

With this, all new features in Spring Boot 3.1 that are of interest to this book are covered. Next up is a walkthrough of some new and resolved bugs.

3. Handling new and resolved bugs

Unfortunately, all new major versions of complex software like Spring Boot come with new bugs. In this section, we will go through new, partly fixed and bugs that have been resolved.

3.1. A new bug in the Configuration server

Since v4.0.3 of the Spring Cloud Configuration server, a new bug prevents properties from being correctly loaded from the config server. The bug affects Spring Cloud 2022.0.4, used with Spring Boot 3.1 in this blog post, and is described in https://github.com/spring-cloud/spring-cloud-config/issues/2293. To workaround this bug, all Spring Cloud clients have been downgraded to v4.0.2 by replacing:

    implementation 'org.springframework.cloud:spring-cloud-starter-config'

with:

    implementation 'org.springframework.cloud:spring-cloud-starter-config'
    implementation 'org.springframework.cloud:spring-cloud-config-client:4.0.2'

in their build.gradle files. This affects the code examples in chapters 12 to 16.

3.2. Improved but still buggy support for native tests

In Spring Boot 3.0, native tests didn’t work out for the tests used in the book. For details, see Chapter 23, Native-Complied Java Microservices. With Spring Boot 3.1, native tests have improved and work for many tests, but not all.

Therefore, all test classes for the microservices in Chapter 23 have been revised, and tests that still fail when run as a native test have been annotated with @DisabledInNativeImage together with a comment describing the error that caused the test to fail.

3.3. Bug resolved in the Eureka server

The version of the Spring Cloud Eureka server used in the book, v4.0.0, contains a bug described in https://github.com/spring-cloud/spring-cloud-netflix/issues/4145. The Eureka server’s application class used in the book contains a workaround that looks like this:

  // Workaround for issue gh-4145 in Spring Cloud 2022.0.3
  // See https://github.com/spring-cloud/spring-cloud-netflix/issues/4145
  @RestController
  static class CustomErrorController implements ErrorController {

    private static final String ERROR_MAPPING = "/error";

    @RequestMapping(ERROR_MAPPING)
    public ResponseEntity<Void> error() {
      return ResponseEntity.notFound().build();
    }
  }

Since this bug is resolved in the version of the Eureka server used in Spring Cloud 2022.0.4, this workaround has been removed.

This completes the walkthrough of new and resolved bugs; let’s wrap this blog post with a short recap on how to run the end-to-end tests to verify that everything works as expected with Spring Boot 3.1.

4. Running end-to-end tests

To try out the source code in this book with Spring Boot 3.1, first ensure that Docker, Java 17, git, jq, curl are installed. For installation instructions, see Chapters 21 and 22. Then perform the following steps:

git clone https://github.com/PacktPublishing/Microservices-with-Spring-Boot-and-Spring-Cloud-Third-Edition.git
cd Microservices-with-Spring-Boot-and-Spring-Cloud-Third-Edition
git checkout SB3.1
cd ChapterNN
./gradlew build
docker-compose build
docker-compose up -d
./test-em-all.bash

Note: Replace ChapterNN with Chapter04 - Chapter23 depending on what chapter you want to test.

Look for a final log message that says:

End, all tests OK...

Tear down the microservice landscape with the command:

docker-compose down

For testing the source code changes with Kubernetes, Istio, and the EFK Stack, see instructions in chapters 17 - 20 in the book for setting up the Kubernetes cluster. For building native images with the source code changes, see instructions in Chapter 23, Native-Complied Java Microservices.

5. Summary

In this blog post, we have seen how to upgrade the source code in this book to Spring Boot 3.1 and use the end-to-end test script, test-em-all.bash to verify that everything works as expected. We have also learned about new and resolved bugs and, most importantly, how to use new features in Spring Boot 3.1, such as the support for Testcontainers at development time and SSL Bundles.

Tack för att du läser Callistas blogg.
Hjälp oss att nå ut med information genom att dela nyheter och artiklar i ditt nätverk.

Kommentarer