Building a Cloud Native Application using Spring
Cloud native applications are one of the most impactful ways of building applications for the global population. Spring Boot mixed with Spring Cloud gives developers a rapid starting point for building Cloud Native as well as Microservices based systems.
WHAT ARE MICROSERVICES?
It is similar to Services Oriented Architecture or SOA but are not SOA. In Microservices, the communication is mostly being made using REST or gRPC (uses Protocol Buffers as binary data interchange) where SOA communicates using SOAP. A single microservice is a single domain, single purpose service such as data service and process service.
WHY SPRING BOOT?
Spring Boot was designed for two main purposes, Microservices is one of them. Spring cloud adds 12 factor plumbing to a Java Based Microservices Application.
Our main focus is to Build a Microservices and Cloud Native Application rather than a Spring Based Application. Spring Boot with the help of Spring Cloud serves the purpose.
TYPES OF SERVICES
* Data Services - Microservices that serve data for a single domain
* Process/Buiness Services - Microservices that aggregate one or more data services and apply business processing
* Web Application Services - Microservices that serve a single purpose web application
It is an Data Service for Employee.
Note create two SQL scripts - data.sql and schema.sql for inserting data and defining data schema respectively and place them in resources folder. In java folder, place following files in addition to Main class:
Now Run the application and test it in browser.
Spring boot lets us externalize the configuration so that we can work with same application code in different environments.
We can create a Config server which manages all the microservices. We need to manage just the Config server and it will itself manage the microservices. The microservices will become the client of the Config server. It also acts as central variables management. And the configurations will be controlled by Version control (Git).
Steps involved:
Now initalize it as a git repo and commit it.
Now run the config server. Access url : <host>:9000/<microservice_name>/default.
Add bootstrap.properties file in resources:
Now run it and check.
Eureka Server is much like Config Server and has a built-in dashboard which supports replication.
Eureka Client works much like Config client and leverages spring.application.name.
Run the application and go to <host>:8761.
Restart the application and refresh the eureka server page to check visibility of the microservice. (Ignore the warnings as of now)
Create a new Spring project from start.spring.io with name main-service. It is a type of business service that integrates multiple domains (a single microservice represents a single domain). Add dependencies - web, spring-cloud-starter-netflix-eureka-client and spring-cloud-starter-ribbon.
Configure the application as:
Note: We got SERVICEONE from Eureka Registry.
Run the application and check.
We can also use Feign Client instead of Rest Template by adding above new class and configuring old app as:
This is the most basic implementation of Microservices Cloud Native Application. We will work on API Gatways, Microservice Intercommunication using Messaging rather than Rest Templates or Feign Clients and a production like application in coming tutorials.
WHAT ARE MICROSERVICES?
It is similar to Services Oriented Architecture or SOA but are not SOA. In Microservices, the communication is mostly being made using REST or gRPC (uses Protocol Buffers as binary data interchange) where SOA communicates using SOAP. A single microservice is a single domain, single purpose service such as data service and process service.
WHY SPRING BOOT?
Spring Boot was designed for two main purposes, Microservices is one of them. Spring cloud adds 12 factor plumbing to a Java Based Microservices Application.
Our main focus is to Build a Microservices and Cloud Native Application rather than a Spring Based Application. Spring Boot with the help of Spring Cloud serves the purpose.
TYPES OF SERVICES
* Data Services - Microservices that serve data for a single domain
* Process/Buiness Services - Microservices that aggregate one or more data services and apply business processing
* Web Application Services - Microservices that serve a single purpose web application
MICROSERVICE 1
Start with a Spring Boot starter template from start.spring.io with dependencies - Spring Web, Spring Data JPA and H2 DB. Name it according to the purpose it solves, I will call it - service-one.It is an Data Service for Employee.
Note create two SQL scripts - data.sql and schema.sql for inserting data and defining data schema respectively and place them in resources folder. In java folder, place following files in addition to Main class:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
spring.jpa.hibernate.ddl-auto = none |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.persistence.Entity; | |
import javax.persistence.Table; | |
import javax.persistence.Id; | |
import javax.persistence.Column; | |
import javax.persistence.GeneratedValue; | |
import javax.persistence.GenerationType; | |
@Entity | |
@Table(name="EMPLOYEE") | |
public class Employee{ | |
@Id | |
@Column(name="EMP_ID") | |
@GeneratedValue(strategy=GenerationType.AUTO) | |
private long id; | |
@Column(name="FIRST_NAME") | |
private String firstName; | |
@Column(name="LAST_NAME") | |
private String lastName; | |
@Column(name="EMAIL_ADDRESS") | |
private String emailAddress; | |
@Column(name="DEPARTMENT") | |
private String department; | |
//Getters and Setters | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.springframework.data.repository.CrudRepository; | |
import org.springframework.stereotype.Repository; | |
@Repository | |
public interface GuestRepository extends CrudRepository<Guest, Long>{ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.bind.annotation.RestController; | |
import org.springframework.web.bind.annotation.GetMapping; | |
@RestController | |
@RequestMapping("/employees") | |
public class EmployeeWebServices{ | |
private final EmployeeRepository repository; | |
public EmployeeWebServices(EmployeeRepository repository){ | |
super(); | |
this.repository = repository; | |
} | |
@GetMapping | |
public Iterable<Employee> getAllEmployees(){ | |
return this.repository.findAll(); | |
} | |
@GetMapping("/{id}") | |
public Employee getEmployee(@PathVariable("id") long id){ | |
return this.repository.findById(id).get(); | |
} | |
} |
EXTERNAL CONFIGURATION
Till now, we created a single microservice and it runs on its default port of 8080. As we create more microservices, they can't run on same port of 8080. Though we can change the port of microservice in application properties file but it will become difficult to manage them and configure them. Especially when we run our microservices on different environments.Spring boot lets us externalize the configuration so that we can work with same application code in different environments.
We can create a Config server which manages all the microservices. We need to manage just the Config server and it will itself manage the microservices. The microservices will become the client of the Config server. It also acts as central variables management. And the configurations will be controlled by Version control (Git).
Steps involved:
Setting Up External configuration
Create a config folder with <name_of_microservice>.properties files. Structure of it is as:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
server.port=8800 |
Setting Up Configuration Server
Create a new Spring project from start.spring.io with spring-cloud-config-server dependency. Name it ConfigServerApplication. Configure the structure of project as:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
server.port = 9000 | |
spring.cloud.config.server.git.uri = ${USER.HOME}\\Desktop\\Config #Can also have git Uri where it is commited | |
spring.cloud.config.server.git.force-pull = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
import org.springframework.cloud.config.server.EnableConfigServer; | |
@SpringBootApplication | |
@EnableConfigServer | |
public class ConfigServerApplication{ | |
public static void main(String[] args){ | |
SpringApplication.run(ConfigServerApplication.class, args); | |
} | |
} |
Setting Up Microservice as Client
Change the previous structure and update it as:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- Add Properties --> | |
<properties> | |
<java.version>8</java.version> | |
<sprin-cloud.version>Hoxton.RC</spring-cloud.version> | |
</properties> | |
<!-- Add Dependency --> | |
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-config-client</artifactId> | |
</dependency> | |
<!-- Add Dependency Management --> | |
<dependencyManagement> | |
<dependencies> | |
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-dependencies</artifactId> | |
<version>${spring-cloud.version}</version> | |
<type>pom</type> | |
<scope>import</scope> | |
</dependency> | |
</dependencies> | |
<repositories> | |
<id>spring-milestones</id> | |
<name>Spring Milestones</name> | |
<url>https://repo.spring.io/milestone</url> | |
</repositories> | |
</dependencyManagement> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
spring.application.name = serviceone #same as name of properties file in config server | |
spring.cloud.config.uri = http://localhost:9000 |
SERVICE DISCOVERY WITH EUREKA
As we start to build distributed systems, its become obvious to identify where our service endpoints are. Eureka is a discovery platform by netflix and has a rest based interface.Eureka Server is much like Config Server and has a built-in dashboard which supports replication.
Eureka Client works much like Config client and leverages spring.application.name.
Setting up Eureka Server
Create a new Spring project from start.spring.io with name eureka-server and dependency spring-cloud-starter-eureka-server. Configure it as:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; | |
@SpringBootApplication | |
@EnableEurekaServer | |
public class EurekaServerApplication{ | |
public static void main(String[] args){ | |
SpringApplication.run(EurekaServerApplication.class, args); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
server.port=8761 | |
eureka.client.register-with-eureka=false | |
eureka.client.fetch-registry=false |
Microservices Registration on Eureka
Make following changes in microservice project:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- Add Dependency --> | |
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> | |
</dependency> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; | |
@SpringBootApplication | |
@EnableDiscoveryClient | |
public class EmployeeServicesApplication{ | |
public static void main(String[] args){ | |
SpringApplication.run(EmployeeServicesApplication.class, args); | |
} | |
} |
Consuming Microservices with Ribbon
Since we only have a single instance of each service up and running, we don't need Ribbon's Load Balancing. For our case, we don't have a load balancer involved. Ribbon provides a way to integrate load balancer to our web service calls.Create a new Spring project from start.spring.io with name main-service. It is a type of business service that integrates multiple domains (a single microservice represents a single domain). Add dependencies - web, spring-cloud-starter-netflix-eureka-client and spring-cloud-starter-ribbon.
Configure the application as:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
spring.application.name = mainservice | |
server.port = 8080 | |
eureka.client.serviceUrl.defaultZone = http://localhost:8761/eureka |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; | |
import org.springframework.cloud.client.loadbalancer.LoadBalanced; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.web.client.RestTemplate; | |
@SpringBootApplication | |
@EnableDiscoveryClient | |
public class MainServiceApplication{ | |
@Bean | |
@LoadBalanced | |
public RestTemplate restTemplate(){ | |
return new RestTemplate(); | |
} | |
public static void main(String[] args){ | |
SpringApplication.run(MainServiceApplication.class, args); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Date; | |
public class DismissEmployee(){ | |
private long empId; | |
private boolean status; | |
private Date date; | |
//Getters and Setters | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class EmployeeRecord{ | |
private long id; | |
private String department; | |
//Getters and Setters | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.springframework.web.bind.annotation.RestController; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.client.RestTemplate; | |
import org.springframework.web.bind.annotation.GetMapping; | |
@RestController | |
@RequestMapping("/dismiss-employee") | |
public class MainWebService{ | |
private final RestTemplate restTemplate; | |
public MainWebService(RestTemplate restTemplate){ | |
super(); | |
this.restTemplate=restTemplate; | |
} | |
@GetMapping | |
public List<DismissEmployee> dismissEmployees(){ | |
List<EmployeeRecord> empRecords = this.getAllEmployeeRecords(); | |
List<DismissEmployee> dismissedEmployees = new ArrayList<>(); | |
dismissEmployees.forEach(emp -> { | |
DismissEmployee dismissEmployee = new DismissEmployee(); | |
dismissEmployee.setEmpId(emp.getId()); | |
dismissEmployee.setStatus(false); | |
dismissedEmployees.add(dismissEmployee); | |
}); | |
return dismissedEmployees; | |
} | |
private List<EmployeeRecord> getAllEmployeeRecords(){ | |
ResponseEntity<List<EmployeeRecord>> resp = this.restTemplate.exchange("http://SERVICEONE/employees", | |
HttpMethod.GET, | |
null, | |
new ParameterizedTypeReference<List<EmployeeRecord>>(){ | |
}); | |
return resp.getBody(); | |
} | |
} |
Note: We got SERVICEONE from Eureka Registry.
Run the application and check.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.springframework.cloud.openfeign.FeignClient; | |
import org.springframework.web.bind.annotation.GetMapping; | |
@FeignClient("serviceone") | |
public interface EmployeeClient{ | |
@GetMapping("/employees") | |
List<EmployeeRecord> getAllEmployees(); | |
@GetMapping("/employees/{id}") | |
EmployeeRecord getEmployee(@PathVariable("id") long id); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; | |
import org.springframework.cloud.netflix.feign.EnableFeignClients; | |
@SpringBootApplication | |
@EnableDiscoveryClient | |
@EnableFeignClients | |
public class MainServiceApplication{ | |
public static void main(String[] args){ | |
SpringApplication.run(MainServiceApplication.class, args); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.springframework.web.bind.annotation.RestController; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.bind.annotation.GetMapping; | |
@RestController | |
@RequestMapping("/dismiss-employee") | |
public class MainWebService{ | |
private final EmployeeClient employeeClient; | |
public MainWebService(EmployeeClient employeeClient){ | |
super(); | |
this.employeeCliente=employeeClient; | |
} | |
@GetMapping | |
public List<DismissEmployee> dismissEmployees(){ | |
List<EmployeeRecord> empRecords = this.employeeClient.getAllEmployees(); | |
List<DismissEmployee> dismissedEmployees = new ArrayList<>(); | |
dismissEmployees.forEach(emp -> { | |
DismissEmployee dismissEmployee = new DismissEmployee(); | |
dismissEmployee.setEmpId(emp.getId()); | |
dismissEmployee.setStatus(false); | |
dismissedEmployees.add(dismissEmployee); | |
}); | |
return dismissedEmployees; | |
} | |
} |
This is the most basic implementation of Microservices Cloud Native Application. We will work on API Gatways, Microservice Intercommunication using Messaging rather than Rest Templates or Feign Clients and a production like application in coming tutorials.
Comments
Post a Comment