Skip to main content

Streamline your Spring Boot microservice deployment

· 14 min read

Microservices are small self-contained services that are independently deployed and communicate over well-defined APIs. They are highly maintainable and testable and thus can bring great flexibility to the application. This makes microservices a popular approach in software development. Spring Boot is one of the best backend frameworks that is easy to build and run microservices. Spring Boot uses Apache Tomcat by default as a Java web application server to process HTTP requests.

In this article, basic concepts of deploying Microservices with Spring Boot will be covered. The application that will be built is a to-do web application. There are 2 services to be constructed: Todo and Person. The Todo service stores to do tasks belonging to a person. The Person service stores information about a person and communicates with the Todo service to retrieve the person's list of to-dos. Two ways of deployment will be shown to deploy these services. The first way of deployment uses the popular Eureka as a service registry and the second uses Architect.io to perform service mesh for the microservices to communicate with each other.

Prerequisites

The following tools are needed for this tutorial.

Deploy Spring Boot microservices with Eureka

Eureka is a server registry that registers all client applications. Any clients registered to Eureka will be able to communicate with one another. In this example, the Eureka server is named discoveryservice, and the clients are Todo and Person.

The simplest way to start building Spring Boot applications is to use Spring Initialzr. In this case, 3 services are needed: the Eureka server, the Todo service, and Person service. Provide information about the type of project, language, Spring Boot version, project metadata, and dependencies for the Eureka server as follows, and click GENERATE.

Generate person service

Similarly, for the Todo service:

Generate todo service

Place these projects under a directory named springboot-microservices. The structure of the project should looks like:

Place these projects under a directory named springboot-microservices. The structure of the project should look like:

springboot-microservices
├── discoveryservice
├── person
└── todo

Spring Boot service discovery

The service discovery requires Spring Cloud @EnableEurekaServer in order to serve as a service registry. Adding this annotation allows the communication between microservices that are registered to Eureka. The discovery service application discoveryservice/src/main/java/com/architect/discoveryservice/DiscoveryserviceApplication.java should look like:

package com.architect.discoveryservice;

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

@SpringBootApplication
@EnableEurekaServer
public class DiscoveryserviceApplication {
public static void main(String[] args) {
SpringApplication.run(DiscoveryserviceApplication.class, args);
}
}

Add the following variables to discoveryservice/src/main/resources/application.properties.

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

Todo client service

Project directories:
src/main/java/com/architect/todo/
├── TodoApplication.java
├── controller
├── dto
├── model
├── repository
└── service
package com.architect.todo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class TodoApplication {
public static void main(String[] args) {
SpringApplication.run(TodoApplication.class, args);
}
}

Create the following files:

package com.architect.todo.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.*;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String task;
private Long personId;
}

Lastly, application.properties under resources:

server.port=${SERVER_PORT}
spring.application.name=todo-client
logging.level.org.springframework=ERROR

# eureka
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka

# database
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=create
spring.datasource.initialization-mode=always
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.platform=postgres

Now, the Todo service is ready.

Person client service

Project directories:
src/main/java/com/architect/person
├── PersonApplication.java
├── config
├── controller
├── dto
├── model
├── repository
└── service

The setup of the Person service is similar to the Todo client service. There are a few additional classes needed for the Person client service to communicate with the Todo client service such as RestTemplateConfiguration which is the central Spring class for client-side HTTP requests.

Create the following classes:

package com.architect.person.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Todo {
private Long id;
private String task;
private Long personId;
}

application.properties in the resources folder:

server.port=${SERVER_PORT}
todo-microservice-protocol=${TODO_PROTOCOL}
spring.application.name=person-client
logging.level.org.springframework=ERROR

# eureka
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka

# database
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=create
spring.datasource.initialization-mode=always
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.platform=postgres

Notice (1) in the Person's TodoService, restTemplate is used to make a request to the todo service for a list of todos. By calling the restTemplate function getForEntity(), the person service can communicate with the todo service once both the person and the todo services are registered to the discovery server. (2) To make it simple, only the name field is used to identify a person in Person.java.

The Person service is also ready for deployment.

Build applications

Now, to deploy these applications, each project requires a Dockerfile. The Dockerfile should live at the root directory of each project. A compose.yml file is also needed to tie everything together.

Springboot-microservices
├── compose.yml
├── discoveryservice
│ └── Dockerfile
├── person
│ └── Dockerfile
└── todo
└── Dockerfile

Below is the Dockerfile for the Discovery service. The Todo and Person service Dockerfiles are the same as the Dockerfile of the Discovery service except the name of their jar files.

