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


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:


spring.jpa.hibernate.ddl-auto = none

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
}
view raw Employee.java hosted with ❤ by GitHub

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface GuestRepository extends CrudRepository<Guest, Long>{
}

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();
}
}
Now Run the application and test it in browser.


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:

server.port=8800
Now initalize it as a git repo and commit it.



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:

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
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);
}
}
Now run the config server. Access url : <host>:9000/<microservice_name>/default.



Setting Up Microservice as Client

Change the previous structure and update it as:

<!-- 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>
view raw pom.xml hosted with ❤ by GitHub
Add bootstrap.properties file in resources:


spring.application.name = serviceone #same as name of properties file in config server
spring.cloud.config.uri = http://localhost:9000
Now run it and check.



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:

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);
}
}

server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
Run the application and go to <host>:8761.


Microservices Registration on Eureka

Make following changes in microservice project:

<!-- Add Dependency -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
view raw pom.xml hosted with ❤ by GitHub

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);
}
}
Restart the application and refresh the eureka server page to check visibility of the microservice. (Ignore the warnings as of now)


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:

spring.application.name = mainservice
server.port = 8080
eureka.client.serviceUrl.defaultZone = http://localhost:8761/eureka

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);
}
}

import java.util.Date;
public class DismissEmployee(){
private long empId;
private boolean status;
private Date date;
//Getters and Setters
}

public class EmployeeRecord{
private long id;
private String department;
//Getters and Setters
}

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.

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);
}
We can also use Feign Client instead of Rest Template by adding above new class and configuring old app as:


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);
}
}
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

Popular posts from this blog

What is Computer Programming? How to get started

Top 20 Hacking Movies to Watch During Covid Quarantine 2020

Learn any programming language easily and fast in Just 11 Steps