Spring Bootcamp – GETting Started

I was recently listening to the Arrested DevOps podcast, in the episode on Making DevOps Beginner Friendly guest Laura Santamaria talked about the importance of creating learning paths. A learning path, as the name suggests, is a series of articles or guides that walk someone through how to use a technology or practice. Learning paths differ from normal blog articles, like I have often done, which cover how to accomplish a very specific goal in isolation.

In the decade I have been working with the Spring Framework in general and the 5 years I have specifically worked with Spring Boot I have learned a lot, what to do, what not to do, and in some cases the why behind some of those answers. With many people working from home in response to the COVID-19 outbreak, seems an opportune time to go back to the basics.

In this series we will do a slow burn through Spring Boot, each article will be structured around the steps to do a complete a common task, but will take the time to explain what exactly the code is doing, what is happening in the background, as well as some of the why/best practices behind the tasks. The goal isn’t necessarily to break new ground in what Spring Boot can do, but to try to get a more well-rounded understanding of Spring Boot.

In this first article of the we will initializing a new Spring Boot project and create a couple of simple HTTP GET endpoints. So with that…

HOMAGE on Twitter:

Initializing a Spring Boot Project

Screen Shot 2020-03-27 at 9.05.06 AM

When starting a new Spring Boot project, one of the best places to go is start.spring.io. start.spring.io provides an interface for defining a project’s metadata as well as the ability to easily bring in many commonly used dependencies that should all be compatible to work with one another. Below demonstrates how to quickly initialize a project:

Note: If you are following along with this article you should bring in the spring-web and spring-boot-devtools dependencies.

Building Web APIs with Spring Boot

For many Java developers, a big part of their day is spent building and maintaining applications that service a Web API. Spring Boot makes building and maintaining really easy, which is a big reason why it has become so popular in the Java world.

After importing a new Spring Boot application into your preferred IDE, we can have an accessible endpoint with just these few lines of code:

Once added, starting the Spring Boot application should result in “Hello World!” being printed when you go to: http://localhost:8080/api/v1/hello.

Let’s look at the key elements from the above:

@RestController: This annotation marks to Spring that this class is a web controller, a class that serves as the interface to the Web for interacting with the internal application.

@RequestMapping("/api/v1/hello"): This annotation allows a developer to define the base path for the entire controller. All endpoints defined in this controller will be pre-fixed with /api/v1/hello.

@GetMapping: This annotation defines that the method helloWorld can be accessed as a HTTP GET.

With “Hello World” working, the second task when working with a new language or framework is to take in some user input to create a message. With a GET endpoint there are three ways of accepting input from a client; via the URL path, as query parameters, and as a request headers. Let’s look at how to reference values from each below.

Retrieve Values from the URL Path

To retrieve values from the URL path, in the @GetMapping you will need to define a variable in enclosing braces like above with {message}. In the arguments of the method an argument must be annotated with @PathVariable, if the name of the argument is the same as the variable in the definition of @GetMapping then Spring will automatically map it. @PathVariable has three fields:

name: Allows for manually mapping a url path variable a method argument.

required: Boolean for if the path value is required. Defaults to true.

value: alias for name.

Retrieve Values from the URL Query

Values can easily be retrieved from the query portion of an URL, the section of the URL after the “?” e.g.: ?firstName=Billy&lastName=Korando. Spring by default will attempt map query variables to the names of arguments in the method. So in the example URL query firstName and lastName will automatically map to the arguments firstName and lastName. @RequestHeader has four fields:

name: Allows for manually mapping a query value to a method argument.

required: Boolean for if the path value is required. Defaults to true. A HTTP 400 is thrown if a required value is not provided.

defaultValue: A default value for when the parameter is not provided. Will set required to false.

value: alias for name.

Retrieve Values from the Request Header

Retrieving values from a request header works very similarly to retrieving them from the URL query. Like with @RequestParam, @RequestHeader will automatically map the method argument name to the name of a header value. @RequestHeader has four fields:

name: Allows for manually mapping a header value to a method argument.

required: Boolean for if the path value is required. Defaults to true. A HTTP 400 is thrown if a required value is not provided.

defaultValue: A default value for when the parameter is not provided. Will set required to false.

value: alias for name.

String.format() or String Concatenate

Commonly when building a String in Java many Java developers build a String using concatenation like this:

"A message with a variable: " + var1 + " and another variable: " + var2 + " and more...";

Constructing a String this way can become difficult to read, and also be a formatting nightmare as the code is constantly changed because of slightly different formatting rules. When building a String it can be useful to consider using String.format() instead as demonstrated above. Readability can be a bit easier and there a number of pre-defined ways for printing things like dates available. For more information on how to use String.format() check out the official Javadoc: 8, 11, 14

Convention over Configuration

I am a longtime Spring user, my first experience with Spring was in 2010, using then Spring 2.5. While Spring was a significant improvement over frameworks I had used prior, initializing a new Spring project was still a difficult and and time consuming process process. Getting a static endpoint running as we have done in this article could take hours, even days, if starting truly from scratch.

We were able to accomplish in minutes with Spring Boot, what took hors before because Spring Boot uses a pattern called convention over configuration. In short Spring Boot has a number of default opinions, such as using an embedded Apache Tomcat server running on port ​8080. Many of of these opinions however can easily be change. If we needed to run our Spring Boot application on a different port, we can just set server.port. Using a different application server can be as easy as making a couple small changes to our build file.

Convention over configuration allows developers to focus on key business concerns, because in many cases using embedded Apache Tomcat and running on port 8080 is enough, especially in an increasingly containerized world. Understanding Spring Boot’s default opinions and how to change them will be a key element through out this series because there are definitely right and wrong ways of changing them.

Restart Revolution

As an application is being built there is often a need to rapidly iterate. This means rebuilding and restarting an application frequently. While the steps to rebuild and restart an application aren’t difficult, performing them can disrupt your “flow”. To address this the Spring team developed Spring Boot devtools. Spring Boot devtools provide two key features; automated start and, with browser extensions, live reload. Here is Spring Boot devtools in action:

To use Spring Boot devtools in your project, you will need to add it as a dependency in your build file like this:

Be sure to check out the user guides for more information on how to use Spring Boot Devtools including how to include exclude additional files, use it within a production system, using it on a remote system, and more.

Proper REST and API Best Practices

Like the code in a project itself, the usability and longterm maintainability of an API depends significantly on how well it is designed. Let’s review a few ways to improve the design of an API.

Version Your API

Probably the first, and also on of the easiest ways, to improve the design an API is to include in the URL the API version. In the examples about this was done with v1. Versioning an API allows it to more easily evolve over time as business and client need changes. When breaking changes are introduced, they can be included a new version of the API e.g. v2 and this much easier for clients to migrate to than forcing a hard and complicated switch if the same API endpoints are used.

Follow REST When Practical

Representational State Transfer, or REST, has become a popular architecture to follow when designing Web based APIs. REST was built upon the HTTP protocol, and while there are legitimate critiques that it might not always work well in every business case, there are a few good elements to follow such as; using the appropriate HTTP verb for the behavior of a endpoint e.g., for retrieving data a GET should be used, creating new resources should be a POSTDELETE for when an resource should be deleted.

Additionally proper usage of HTTP codes can be helpful as well; a HTTP 200 should be returned only when a request is successful. 400 should be returned, along with an appropriate message, when the client sends invalid or bad data. A 404 is also appropriate to return when a client requests a non-existent resource.

Fully following all of REST might not be possible or practical in all use cases, but following some of the key elements above can greatly improve the usability and maintainability of a API.

Conclusion

Spring Boot has been a revelation for the Spring developer community. Spring Boot has allowed developers to quickly build new applications while focusing on designing business valuable features for their organizations. As touched on in this article, there is also a lot of subtly to using Spring Boot.  Spring Boot is easy to get started with, but can take a lot to “master”, as even after five years I am still learning new things all the time. In this series we will continue to explore how to use Spring Boot to its full potential.

The code examples used in this article can be found in my GitHub.

Building a Custom Spring Boot Starter

Spring Boot has become incredibly popular since it was first released in 2013. Starting from scratch; a developer can have an application communicating with a database through RESTful endpoints that have security configured for them all within the span of an hour. This was a huge improvement over the often days it would take to accomplish similar tasks using just the core Spring Framework.

Spring Boot itself has some core functionality, but much of what Spring Boot application will be doing; communicating with a database, providing REST endpoints, and processing security on incoming requests, are supplied through starters. The Spring team, and third party vendors that integrate with Spring, have created many publicly available starters which can be seen on start.spring.io.