FROM openjdk:19

# Set the working directory to /app
WORKDIR /app

COPY . .
RUN chmod +x mvnw
RUN ./mvnw clean package -DskipTests

CMD ["java", "-jar", "target/discoveryservice-0.0.1-SNAPSHOT.jar"]

For the Todo client service, change the line that starts with CMD to

CMD ["java", "-jar", "target/todo-0.0.1-SNAPSHOT.jar"]

Similarly, change the CMD line of the Person client service to

CMD ["java", "-jar", "target/person-0.0.1-SNAPSHOT.jar"]

compose.yml

version: '3'

services:
discovery-service:
build:
context: ./discoveryservice
ports:
- '8761:8761'

todo:
build:
context: ./todo
ports:
- 8086:8086
depends_on:
- todo-db
- discovery-service
environment:
SERVER_PORT: 8086
SERVER_HOST: localhost
SPRING_DATASOURCE_USERNAME: architect
SPRING_DATASOURCE_PASSWORD: password
SPRING_DATASOURCE_URL: jdbc:postgresql://todo-db/todo_db
EUREKA_CLIENT_SERVICEURL_DEFAULTZONE: http://discovery-service:8761/eureka/

todo-db:
image: postgres:12
ports:
- '10001:5432'
environment:
POSTGRES_DB: todo_db
POSTGRES_USER: architect
POSTGRES_PASSWORD: password

person:
build:
context: ./person
ports:
- '8087:8087'
depends_on:
- person-db
- todo
- discovery-service
environment:
SERVER_PORT: 8087
SERVER_HOST: localhost
TODO_PROTOCOL: http
SPRING_DATASOURCE_USERNAME: architect
SPRING_DATASOURCE_PASSWORD: password
SPRING_DATASOURCE_URL: jdbc:postgresql://person-db/person_db
EUREKA_CLIENT_SERVICEURL_DEFAULTZONE: http://discovery-service:8761/eureka/

person-db:
image: postgres:12
ports:
- '10002:5432'
environment:
POSTGRES_DB: person_db
POSTGRES_USER: architect
POSTGRES_PASSWORD: password

Deploy the application:

$ docker compose up

Goto http://localhost:8761/ to view the Eureka registry and all of its registered clients.

Please wait a couple of minutes for the client services to be registered before making any requests.

Making requests: Create a person using curl:

$ curl -X POST -H "Content-Type: application/json" -d '{"name":"Person 1"}' http://localhost:8087/api/v1/person-microservice

Create a todo for the person:

$ curl -X POST -H "Content-Type: application/json" -d '{"personName":"Person 1","task": "Buy milk and honey"}' http://localhost:8087/api/v1/person-microservice/todo

Next, see if the Person service can retrieve a list of todos using personId. For example,

$ curl http://localhost:8087/api/v1/person-microservice/{personId}/todos<br>where personId is the person ID, for example, 1.

Successful requests should return:

[
{
"id": 1,
"task": "Buy milk and honey",
"personId": 1
}
]

Scaling the Todo client service

To scale up the Todo client service, annotate the RestTemplateConfiguration class of the Person service with the @LoadBalanced annotation. This will make the instance of RestTemplate load-balanced. Then, update the docker compose ports block of the Todo service to the desired number of replicas. Example ports of 2 Todo replicas:

todo:
ports:
- 8070-8072:8086
# ...

Deploy with command:

$ docker compose up --scale todo=2

Deploy Spring Boot microservices with Architect

To deploy the Todo application with Architect, only the Person and Todo services with their databases are needed. Architect.io uses a yaml file called architect.yml to describe the services and in this example are the Person, Todo, and the 2 postgres services. Place this file at the root of the project under springboot-microservices.

