March 25, 2021

Spring Boot Microservices Project

In Part 2 of this Spring Boot Microservices Project Tutorial Series, we are going to continue from where we left in Part 1.

In Part 1, we created two micro services – Product Service and Order Service, in this blog post, we are going to cover following concepts:

  • Service Discovery using Spring Cloud Netflix Eureka
  • Centralized Configuration using Spring Cloud Config Server
  • Storing Secrets in Hashicorp Vault
  • Refreshing Configuration Changes using Spring Cloud Bus

Download Source Code

You can download the source code of this project at Github – https://github.com/SaiUpadhyayula/springboot-microservices-project

Video Tutorial

If you are a visual learner like me, you can check out the video tutorial, which covers this topic.

Service Discovery using Spring Cloud Netflix Eureka

We are going to implement the Service Discovery Pattern in our microservices using the Spring Cloud Netflix Eureka project.

We will start off by creating the service discovery server at start.spring.io website.

Spring Boot Microservices Project - Create Discovery Server

To create the discovery server, we need to just add one dependency – Eureka Server, once you download the project to your machine, we can enable the Eureka Server functionality by adding the @EnableEurekaServer annotation.

package com.programming.techie.discoveryserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServerApplication {

 public static void main(String[] args) {
  SpringApplication.run(DiscoveryServerApplication.class, args);
 }

}

By default our Eureka Server is running in a standalone mode, and it also acts as a client, but there is no need to register the eureka instance with itself, for this reason, we are going to add the following configuration to the application.properties file

server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

Once we add the above properties, we can start the DiscoveryServerApplication class, once you start the application, you can open the Eureka Dashboard at http://localhost:8761/

Registering Micro Services as Eureka Clients

Now it’s time to register our Product Service and Order Service with our Eureka Server, add the following dependency to pom.xml file of both product-service and order-service.

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

We also have to add the @EnableEurekaClient to the ProductServiceApplication and OrderServiceApplication

package com.programming.techie.productservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class ProductServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProductServiceApplication.class, args);
    }

}
package com.programming.techie.orderservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class OrderServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProductServiceApplication.class, args);
    }

}

We need to add the details of the Eureka Service inside the application.properties file, so that both product-service and order-service can communicate and register with the discovery server.

Product Service’s application.properties file

spring.application.name=product-service
server.port=0
eureka.instance.instance-id=${spring.application.name}:${random.uuid}
spring.data.mongodb.uri=mongodb://localhost:27017/product-service

Order Service’s application.properties file

spring.application.name=order-service
server.port=0
eureka.instance.instance-id=${spring.application.name}:${random.uuid}

spring.datasource.url=jdbc:mysql://localhost:3306/order-service
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.jpa.hibernate.ddl-auto=update
spring.datasource.initialization-mode=always
spring.jpa.show-sql=true
spring.datasource.username=root
spring.datasource.password=mysql
server.port=0

When you start both the applications, they will communicate with the Eureka Server which is running at default port 8761. The Eureka Clients will send heartbeat signals to the Server to inform that they are still up and running, when the client stops sending the heartbeat signals after reaching the expected threshold limit, Eureka Server will mark those clients as unhealthy.

We also set the property eureka.instance.instance-id property to a combination of the application name + a Random UUID, so that each instance of our service will be displayed inside the Eureka Dashboard.

Now if you open the Eureka Dashboard at http://localhost:8761/ you should see both Product Service and Order Service.

Spring Boot Microservices Project Tutorial - Eureka Dashboard

Centralized Configuration using Spring Cloud Config Server

We can implement the Centralized Configuration using the Spring Cloud Configuration Server project, using this project we can place the configuration files for all our microservices in a central configuration repository which makes it easier to handle and make changes to them.

To store the configuration properties, we have multiple options to consider:

  • Git Repository
  • Local FileSystem
  • Database (through JDBC)
  • Hashicorp Vault

In this blogpost, we are going to mainly make use of Git Repository to store the configuration, we will also see in the next section how to use HashiCorp Vault to store secrets (passwords)

Setting up config server

Setting up a config server is straight forward, we have to first head over to start.spring.io website, and add the following dependencies:

  • spring-cloud-config-server

That’s all you need to add to create our Config Server, now once you download the project to your machine you have to add the @EnableConfigServer annotation to the ConfigurationServerApplication class.

package com.programming.techie.configurationserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer
public class ConfigurationServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigurationServerApplication.class, args);
    }

}

Now add the following configuration properties, to the application.properties file.

spring.application.name=configuration-server
server.port=8888
spring.cloud.config.server.git.uri=https://github.com/SaiUpadhyayula/microservices-config-server
spring.cloud.config.server.git.username=
spring.cloud.config.server.git.password=

spring.cloud.config.server.git.clone-on-start=true
  • First, we are defining our application name through the spring.application.name
  • We are going to use the default port 8888 for the configuration server
  • Next, we are going to point to the Git Repository URL, where the configuration properties of our microservices are stored.
  • We also added a placeholder to define the username and password of the git repository (make sure not to check in your github credentials 🙂 )
  • By default, Spring Cloud Config Server tries to clone a repository after the first HTTP resource call. So we are going to force the server to clone the repository at the time of startup itself, with the help of spring.cloud.config.server.git.clone-on=true property.

That’s all you need to do to setup the config server 🙂 Now let’s refactor our Product and Order Services to make use of the Config Server.

Refactoring our Microservices to use Config Server

Our product-service and order-service are going to be clients for the Config Server, for this reason I am going to add the spring-cloud-config-client and spring-cloud-starter-bootstrap dependencies to the pom.xml of both product-service and order-service

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
            <version>3.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <version>3.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