These starters are great, but organizations have domain specific concerns. Organizations typically only use certain database vendors, have specific requirements when implementing security, among many other needs. Here it can be beneficial to create custom Spring Boot Starters that incorporate these constraints and requirements. In this article we will walk through the process of creating a custom Spring Boot starter.

Not Just Another Shared Library

Organizations creating shared libraries to handle cross-cutting concern like data access or security isn’t new. What is new is the Spring team has provided several extension points within Spring Boot that can be used to improve the developer experience, these shared libraries are called “starters”. Improving developer experience can increase the active use of a library which can help in making sure standards are followed across an organization.

Starter Components

There are several extensions points the Spring team has provided for building starters they are:

  • Auto-Configuration
  • Conditionals
  • Metadata for Configuration

We will step through how to use each of these features when building a starter so that it; requires minimal configuration to be used, is flexible for a variety of scenarios, and provides developers with information they need to configure it.

We will use security, my-org-starter-security, as an example for building the custom starter. Security is a common concern for organizations, it is also something that require some amount of configuration, and also might need to behave differently depending on context. This will give us a good opportunity to flex out all the above features in a semi-realistic scenario.

You can find the code example used in this article on my GitHub.

Auto-Configuration

Spring Boot is often described as being “opinionated”, one of the ways this is done is through auto-configuration. Spring components can be automatically (automagically?) loaded into context without requiring developer intervention. This is great for making your starter easier to consume and also used correctly (i.e. ensure required classes/beans are loaded). To have a component be auto-loaded requires following a few steps:

1. Add spring-boot-autoconfigure as a dependency

For auto-configuration to work, and to have the appropriate classes available, the spring-boot-autoconfigure dependency must be on classpath. The dependency can be added directly, or indirectly by including another starter as a dependency. In my example I am using spring-boot-starter as a dependency.

2. Create a Configuration class

Create a normal configuration class like you would within a Spring Boot application and have it perform whatever required behavior you need. In this example we will have the @Configuration class load a UserDetailsService and PasswordEncoder into the application context.

Full class here.

3. Add spring.factories

Next we need to tell Spring Boot that the class ​SecurityConfig is a candidate for auto-configuration. To do this we need to create a file named spring.factories which needs to be located under the META-INF (typically this is located src/main/resources, like this). Within spring.factories we will add the following:

Now when a developer brings in the my-org-starter-security, the class SecurityConfig will be automatically loaded into the application context.

Conditionals

Auto-configuration is great, but there might be components you want to load only in a certain scenarios. In our hypothetical organization we will have both web based and console based applications. How security works in these scenarios would differ dramatically, however using conditionals we can have only the appropriate classes loaded into the application context depending on the scenario, this saves developers who are using our starter from a lot of headaches.

Spring Boot provides a lot of flexibility when it comes to defining conditionals, however in this example we will use a couple of pre-defined ones @ConditionalOnWebApplication and @ConditionalOnNotWebApplication. Let’s create a couple classes that will only be auto-configured conditionally.

1. Create configuration classes

Like above we will create a couple of standard @Configuration classes, however we will also need to annotate those classes with the appropriate annotations, @ConditionalOnNotWebApplication for NonWebSecurityConfig and @ConditionalOnWebApplication for WebSecurityConfig. The classes look like this:

Note: Conditional annotations can also be placed at the method level if needed.

2. Update spring.factories

The spring.factories file will need to be updated with these new classes to mark them as auto-configuration candidates.

3. Curate Your POM

I will cover this in-depth in a separate article, but a key element in building a good starter, particularly when using @Conditional, is setting up the POM for your starter correctly. Commonly Conditionals will be looking for the presence (or non-presence) of classes on classpath. In my starter POM I made use of optionals, which made consuming my starter easier for console applications:

Metadata for Configuration

Inevitably some amount of configuration is needed. Remembering the exact name for a property or the correct values to supply it with can be difficult. To address this the Spring team provides a mechanism for defining metadata about configuration properties which can provide developers with which properties are available, information about those properties, and hints on valid values.

There are several options for supplying configuration metadata.

1. Add the spring-boot-configuration-processor:

Before you can do anything with metadata you will need to add the spring-boot-configuration-processor to your pom file for the metadata to be generated:

2. Add META-INF/spring-configuration-metadata.json

Under the META-INF folder where we earlier added spring.factories you will need to create another file called spring-configuration-metadata.json which, as its name suggests, Spring will read to generate configuration meta data.

With the initial setup work done, there are several ways to provide configuration metadata. Below at two popular ways of doing this.

Defining metadata in spring-configuration-metadata

Metadata can be defined directly within the spring-configuration-metadata file. Here I am providing metadata on how to configure console security:

With this added, going into application.properties hints are provided saying that my-org.cli.security.required-role is a property, providing information on that property, and then with the hints field, valid values and their meaning can also be provided. This metadata makes configuring security for console applications much easier for developers.

Binding Properties to a Class

Within a @Configuration class I could just use a @Value to retrieve the value of my-org.cli.security.required-role. However I can also bind that property to a class as well, this is particularly helpful when dealing with several properties. To do this simply create a POJO and add a @ConfigurationProperties annotation to it. You will also need to give it a prefix that is the same as the groups value you defined in spring-configuration-metadata, so in this case my-org.cli.security.

Supply Properties to a Configuration Class

If we want to retrieve the values out of CommandLineSecurityConfigurer we will need to annotate a @Configuration class with @EnableConfigurationProperties({ CommandLineSecurityConfigurer.class }), like I did in NonWebSecurityConfig.

Fields and Javadoc as Metadata

Class fields and Javadoc can also be used to provide metadata for properties. I created a second properties class, WebSecurityConfigurer. In this class I have three properties, userEndpoints, adminEndpoints, unsecuredEndpoints, each with Javadoc attached. Because I use the same group name for the prefix in @ConfigurationProperties as I supplied above in spring-configuration-metadata, Spring will process the fields and Javadoc and generate property metadata from it. As can be seen in the screen shot below the code. Like above, the values supplied in applications.properties will be bound to the fields.

Screen Shot 2019-12-30 at 11.31.52 AM

Providing Sensible Configuration Defaults

Another way Spring Boot is described as being “opinionated” is through providing “sensible defaults”. A common way this is experienced is through the default port number Spring Boot uses of 8080. Providing default property values is super easy, as I have done above by initializing unsecuredEndpoints with "/public**". ​

Conclusion

Starters offer a lot of opportunities for organizations to improve the experience of their developers. This article only scrapes the surface of what is possible. Be sure to check out the provided links for more details on how to create starters as well as the official documentation:

Auto-configuration: https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-developing-auto-configuration

Providing metadata: https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-configuration-metadata.html

You can find the code example used in this article on my GitHub.

JPA or SQL in a Spring Boot Application? Why Not Both?

Image result for why not both?

JPA, Java Persistence API, was designed with the goal of making database interaction easier for Java developers. When used with a library like Spring Data JPA, getting basic database communication setup can be accomplished in mere minutes.

Spring Data JPA works great when performing relatively simple queries against one or two tables, but can become unwieldy once you start moving beyond this basic level of complexity. In real-world applications querying against three, four, or more tables at a time, along with performing other complex behavior within a query, is often necessary. Databases are, unsurprisingly, optimized around querying, filtering, and parsing large amounts of data, to say nothing of the transport time between the database and an application. So even if the logic of a complex query could be broken down and implemented with JPA, performance requirements might necessitate keeping that behavior as a single query.

Spring Data JPA makes handling a lot of mundane database activity a lot easier, but runs into some pain with more complex behavior. SQL can handle really complex behavior, but requires writing a decent amount of boilerplate code for even simple selects and inserts. Surely there must be a way to leverage the strengths of both JPA and SQL behind a consistent interface?

Custom Interfaces, Consistent Behavior

Spring Data JPA offers the ability to create custom repository interfaces and implementations and combine them with the default Repository interfaces Spring Data JPA offers, creating a composite repository. This allows applications to interact with the database through a single consistent interface. Let’s step through creating our own composite repository and then reference it in our application.

The first step is to define a new interface and method signature within. Here I am creating the ‘CustomBlogRepo’ interface:

public interface CustomBlogRepo {
    Iterable findBlogsByContent(String content);
}

Next we need to implement the interface. In this example I am using JdbcTemplate to handle querying, but there isn’t any constraints around how you implement database behavior in a custom repository class.

