Do you want your ad here?

Contact us to get your ad seen by thousands of users every day!

[email protected]

Spring Boot: Local Development Enhancements, Let’s Compose!

  • August 25, 2023
  • 5249 Unique Views
  • 4 min read
Table of Contents
Docker compose supportTestcontainers at development timeTestcontainers desktop appWrap upReferences

Quite often when we are developing an application we need external services such as rabbitMQ, Kafka, etc.

When you are developing locally, you are quite likely using a docker-compose file to start these up, and I am certainly (hopefully) not the only one that has forgotten at least once to start these instances up.

And maybe you are even already using Testcontainers for your testing.

Luckily, Spring Boot 3.1 introduced some nice improvements to make our lives a bit easier.

  1. Docker compose support which allows us to make use of our compose.yml file to start these up and create the service connections for supported containers
  2. Testcontainers at development time

Both of these functionalities are built atop the ConnectionDetails abstraction, so if you are unfamiliar with this. I recommend checking out this article.

Note: the docker compose CLI application needs to be on your path for these to work properly.

Feel free to clone the demo repository, to run the samples!

Docker compose support

This method allows us to leverage our existing docker-compose.yml files, with some extra quality of live functionality.

We just need to add a dependency on spring-boot-docker-compose

Maven:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-docker-compose</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

Gradle:

dependencies {
    developmentOnly("org.springframework.boot:spring-boot-docker-compose")
}

note: the docker-compose support is limited at the moment (such as no Kafka) when we’re using spring-boot-testcontainers we can use any container with the programmatic API.

How it works

When we then start our application Spring boot will:

  • look for common filenames (compose.yml | compose.yaml | docker-compose.yml | docker-compose.yaml)
  • start the defined containers/services using docker compose up
  • create the service connection beans for supported containers

And when the application stops, the defined containers/services are shut down using docker compose down

Configuration

There are a slew of configuration options, but some useful ones to know:

  • specifying a specific compose file: spring.docker.compose.file
  • managing the docker-compose lifecycle can be done using spring.docker.compose.lifecycle-management to configure it as:
    • none: do not start nor stop
    • start-only
    • start-and-stop
  • making use of spring profile-specific docker compose files (docker–compose-{profile}.yaml) can be done using: spring.docker.compose.profiles.active

Testcontainers at development time

The setup

We just need to add a dependency on spring-boot-testcontainers

Maven:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-testcontainers</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

Gradle:

dependencies {
    testImplementation("org.springframework.boot:spring-boot-testcontainers")
}

The application itself is a very simple one that allows us to get a die result which is then stored in our Redis instance, and to retrieve all these rolls

Now rather than having to install a Redis instance locally, or using a docker.yaml file, we’re making use of the new Testcontainers functionality.

As you can see in DemoConfiguration we are making use of the new ServiceConnection to define our Redis instance making use of a Testcontainer.

@Bean
@ServiceConnection(name = "redis")
GenericContainer<?> redisContainer() {
    return new GenericContainer<>(DockerImageName.parse("redis:latest")).withExposedPorts(6379);
}

Now in TestTestcontainersDemoApplication you’ll see that we are making use of the new SpringApplication.from method to delegate to our actual application, and we are passing in our Test configuration.

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

This way we can run our application for development purposes.
Alternatively, we can make use of: ./gradlew bootTestRun or ./mvnw spring-boot:test-run.

After this, we can see that our application has started up including our Testcontainers.

What if my desired container does not have a ServiceConnection yet?

Using @ServiceConnection is recommended, but not all technologies support this method yet.

If this is the case, then you can inject your @Bean definition of the container with DynamicPropetyRegistry to contribute the dynamic properties at development time.

This works akin to the @DnamicPropertySource annotation from tests and allows us to add properties that become available once the container has started.

For example, let’s say we want to send out e-mails from our application and we want to use make use of MailHog which does not have a service connection factory provided yet in spring-boot-testcontainers we can do:

@Bean
public GenericContainer mailhogContainer(DynamicPropertyRegistry registry) {
   GenericContainer container = new GenericContainer("mailhog/mailhog")
                                        .withExposedPorts(1025);
   registry.add("spring.mail.host", container::getHost);
   registry.add("spring.mail.port", container::getFirstMappedPort);
   return container;
}

To provide the required information at development time.

Keeping our data

You will notice that when your application stops, the containers are also stopped.
This does mean that you’ll also lose your data.

There are two options to work around this in case you want to keep your data.

Reusable testcontainers (experimental)

The first option, Reusable Testcontainers is an experimental feature that can be used by adding .withReuse(true).
These containers are not stopped when your application stops!

@Bean
@ServiceConnection(name = "redis")
GenericContainer<?> redisContainer() {
    return new GenericContainer<>(DockerImageName.parse("redis:latest"))
            .withExposedPorts(6379)
            .withReuse(true);
}
Given the experimental state there are still some limitations which you will have to keep in mind which are document in the

announcement post

.

Spring Boot devtools with @RestartScope

The second option requires you to annotate the desired containers with @RestartScope, and to have devtools set up.
After which they’re no longer restarted when devtools restarts your application.

For devtools we’ll need to add this to our pom.xml file:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

or our Gradle build file:

dependencies {
    developmentOnly("org.springframework.boot:spring-boot-devtools")
}

and then we just need to annotate our container(s)

@Bean
@ServiceConnection(name = "redis")
@RestartScope
GenericContainer<?> redisContainer() {
    return new GenericContainer<>(DockerImageName.parse("redis:latest"))
            .withExposedPorts(6379);
}

Testcontainers desktop app

This software is not needed, but it’s still a nice extra utility to get even more mileage out of your testcontainer usage.

It was recently (donated to the community) as a free testcontainers desktop application, and can be downloaded from https://testcontainers.com/desktop/ and is available for Windows, Mac & Linux.

There are some quite useful features in there such as:

  • proxying a service to a fixed port to facilitate debugging
  • tracking of used images & test parallelization
  • functionality to switch local runtime for (cloud based) testcontainers
  • tweak Testcontainer behaviour such as freezing containers on shutdown/enable reusable testcontainers
  • …​

Wrap up

I hope this brief showcase was helpful and offered some new insights as to how to ease local development.

In case of any questions, feel free to reach out.

The people at the testcontainers slack are also very kind, and always willing to help out.

References

A Simple Service with Spring Boot

I will demonstrate how to create a simple Web Service using Spring Boot.  This framework makes it almost effortless to develop web services, so long as the appropriate dependencies are in place.

In this example, I will create a Web Service that will read the current temperature from a file and make it available to clients via a RESTful endpoint.

Book Review: “Quarkus for Spring Developers”

Quarkus for Spring Developers is a straight-forward guide to enable senior developers to quickly shift their Spring skills to leverage the “supersonic subatomic” Quarkus framework, and junior/mid-level developers to learn two frameworks at once.

The book gets straight to the point of Quarkus’ speed before the first chapter, with the foreword providing a real world testimonial of Quarkus software that many Java developers already use.

Build a Status Dashboard Using Spring Boot and Astra DB

Learn how to leverage the Astra Document API on top of Cassandra to build a dashboard of statuses, with Spring Boot for rendering. 

Lego character that removes block on keyboard
Build and Test Non-Blocking Web Applications with Spring WebFlux, Kotlin and Coroutines

In this article, we will develop a simple RESTful API using Spring WebFlux and aim to leverage the special Kotlin extensions in Spring.

Build Rot: The Hidden Technical Debt in Maven and Gradle Builds

Explore the impact of Build Rot on build speed and test times, offering strategies for enhanced observability and Developer Productivity Engineering to optimize build processes.

Do you want your ad here?

Contact us to get your ad seen by thousands of users every day!

[email protected]

Comments (0)

Highlight your code snippets using [code lang="language name"] shortcode. Just insert your code between opening and closing tag: [code lang="java"] code [/code]. Or specify another language.

No comments yet. Be the first.

Subscribe to foojay updates:

https://foojay.io/feed/
Copied to the clipboard