springboot-microservices
├── architect.yml
├── person
└── todo
name: springboot-microservices
description: Spring boot microservices and postgres database
homepage: https://github.com/architect-templates/springboot-microservices
keywords:
- spring boot
- microservices
- postgres
# Add secrets to be used by different services. For more information:
# https://docs.architect.io/deployments/secrets/
secrets:
todo_db_name:
description: Name of the Todo microservice database the Todo component will store content in
default: todo_db
person_db_name:
description: Name of the Person microservice database the Person component will store content in
default: person_db
db_user:
description: Root user to assign to the component's database
default: architect
db_pass:
description: Root password to assign to the component's database
default: password
db_port:
description: Port for database
default: 5432
todo_port:
description: Port for todo service
default: 8086
person_port:
description: Port for person service
default: 8087
services:
todo:
build:
context: ./todo
interfaces:
main:
port: ${{ secrets.todo_port }}
depends_on:
- todo-db
environment:
SERVER_PORT: ${{ services.todo.interfaces.main.port }}
SERVER_HOST: ${{ services.todo.interfaces.main.ingress.host }}
SPRING_DATASOURCE_USERNAME: ${{ secrets.db_user }}
SPRING_DATASOURCE_PASSWORD: ${{ secrets.db_pass }}
SPRING_DATASOURCE_URL:
jdbc:postgresql://${{ services.todo-db.interfaces.main.host }}:${{
services.todo-db.interfaces.main.port }}/${{ secrets.todo_db_name }}
todo-db:
image: postgres:12
interfaces:
main:
port: ${{ secrets.db_port }}
protocol: postgresql
environment:
POSTGRES_DB: ${{ secrets.todo_db_name }}
POSTGRES_USER: ${{ secrets.db_user }}
POSTGRES_PASSWORD: ${{ secrets.db_pass }}
person:
build:
context: ./person
interfaces:
main:
port: ${{ secrets.person_port }}
ingress:
subdomain: person
depends_on:
- person-db
- todo
environment:
SERVER_PORT: ${{ services.person.interfaces.main.port }}
SERVER_HOST: ${{ services.person.interfaces.main.ingress.host }}
TODO_URL: ${{ services.todo.interfaces.main.url }}
SPRING_DATASOURCE_USERNAME: ${{ secrets.db_user }}
SPRING_DATASOURCE_PASSWORD: ${{ secrets.db_pass }}
SPRING_DATASOURCE_URL:
jdbc:postgresql://${{ services.person-db.interfaces.main.host }}:${{
services.person-db.interfaces.main.port }}/${{ secrets.person_db_name }}
person-db:
image: postgres:12
interfaces:
main:
port: ${{ secrets.db_port }}
protocol: postgresql
environment:
POSTGRES_DB: ${{ secrets.person_db_name }}
POSTGRES_USER: ${{ secrets.db_user }}
POSTGRES_PASSWORD: ${{ secrets.db_pass }}

The Todo service replicas will be load balanced for any requests made by the Person service. To communicate with the Todo service, specify Todo url as follow:

person:
environment:
TODO_URL: ${{ services.todo.interfaces.main.url }}

Architect.io will interpolate the value of ${{ services.todo.interfaces.main.url }} to the url of the Todo service. In the Person service, read that value in application.properties. Then, use TODO_URL in the Person's TodoService class to make the request for the list of todos. Since the TODO_URL is used to access the Todo service, there is no need to specify the TODO_PROTOCOL environment variable.

Remove any reference to Eureka

In application.properties, remove eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka

In PersonApplication.java and TodoApplication.java, remove @EnableDiscoveryClient

Remove the following pom.xml dependencies for both Person and Todo client services:

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

Add GSON dependency to pom.xml of the Person client service to serialize and deserialize Java objects to JSON:

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>

Remove @LoadBalanced and its import from RestTemplateConfiguration.java import org.springframework.cloud.client.loadbalancer.LoadBalanced;

Deploy Spring Boot microservices with Architect locally

Be sure to install Architect CLI:

$ npm install -g @architect-io/cli

For more information about the installation, please refer to https://github.com/architect-team/architect-cli.

Once, Architect CLI is installed, run the following command:

$ architect dev

Make requests to the application:

Create a person

$ curl -X POST -H "Content-Type: application/json" -d '{"name":"Person 1"}' https://person.localhost.architect.sh/api/v1/person-microservice

Create todo

$ curl -X POST -H "Content-Type: application/json" -d '{"personName":"Person 1","task": "Buy milk and honey"}' https://person.localhost.architect.sh/api/v1/person-microservice/todo

Get todos

$ curl https://person.localhost.architect.sh/api/v1/person-microservice/1/todos

Scaling the Todo client service with Architect

Architect.io uses Kubernetes cluster to provision and manage containerized applications. Scaling the Todo service can be achieved by simply adding a field named replicas and specify the desired number of replicas. For example, to scale the Todo service up to 2 replicas:

services:
todo:
build:
context: ./todo
replicas: 2

Replicas option greater than 1 only works in production deployments. To deploy spring boot microservices in production, run the following command:

$ architect deploy -e <environment-name> --auto-approve

Learn More

Learn More about building and deploying microservices by checking out similar posts on this blog:

Feel free to leave comments and questions below, and don't forget to follow us on Twitter!