STEP:1
Building a Simple Application with Spring Boot
1.1 Controller-Service-Repository Architecture
- Controller- Controller layer contains the *application logic, mapping the user request to particular functions and passing the user input to service layer to apply the business logic.
- Service- This is the layer between the controller and repository which performs the *business logic and validation logic. The controller passes the user input to the service layer and after applying the business logic, it is passed to the repository layer.
- Repository- The layer which interact with the database CRUD operations via the DAOs(data access objects).
- Model- Is the simple POJO classes which is acting as the DTO(Interact with application level data transfer) or DAO(Interaction with database operations)
On the other hand the application logics represent the application level operations, which is not specific to the business domain but a function of the application itself. Ex: Press the button and pop up the login form is an example of the application logic. Validating the user login data is the domain specific requirement which comes under business logic.
1.2 Create Spring Projecr Structure
Go to the https://start.spring.io and there you can configure the spring project structure. In this sample we are using the Java 1.8 as the JRE and maven as the build tool. In addition to that we need to add spring dependencies. For the moment we will add 'spring web starter' dependency. You can add it by just typing the key-word 'web' in the dependencies text box and select the 'spring web starter' dependency from the list. After that press the 'generate the project' button to save the project in your local machine.
After that you can import the project into the IDE(Here I'm using Inte IntelliJ IDEA) and continue your development. As the first change, we need to add the layer structure(controller-model-service-repository) as below.
I'm going to build the very simple application to expose some APIs. Before going through the sample code, It's worth to get some idea about the usage of the annotations and some important concepts in spring framework.
@EnableAutoConfiguration - It is used to get the default configurations in spring. Spring boot gathers the required component to run the application from the class path and automatically provide those configurations and objects from the dependencies which you already added to the project.(Ex: Once it found the DB driver in the class path, It will automatically add the DB configurations)
With the above interface definition, we can define the reference variable in our 'Car' class by referring the interface as below.
1.2 Create Spring Projecr Structure
Go to the https://start.spring.io and there you can configure the spring project structure. In this sample we are using the Java 1.8 as the JRE and maven as the build tool. In addition to that we need to add spring dependencies. For the moment we will add 'spring web starter' dependency. You can add it by just typing the key-word 'web' in the dependencies text box and select the 'spring web starter' dependency from the list. After that press the 'generate the project' button to save the project in your local machine.
After that you can import the project into the IDE(Here I'm using Inte IntelliJ IDEA) and continue your development. As the first change, we need to add the layer structure(controller-model-service-repository) as below.
1.3 Build a Sample Application
I'm going to build the very simple application to expose some APIs. Before going through the sample code, It's worth to get some idea about the usage of the annotations and some important concepts in spring framework.
Spring Annotations used in this application
- @Controller,@Repository,@Service
In spring framework,we can use the class level annotation @Component to order to create a bean from the class automatically and register in the Spring container to release it on demand.
Ex:
A bean of the Car class will be registered in the Spring container with the below definition.
@Component class Car{ }
@Controller, @Repository,@Service annotations are inherited from @Component annotation. All of them are registering beans relevant to the classes which they have annotated with. In addition to that we can clearly differentiate the classes in the layered architecture by those annotation names instead of using the @Component for all. The other important thing is those three annotations have some special features than the @Component annotation.
Ex:The @Controller annotation is not only registering a controller bean in the container, but also providing the capability of handling the request mappings(@RequestMapping) received from the clients.
- @Autowired
The heart of the spring framework is dependency injection. Instead of creating the objects using class constructor with 'new' key word, we are asking the spring container to inject the bean object to the property annotated with @Autowired annotation.
Class Test{ @Autowired Car BMW; }
- @RequestMapping
As I mentioned earlier, the class annotated with @Controller handle the request path mappings. So it can contain multiple request mappings. @RequestMapping is used to define the particular path which the request should be mapped to.
- @Bean and @Configuration
Instead of using the class level annotation @Component to register bean classes, we can use @Bean method level annotation. But the class should be annotated with @Configuration to indicate this class has the methods for bean definitions.
@Configuration
public class AppConfig {
@Bean
public Employee employee123() {
return new Employee();
}
}
- @SpringBootApplication
@EnableAutoConfiguration - It is used to get the default configurations in spring. Spring boot gathers the required component to run the application from the class path and automatically provide those configurations and objects from the dependencies which you already added to the project.(Ex: Once it found the DB driver in the class path, It will automatically add the DB configurations)
@ComponentScan - This annotation indicates the application to check the components inside the package to find out the beans, services and configuration required to run the application.
@Configuration -Already described above.
Concepts of code to interface and dependency injection
Dependency injection is the concept of getting the support of the third party to inject the dependency required by the classes instead of creating them itself.
Ex: If class Car needs to invoke the methods of class Engine, the traditional way is creating the object inside the class Car and invoke the method of class Engine.
class Car{
Engine engine=new Engine();
System.out.println(engine.power());
}
Anyway the problem of the above traditional way is if we changed the class name 'Enging' as 'Motor', we need to make that change in each and every class which create the 'engine' object by referring the Engine class. To overcome that issue we can use the concept of 'code to interface'.
We can define an interface called 'CarParts' and implement that interface.
We can define an interface called 'CarParts' and implement that interface.
Interface CarParts{ engine.power(); } class Motor implements CarParts{ int cylinderCapacity=1000; public int power(){ return cylinderCapacity*4; } }
With the above interface definition, we can define the reference variable in our 'Car' class by referring the interface as below.
class Car{
CarParts engine=new Motor();
System.out.println(engine.power());
}
But still we have to change the constructor used to create the object from the new Motor class. The only way to overcome this issue is getting the help of third party to create the object and inject the object at run time. In spring, It is done by the spring container with the help of beans. Finally, your Car class appears like below.
class Car{
CarParts engine;
System.out.println(engine.power());
}
With the above code, even though we make the changes to the Motor class, no need to touch the other classes which it refers. The dependency objects contains all the changes and inject them at the run time to the Car class.
Sample Java Code, Run Application and Testings
The classes and interfaces should be added as below in the project structure.
Controller
EmployeeController.java
package com.test.sample.controller; import com.test.sample.model.Department; import com.test.sample.model.Employee; import com.test.sample.service.DepartmentService; import com.test.sample.service.EmployeeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/employee") public class EmployeeController { @Autowired EmployeeService employeeService; @Autowired DepartmentService departmentService; @RequestMapping("/personalInfo") public Employee getEmployeeDetails() { return employeeService.getEmployeePersonalInfo(); } @RequestMapping("/departmentInfo") public Department getDepartmentDetails() { return departmentService.getDepartmentInfo(); } }
Service
EmployeeService.java
package com.test.sample.service;
import com.test.sample.model.Employee;
public interface EmployeeService {
Employee getEmployeePersonalInfo();
}
EmployeeServiceImpl.java
package com.test.sample.service;
import com.test.sample.model.Employee;
import com.test.sample.repository.EmployeeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
EmployeeRepository employeeRepository;
@Override
public Employee getEmployeePersonalInfo() {
return employeeRepository.getEmployeePersonalInfo();
}
}
DepartmentService.java
package com.test.sample.service;
import com.test.sample.model.Department;
public interface DepartmentService {
Department getDepartmentInfo();
}
DepartmentServiceImpl.java
package com.test.sample.service;
import com.test.sample.model.Department;
import com.test.sample.repository.DepartmentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DepartmentServiceImpl implements DepartmentService {
@Autowired
DepartmentRepository departmentRepository;
@Override
public Department getDepartmentInfo() {
return departmentRepository.getDepartmentInfo();
}
}
Repository
EmployeeRepository.java
package com.test.sample.repository;
import com.test.sample.model.Employee;
public interface EmployeeRepository {
Employee getEmployeePersonalInfo();
}
EmployeeRepositoryImpl.java
package com.test.sample.repository;
import com.test.sample.model.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class EmployeeRepositoryImpl implements EmployeeRepository {
@Autowired
Employee employee;
@Override
public Employee getEmployeePersonalInfo() {
employee.setEmpId(1);
employee.setEmpName("Randika");
employee.setEmpAddress("Maharagama");
return employee;
}
}
DepartmentRepository.java
package com.test.sample.repository;
import com.test.sample.model.Department;
public interface DepartmentRepository {
Department getDepartmentInfo();
}
DepartmentRepositoryImpl.java
package com.test.sample.repository;
import com.test.sample.model.Department;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class DepartmentRepositoryImpl implements DepartmentRepository {
@Autowired
Department department;
@Override
public Department getDepartmentInfo() {
department.setDeptId("D12");
department.setDeptName("Finance");
return department;
}
}
Model
To create the beans in following two classes, I have used different method as I mentioned earlier. In the department class, I used class level annotation @Component to create a bean. But in Employee class I'm using method level annotation @Bean with @Configuration annotation which is defined inside the AppConfig.java class.
Department.java
package com.test.sample.model;
import org.springframework.stereotype.Component;
@Component
public class Department {
private String deptId;
private String deptName;
public String getDeptId() {
return deptId;
}
public void setDeptId(String deptId) {
this.deptId = deptId;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
}
Employee.java
package com.test.sample.model;
public class Employee {
private int empId;
private String empName;
private String empAddress;
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public String getEmpAddress() {
return empAddress;
}
public void setEmpAddress(String empAddress) {
this.empAddress = empAddress;
}
}
Bean Definition
AppConfig.java
package com.test.sample;
import com.test.sample.model.Employee;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public Employee employee() {
return new Employee();
}
}
Application
SampleApplication.java
package com.test.sample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SampleApplication {
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
}
application.properties
/*Spring boot contains the embeded tomcat server.
We can define the application running port by below property.*/
server.port=9090
Run the application - right click on the 'SampleApplication.java' and run. If the application started correctly, you can see the following log.
Test the application - Invoke the following requests via the browser.
STEP:2
Registering the Server Instances in Service Registry.2.1 What is Service Registry?
But in cloud based micro service architectures, can't apply these static network addresses, because of there are concepts like auto scaling, failures, upgrades etc...
In micro service architecture this is done by service registry. Actually, it is a database of network addresses of available instances. Service registry exposes an API to register(POST), update(PUT), delete(DELETE) the network location of server instances.
Netflix Eureka is a sample for service registry. Eureka client query DNS to discover the network locations of Eureka servers.
2.2 Discovery Patterns
Clients use the following methods to search the service discovery and get the network address of the service instances which it needs to access.Client side discovery - Client is responsible to find the network address of the service instance which needs to connect to access the service.
Server side discovery - Client sends the request to the outside load balancer and load balancer responsible to search the service registry and redirect the request to relevant service instances.
2.3 Create Eureka Service Registry(Eureka server)
Create the project structure with https://start.spring.io/. Add the following dependencies when you create the project structure.
- Spring Web Starter
- Eureka Server
After creating the project, import that into your IDE.
Sample Java Code, Run Application and Testings
Sample Java Code, Run Application and Testings
- Configuring Eureka Registry Server
EurekaApplication.java
package com.example.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
- @EnableEurekaServer annotation indicates that this spring boot application need to be catered as a registry server
spring.application.name= Eureka Application
server.port = 8765
eureka.client.registerWithEureka= false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone= http://localhost:${server.port}/eureka/
- eureka.client.registerWithEureka: If we set this property as true then while the server starts the inbuilt client will try to register itself with the Eureka server.
- eureka.client.fetch-registry: The inbuilt client will try to fetch the Eureka registry if we set this property as true.It's like a response from cache.
- eureka.client.serviceUrl.defaultZone: Define the host and port which Eureka server is running and the eureka clients need to be fetch the information for register
Start the Eureka server by running the "EurekaApplication.java". After starting the application, browse the console by 'localhost:8765'.
- Registering service instance in Eureka(Service Registry)
You need to add new configuration to the following files to represent our sample application as discovery client and enable the service registration with Eureka registry server.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath />
<!-- lookup parent from repository -->
</parent>
<groupId>com.test</groupId>
<artifactId>sample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sample</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<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>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
In the pom.xml, add the above highlighted dependencies related to eureka client configuration.
application.properties
server.port=9090 spring.application.name=Test-app eureka.client.serviceUrl.defaultZone= http://localhost:8765/eureka/
Insert correct eureka.client.serviceUrl.defaultZone to register the application with Eureka server.
SampleApplication.java
SampleApplication.java
package com.test.sample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class SampleApplication {
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
}
Add @EnableDiscoveryClient annotation to make the application as a discovery client and register in the Eureka server.
- Start the service instances
For one instance we have already given the port number server.port=9090.
Any way we can override this port number at the time of starting the application. To do that we need to create a jar file from the application and run the jar file with the specific port number. We can start multiple application instances by running the jar file with different port numbers.
- To create a jar file, execute mvn clean install from the location of pom.xml file. A jar file will be created in the target folder.
- From target directory, run the following command with different port number to start two instances.
java -jar -Dserver.port=8766 sample-0.0.1-SNAPSHOT.jar
java -jar -Dserver.port=8767 sample-0.0.1-SNAPSHOT.jar
After starting the instances, go and refresh the Eureka server console
You can see that two instances from TEST-APP registered with the service registry.
No comments:
Post a Comment