public class CustomBlogRepoImpl implements CustomBlogRepo {

    private JdbcTemplate jdbcTemplate;

    protected CustomBlogRepoImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public Iterable findBlogsByContent(String content) {
        return jdbcTemplate.query("SELECT * FROM Blogs WHERE body LIKE ?", new Object[] { "%" + content + "%" },
                new BlogRowMapper());
    }

    class BlogRowMapper implements RowMapper {
        @Override
        public Blog mapRow(ResultSet rs, int rowNum) throws SQLException {
            return new Blog(rs.getLong("id"), rs.getString("title"), rs.getString("body"));
        }
    }
}

Note: The above isn’t a good use case of when to use SQL, Spring Data JPA is easily capable of implementing a LIKE.

A couple of other important points, by default Spring Data JPA will scan for classes that match the name of your custom interface CustomBlogRepo with the post fix of “Impl”, thus why the implementation class is named CustomBlogRepoImpl. This behavior can be customized if needed. Additionally the implementation class must also be located in package where Spring Data JPA is scanning for repositories. This too is configurable, as can be seen in the previous link.

Finally we will need to extend the appropriate Repository interface with the customer interface we created. Here is an example of what this would look like:

public interface BlogRepo extends CrudRepository, CustomBlogRepo {

}

With this, we can interact both with the standard Spring Data JPA methods and the more complex database queries implemented in CustomBlogRepoImpl all through the single repository BlogRepo. We can see this in action in BlogController:

@RestController
@RequestMapping("/api/v1/blogs")
public class BlogController {

    private BlogRepo repo;

    protected BlogController(BlogRepo repo) {
        this.repo = repo;
    }

    @GetMapping
    public ResponseEntity<Iterable> getAllBlogs() {
        return ResponseEntity.ok(repo.findAll());
    }

    @GetMapping("/search-by/content/{content}")
    public ResponseEntity<Iterable> findBlogsByContent(@PathVariable String content) {
        return ResponseEntity.ok(repo.findBlogsByContent(content));
    }
}

Conclusion

jpasqlhysteria

Being able to reference either JPA or more complex SQL queries from a single consistent Repository interface makes it a lot easier and more consistent to interact with JPA and SQL within your Spring Data JPA projects. With it you can largely side step questions or rather you should use JPA or SQL in your next project, the answer now can simply be, why not both?

The code used in this article can be found on my Github.

Wiring Multiple Datasources in a Spring Boot and Spring Data JPA Application

FifthElement

Having to wire a Spring Boot application to talk to multiple datasources is a requirement you come along every once awhile. The good news is this can be done reasonably easily within Spring Boot and in this article we will walk through how to accomplish this.

Wiring Multiple Datasources Step-by-Step

Pre-Requisites

To run the demo application the following tools are needed:

  • Docker
  • Java

There are also some shell scripts available for building, running, testing, and tearing down the application for *nix OS users.

Application Structure

The example application used in this article has two domain models, Doctor and Clinic, each of which are persisted to their own separate datastores. This application overall is a very standard implementation of Spring Boot and Spring Data. Because there is already a lot of great documentation on how to implement such an application, those steps will be skipped. However for clarity, here is what the overall structure of the application looks like:

com.developer.ibm.multidatasource\
   clinic\
      Clinic
      ClinicsController
      ClinicsDatasourceConfiguration
      ClinicsRepo
   doctor\
      Doctor
      DoctorsController
      DoctorsDatasourceConfiguration
      DoctorsRepo
   MultiDatasourceApplication

The full application can be seen here: https://github.com/wkorando/multi-datasources-spring-boot

Configuring Spring Data

The first step would be to define the @Configuration classes that add the DataSource, PlatformTransactionManager, and LocalContainerEntityManagerFactoryBean to the application context which will be used by Spring Data when communicating with the databases. Both of the configuration classes look essentially identical, with one exception which will be covered in detail below. Let’s step through some of the key elements in these configuration classes:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "clinicEntityManagerFactory", transactionManagerRef = "clinicTransactionManager")
public class ClinicsDatasourceConfiguration {

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "clinics.datasource")
    public DataSource clinicsDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    PlatformTransactionManager clinicTransactionManager(
            @Qualifier("clinicEntityManagerFactory") LocalContainerEntityManagerFactoryBean clinicEntityManagerFactory) {
        return new JpaTransactionManager(clinicEntityManagerFactory.getObject());
    }

    @Bean
    LocalContainerEntityManagerFactoryBean clinicEntityManagerFactory(
            @Qualifier("clinicsDataSource") DataSource clinicsDatasource, EntityManagerFactoryBuilder builder) {
        return builder.dataSource(clinicsDatasource).packages(Clinic.class).build();
    }
}