The spring-cloud-starter-bootstrap is used to tell Spring framework to connect to the configuration server at the time of bootstrapping the application.

For this reason, we have to move our configuration from application.properties to bootstrap.properties file.

bootstrap.properties (Product Service)

spring.application.name=product-service
server.port=0
eureka.instance.instance-id=${spring.application.name}:${random.uuid}
spring.data.mongodb.uri=mongodb://localhost:27017/product-service
spring.cloud.config.uri=http://localhost:8888

management.endpoints.web.exposure.include=*

bootstrap.properties (Order Service)

spring.application.name=order-service
server.port=0
eureka.instance.instance-id=${spring.application.name}:${random.uuid}

spring.datasource.url=jdbc:mysql://localhost:3306/order-service
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.jpa.hibernate.ddl-auto=update
spring.datasource.initialization-mode=always
spring.jpa.show-sql=true
spring.datasource.username=root
spring.datasource.password=mysql
server.port=0

spring.cloud.config.uri=http://localhost:8888
management.endpoints.web.exposure.include=*

Okey, we configured the Config Server details in both our microservices, now it’s time to actually move the configuration to our Git Repository, for that I already created a git repository https://github.com/SaiUpadhyayula/microservices-config-server

In there, I created properties file and moved the configuration properties for each microservice.

After moving the configuration properties, this is how the order-service.properties file inside the Git Repository looks like:

spring.datasource.url=jdbc:mysql://localhost:3306/order-service
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.jpa.hibernate.ddl-auto=update
spring.datasource.initialization-mode=always
spring.jpa.show-sql=true

bootstrap.properties (Order Service)

spring.application.name=order-service
server.port=0
eureka.instance.instance-id=${spring.application.name}:${random.uuid}

spring.datasource.username=root
spring.datasource.password=mysql
server.port=0

spring.cloud.config.uri=http://localhost:8888
management.endpoints.web.exposure.include=*

Now if you startup both the product-service and order-service applications, they should startup without any errors.

This is all well and good, but we are still left with some important configurations , ie. Database Credentials. Storing the sensitive Database URL and Credentials inside a Git Repository is not good idea, so we are going to make use of Hashicorp Vault, to store this sensitive information.

Storing Secrets in Hashicorp Vault

Vault is a tool by Hashicorp which is used to store and access secrets from our application. You can download the software to your machine from the Downloads page – https://www.vaultproject.io/downloads

After installing Vault, you can start the vault server in dev-mode using the below command:

$ vault server -dev

And now in the console, you will see the command to set Vault Address as environment variable, you can execute the command based on your Operating System, for Windows the following commands are displayed:

PowerShell:
    $env:VAULT_ADDR="http://127.0.0.1:8200"
cmd.exe:
    set VAULT_ADDR=http://127.0.0.1:8200

Once you execute the above command, we can now store the secrets inside the Vault Store. To store the secrets, we are going to maintain them in a json file, so I created the file product-service-credentials.json and move the spring.data.mongo.uri property from bootstrap.properties file

{
  "spring.data.mongodb.uri": "mongodb://localhost:27017/product-service"
}

order-service-credentials.json

Let’s move the spring.datasource.username and spring.datasource.password properties from the bootstrap.properties file.

{
  "spring.datasource.username": "root",
  "spring.datasource.password": "mysql"
}

Now we have to write these json files to Vault Store using the following commands:

vault kv put secret/order-service @order-service-credentials.json
vault kv put secret/product-service @product-service-credentials.json

Adding Vault Starter to our Microservices

We moved the secrets to vault, but we still have to configure our microservices as clients to read the secrets from the Vault Server, we can do that by adding the following dependency.

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

Now we have to update our bootstrap.properties with the Vault Server information, we can do that by adding the below properties:

spring.cloud.vault.host=localhost
spring.cloud.vault.port=8200
spring.cloud.vault.scheme=http
spring.cloud.vault.authentication=TOKEN
spring.cloud.vault.token=s.z39kI9tfR6Ljvkc57MWYhmPX
spring.cloud.vault.application-name=order-service

After configuring this details inside both product and order service, we can start the applications, and they should run without any issues.

Refreshing Configuration Changes using Spring Cloud Bus

Alright, so we managed our configuration through Git Repository and secrets through Vault Secret Store, now how can we handle configuration changes in our microservices ? In a non-trivial setup it’s fairly common that the configuration properties will change now and then, in that case we have to manually trigger the /refresh endpoint for each microservice.

You can find more explanation about this topic in the Spring Boot Microservices Project Tutorial Video

But it’s not practical to trigger this endpoint if we have multiple microservices, for this reason we can automate this process by using the Spring Cloud Bus project, using this project, we can broadcast the configuration changes through a message broker like Rabbit MQ, and all the consumers which are subscribed to this broker, will refresh the configuration properties.

To add Spring Cloud Bus to our project, we can add the following dependency to our product-service and order-service microservies.

<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

After that, we have to add the following properties to the bootstrap.properties file

spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

As you can see we are using RabbitMQ as message broker for this case, you can download and install RabbitMQ from this page – https://www.rabbitmq.com/download.html

PS: We can also move the RabbitMQ credentials to our Vault Secret Store.

Once this is done, just restart both the microservices, and if you change any property inside the Git Repository, just trigger http://localhost:<port-numbe>/bus/refresh endpoint for either of the microservice.

Spring Cloud Bus will trigger an event and all the microservices subscribed to our Message Broker, will refresh the configuration.

Conclusion

That’s it for this blogpost, in the next article, we are going to implement the API Gateway and Authorization Server using Spring Cloud Gateway and Keycloak.

About the author 

Sai Upadhyayula

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

Subscribe now to get the latest updates!