@Primary

Outside of name differences, the @Primary added to clinicsDataSource is the only functional difference between ClinicsDatasourceConfiguration and DoctorsDatasourceConfiguration. Adding @Primary to clinicsDataSource is necessary as some of the autoconfiguring behavior within Spring Data depends upon a DataSource being available in the application context. However in our situation there will be two DataSources, available in the application context so adding @Primary to one gives Spring the information it needs on which bean to choose. For this applications purposes, making clinicsDataSource the primary DataSource was an arbitrary decision. However deciding which DataSource should be the primary one might be something worth thinking about depending on the requirements and behavior of your application.

@ConfigurationProperties

Automatically maps the jdbc-url, password, and username properties prefixed with clinics.datasource available in the environment, in this case defined in application.properties (source), and maps them to the DataSource being created in clinicsDataSource. If this feels too “magical” DataSourceBuilder (javadoc) also has standard builder methods; url(String), password(String), username(String) available among others.

Using @ConfigurationProperties helps to keep the behavior, from a developer’s perspective, more consistent with how a Spring Boot application would work if it had only a single DataSource. @ConfigurationProperties could also be useful in other scenarios. Here is an example of using @ConfigurationProperties to map to fields within a configuration class from an earlier version of the example project.

@Qualifier

The arguments for the @Bean methods of clinicTransactionManager and clinicEntityManagerFactory are annotated with @Qualifier. Like with @Primary, @Qualifier tells the Spring which instance of class to use when there are multiple available in the application context. By default the name of a bean is the same name as the method that created the bean.

Defining the Properties

Next we need to provide Spring with the values to connect to both our databases. In this example we are connecting to containerized instances of MySQL and Postgres. We will go into a little more detail on this properties file below:

clinics.datasource.jdbc-url=jdbc:mysql://localhost:3306/clinics-db
clinics.datasource.username=root
clinics.datasource.password=secret

doctors.datasource.jdbc-url=jdbc:postgresql://localhost:5432/doctors-db
doctors.datasource.username=postgres
doctors.datasource.password=secret

spring.jpa.open-in-view=false

jdbc-url

When defining a datasource using the spring.datasource properties the property of url would be used. However with Spring Boot 2, HirkariDataSource became the standard DataSource implementation, so to use @ConfigurationProperties like in the @Configuration classes above, the property needs to be jdbc-url.

More info can be found here, as well as a workaround if you’d prefer to keep using url.

(I plan on going into more depth about this in a future article, as this change caused me a lot of pain while putting together the example application)

spring.jpa.open-in-view

This is ultimately an optional addition. By default Spring Boot sets this property to true which is arguably an anti-pattern, which you can read more about here. Why this is relevant in an article about configuring multiple datasources is that when spring.jpa.open-in-view is set to true, Spring MVC will look for an instance of PlatformTransactionManager and LocalContainerEntityManagerFactoryBean in the application context.

This could had been alternatively resolved by adding @Primary to one of the @Bean definitions of both PlatformTransactionManager and LocalContainerEntityManagerFactoryBean as was done with clinicsDataSource, however disabling spring.jpa.open-in-view should generally be done anyways, so that is a better resolution.

Running the Application

There are several scripts available for running the demo application, added for convenience and experimentation.

  • build.sh – builds the docker images and Java artifact
  • run.sh – starts up the Docker containers and Spring Boot application (note: there is a 15 second sleep between starting the containers and starting the app, to give the containers time to startup)
  • requests.sh – curl commands for POSTing and GETting to the Spring Boot application
  • kill.sh – stops and removes the Docker containers

The application by default runs at http://localhost:8080 with GET/POST endpoints residing at /clinics and /doctors.

Conclusion

With a little work a Spring Boot application can be setup to handle multiple datasources and still have an overall pretty familiar look and feel to it.

The code used in this article can be found here: https://github.com/wkorando/multi-datasources-spring-boot