A bit about me
What is microservice ?
How an Microservices Architecture looks like ?
Advantages of Using Microservices Architecture
Challenges with Microservices
Spring Cloud
Spring Cloud provides tools for developers to quickly build some of the common patterns in distributed systems.
For more details click https://projects.spring.io/spring-cloud/
Solutions in Spring Cloud for Microservices challenges
Problems | Solution |
---|---|
Configuration Management | Spring cloud config server |
Dynamic Scale Up and Down | Naming Server (Eureka) Ribbon (Client Side Load Balancing) Feign (Easier Rest Client) |
Visibility and Monitoring | Zipkin Distributed Tracing Netflix API Gateway |
Fault Talerance | Hystrix |
Microservices we will gonna create
CurrencyCalculationService |
---|
CurrencyExchangeService |
---|
LimitsService |
---|
Spring Cloud Config Server
CurrencyCalculationService |
---|
CurrencyExchangeService |
---|
LimitsService |
---|
Spring Cloud Config Server |
---|
Git |
---|
Setting up limits service
Step 1: Go to https://start.spring.io/ and generate a spring boot project with gradle with the below mention dependencies
Step 2:
Step 3:
Introduce following properties in application.properties
spring.application.name=limits-service
limits-service.minimum=9
limits-service.maximum=999
Step 4:
Create the following files in your project
Note : code for the files above is in the further slides
package com.springcloud.demo.limitsservice;
public class LimitsConfiguration {
private int maximum;
private int minimum;
public int getMaximum() {
return maximum;
}
public void setMaximum(int maximum) {
this.maximum = maximum;
}
public int getMinimum() {
return minimum;
}
public void setMinimum(int minimum) {
this.minimum = minimum;
}
public LimitsConfiguration(int maximum, int minimum) {
this.maximum = maximum;
this.minimum = minimum;
}
}
package com.springcloud.demo.limitsservice;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties("limits-service")
public class Configuration {
private int minimum;
private int maximum;
public int getMinimum() {
return minimum;
}
public void setMinimum(int minimum) {
this.minimum = minimum;
}
public int getMaximum() {
return maximum;
}
public void setMaximum(int maximum) {
this.maximum = maximum;
}
}
package com.springcloud.demo.limitsservice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LimitsConfigurationController {
@Autowired
Configuration configuration;
@GetMapping("/limits")
LimitsConfiguration helloWorld(){
return new LimitsConfiguration(configuration.getMinimum(),configuration.getMaximum());
}
}
Setting up Spring Cloud Config Server
Step 1 : Create the spring boot project with gradle with the dependencies shown below on start.spring.io
Step 2 :
Download and extract the project on your local system. Once you have extracted project then add it as module in your existing project
Step 3 :
Set the following values in application.properties file of spring-cloud-config-server
spring.application.name=spring-cloud-config-server
server.port=8888
spring.cloud.config.server.git.uri=<git-url-containing-properties-file>
Note : Enable Config server by placing @EnableConfigServer at the top of SpringCloudConfigServerApplication.java
In the git repo which you have added limits-service.properties file with the following value
Setting up git for spring cloud config server
limits-service.minimum=9
limits-service.maximum=999
Now hit http://localhost:8888/limits-service/default
You must get the properties which you have configured in the above git repo
Note: url name is related to properties filename
Environment specific configuration
For environment specific configuration you just need to add hyphen with Environment Name i.e limits-service-qa.properties and the properties available in this file will be available on the following url:
Connecting limits-service to spring-cloud-config-server
Rename application.properties file in limits-service to bootstrap.properties and add the following properties to it
spring.application.name=limits-service
spring.cloud.config.uri=http://localhost:8888/
Run both applications i.e limits-service and spring-cloud-config-server and hit
You will see the default values from spring cloud config server
Note : The file is picked on the bases of spring.application.name whose value in this case is limits-service so limits-service.properties will be picked
Configure profiles for Limits service
In order to configure the profile for limits service add the line below in boostrap.properties
spring.profiles.active=qa
Note : if the value is not set in the specified profile's properties file then default value will be picked
Setting up currency exchange service
Step 1:
Create a Spring boot project gradle project with the dependencies shown below
Step 2 :
After downloading and extracting the currency exchange service import it in Intellij idea with other microservices and set the following values in application.properties
spring.application.name=currency-exchange-service
server.port=8000
Step 3 :
Include the following files in the currency-exchange-service
Code for the files is in the next 2 slides
package com.springcloud.demo.currencyexchangeservice;
import java.math.BigDecimal;
public class ExchangeValue {
private Long id;
private String from;
private String to;
private BigDecimal conversionMultiple;
public ExchangeValue() {
}
public ExchangeValue(Long id, String from, String to, BigDecimal conversionMultiple) {
this.id = id;
this.from = from;
this.to = to;
this.conversionMultiple = conversionMultiple;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public BigDecimal getConversionMultiple() {
return conversionMultiple;
}
public void setConversionMultiple(BigDecimal conversionMultiple) {
this.conversionMultiple = conversionMultiple;
}
}
package com.springcloud.demo.currencyexchangeservice;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
@RestController
public class CurrencyExchangeController {
@GetMapping("/currency-exchange/from/{from}/to/{to}")
public ExchangeValue retrieveExchangeValue(@PathVariable String from,@PathVariable String to){
return new ExchangeValue(1000L,from,to, BigDecimal.valueOf(20));
}
}
Run Mulitple instances of Currency Exchange service
Create a copy of existing configuration and set VM options for it to -Dserver.port=8001
Introduce port int type instance variable in ExchangeValue and set the port value in CurrencyExchangeController in the following way:
exchangeValue.setPort(Integer.parseInt(environment.getProperty("local.server.port")));
Setting up currency conversion
Create a spring boot project with gradle which contains the dependency shown in the image below from https://start.spring.io/
Step 2:
In application.properties of currency exchange service add the following lines
spring.application.name=currency-conversion-service
server.port=8100
Step 3:
Include the following files in currency convert module:
Code for the files is in the next to slides
package com.springcloud.demo.currencyconversionservice;
import java.math.BigDecimal;
public class CurrencyConversion {
private Long id;
private String from;
private String to;
private BigDecimal conversionMultiple;
private int port;
private BigDecimal totalCalculatedAmount;
public CurrencyConversion() {
}
public CurrencyConversion(Long id, String from, String to, BigDecimal conversionMultiple) {
this.id = id;
this.from = from;
this.to = to;
this.conversionMultiple = conversionMultiple;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public BigDecimal getConversionMultiple() {
return conversionMultiple;
}
public void setConversionMultiple(BigDecimal conversionMultiple) {
this.conversionMultiple = conversionMultiple;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public BigDecimal getTotalCalculatedAmount() {
return totalCalculatedAmount;
}
public void setTotalCalculatedAmount(BigDecimal totalCalculatedAmount) {
this.totalCalculatedAmount = totalCalculatedAmount;
}
}
package com.springcloud.demo.currencyconversionservice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
@RestController
public class CurrencyConversionController {
@Autowired
Environment environment;
@GetMapping("/currency-converter/from/{from}/to/{to}/quantity/{quantity}")
public CurrencyConversion covertCurrency(@PathVariable String from,
@PathVariable String to,
@PathVariable BigDecimal quantity){
Map<String,String> pathVariables = new HashMap<>();
pathVariables.put("from",from);
pathVariables.put("to",to);
ResponseEntity<CurrencyConversion> responseEntity=new RestTemplate()
.getForEntity("http://localhost:8000/currency-exchange/from/{from}/to/{to}",
CurrencyConversion.class,pathVariables);
CurrencyConversion currencyConversion= responseEntity.getBody();
currencyConversion.setPort(Integer.parseInt(environment.getProperty("local.server.port")));
currencyConversion.setTotalCalculatedAmount(quantity.multiply(currencyConversion.getConversionMultiple()));
return currencyConversion;
}
}
CurrencyCalculationService |
---|
NamingServer |
---|
CurrencyExchangeService1 |
---|
Ribbon |
---|
CurrencyExchangeService2 |
---|
CurrencyExchangeService3 |
---|
Using feign Rest Client for Service invocation
Include the following dependency in build.gradle of currency-conversion-service
compile('org.springframework.cloud:spring-cloud-starter-openfeign')
Enable feign in Spring Boot application by Marking the application class of currency conversion service with
@EnableFeignClients("<package name to scan>")
i.e
@EnableFeignClients("com.springcloud.demo.currencyconversionservice")
Now we need to create a proxy interface class for currency exchange service
package com.springcloud.demo.currencyconversionservice;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "currency-exchange-service" ,url = "localhost:8000")
public interface CurrencyExchangeServiceProxy {
@GetMapping("/currency-exchange/from/{from}/to/{to}")
CurrencyConversion retrieveExchangeValue(@PathVariable("from") String from,
@PathVariable("to") String to);
}
Now add one more action ConvertCurrencyFeign to CurrencyConversionController as shown below:
package com.springcloud.demo.currencyconversionservice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
@RestController
public class CurrencyConversionController {
@Autowired
Environment environment;
@Autowired
CurrencyExchangeServiceProxy proxy;
@GetMapping("/currency-converter/from/{from}/to/{to}/quantity/{quantity}")
public CurrencyConversion covertCurrency(@PathVariable String from,
@PathVariable String to,
@PathVariable BigDecimal quantity){
Map<String,String> pathVariables = new HashMap<>();
pathVariables.put("from",from);
pathVariables.put("to",to);
ResponseEntity<CurrencyConversion> responseEntity=new RestTemplate()
.getForEntity("http://localhost:8000/currency-exchange/from/{from}/to/{to}",
CurrencyConversion.class,pathVariables);
CurrencyConversion currencyConversion= responseEntity.getBody();
currencyConversion.setPort(Integer.parseInt(environment.getProperty("local.server.port")));
currencyConversion.setTotalCalculatedAmount(quantity.multiply(currencyConversion.getConversionMultiple()));
return currencyConversion;
}
@GetMapping("/currency-converter-feign/from/{from}/to/{to}/quantity/{quantity}")
public CurrencyConversion covertCurrencyFeign(@PathVariable String from,
@PathVariable String to,
@PathVariable BigDecimal quantity){
CurrencyConversion currencyConversion= proxy.retrieveExchangeValue(from,to);
currencyConversion.setPort(Integer.parseInt(environment.getProperty("local.server.port")));
currencyConversion.setTotalCalculatedAmount(quantity.multiply(currencyConversion.getConversionMultiple()));
return currencyConversion;
}
}
Setting up client side load balancing with Ribbon
CurrencyCalculationService |
---|
NamingServer |
---|
CurrencyExchangeService1 |
---|
Ribbon |
---|
CurrencyExchangeService2 |
---|
CurrencyExchangeService3 |
---|
In order to get started with ribbon first you need to import the dependency of ribbon in build.gradle of currency-conversion-service as follows:
compile('org.springframework.cloud:spring-cloud-starter-netflix-ribbon')
Mark CurrencyExchangeServiceProxy with RibbonClient annotation as shown below
package com.springcloud.demo.currencyconversionservice;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "currency-exchange-service" )
@RibbonClient(name = "currency-exchange-service")
public interface CurrencyExchangeServiceProxy {
@GetMapping("/currency-exchange/from/{from}/to/{to}")
CurrencyConversion retrieveExchangeValue(@PathVariable("from") String from, @PathVariable("to") String to);
}
Now we need to add following property in the application.properties of currency-conversion-service
spring.application.name=currency-conversion-service
server.port=8100
currency-exchange-service.ribbon.listOfServers=http://localhost:8000,http://localhost:8001
Need of a naming server
In the previous example we have hardcoded the names of the 2 instances of currency-exchange-service into application.properties of currency-conversion service.
Now if we need to add another instance of currency-exchange-service to ribbon then we need to add the url of that instance in application.properties file of currency-conversion-service.
In order to avoid making hardcoded changes application.properties file we will use Eureka naming server.
Setting up the eureka server
Step 1 : Create Spring boot Gradle project with the dependencies mentioned the image below
Step 2 : Enable the Eureka Server
package com.springcloud.demo.netflixeurekanamingserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class NetflixEurekaNamingServerApplication {
public static void main(String[] args) {
SpringApplication.run(NetflixEurekaNamingServerApplication.class, args);
}
}
Step 3 : Set the following properties value in application properties file for eureka naming server
spring.application.name=netflix-eureka-naming-server
server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
Run the application and hit http://localhost:8761/
You should be able to see UI of Eureka naming server
Connecting currency-conversion-service to eureka naming server
Step 1: Add the following dependency in currency-conversion-service
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
Step 2: Enable Client discovery through annotaion @EnableClientDiscovery
package com.springcloud.demo.currencyconversionservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients(basePackages ={"com.springcloud.demo.currencyconversionservice"} )
@EnableDiscoveryClient
public class CurrencyConversionServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CurrencyConversionServiceApplication.class, args);
}
}
Step 3: Add the eureka server url in application.properties file of currency-conversion-service
spring.application.name=currency-conversion-service
server.port=8100
eureka.client.service-url.default-zone=http://localhost:8761/eureka
currency-exchange-service.ribbon.listOfServers=http://localhost:8000,http://localhost:8001
Now first run the eureka server and then currency-conversion-service. Now hit http://localhost:8761/ you should be able to see that currency-coversion-service is registered as instance in eureka naming server
Connecting currency-exchange-service to Eureka naming server
Step 1: Add the following dependency in currency-exchange-service
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
Step 2: Enable Client discovery through annotaion @EnableClientDiscovery
package com.springcloud.demo.currencyexchangeservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class CurrencyExchangeServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CurrencyExchangeServiceApplication.class, args);
}
}
Step 3: Add the eureka server url in application.properties file of currency-exchange-service
spring.application.name=currency-exchange-service
server.port=8000
eureka.client.service-url.default-zone=http://localhost:8761/eureka
Now run the 2 instances of currency- exchange-service and both the instances should be registered in Eureka naming server
Distributing calls using Eureka and Ribbon
comment line currency-exchange-service.ribbon.listOfServers
in application.properties and now try to run currency-conversion-service to observe the ports form currency-exchange-service
spring.application.name=currency-conversion-service
server.port=8100
eureka.client.service-url.default-zone=http://localhost:8761/eureka
#currency-exchange-service.ribbon.listOfServers=http://localhost:8000,http://localhost:8001
API Gateways
API Gateways are required to intercept calls between microservices
Common Functionality of API Gateways
Setting up Zuul API Gateway
Enable Zuul proxy and Discover Clients
package com.springcloud.demo.netflixzuulapigatewayserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.EnableZuulServer;
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class NetflixZuulApiGatewayServerApplication {
public static void main(String[] args) {
SpringApplication.run(NetflixZuulApiGatewayServerApplication.class, args);
}
}
Values in application.properties for Zuul API Gateway
spring.application.name=netflix-zuul-api-gateway-server
server.port=8765
eureka.client.service-url.default-zone=http://localhost:8761/eureka
Create the following Bean of Zuul Filter as follows :
package com.springcloud.demo.netflixzuulapigatewayserver;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.logging.Logger;
@Component
public class ZuulLoggingFilter extends ZuulFilter {
private Logger logger= Logger.getLogger(this.getClass().toString());
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
//business logic goes here
return true;
}
@Override
public Object run() throws ZuulException {
HttpServletRequest httpServletRequest =
RequestContext.getCurrentContext().getRequest();
logger.info("httpServletRequest>>>"+httpServletRequest);
logger.info("request uri"+httpServletRequest.getRequestURI());
return null;
}
}
Endpoint to hit in order to execute endpoint through API gateway
API-Gateway-App-URL/Application-name/Application-URL
Following is the example where we are hitting endpoint of currency-conversion-service with the help of API Gateway
localhost:8765/currency-conversion-service/currency-converter-feign/from/USD/to/INR/quantity/22000
Setting up Zuul between microservices invocation
In our case we are hitting currency-exchange-service with currency-conversion-service and we want to invoke the API call via API gateway. We need to do the following changes in CurrencyExchangeServiceProxy.java
package com.springcloud.demo.currencyconversionservice;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
//@FeignClient(name = "currency-exchange-service" )
@FeignClient(name = "netflix-zuul-api-gateway-server" )
@RibbonClient(name = "currency-exchange-service")
public interface CurrencyExchangeServiceProxy {
// @GetMapping("/currency-exchange/from/{from}/to/{to}")
@GetMapping("/currency-exchange-service/currency-exchange/from/{from}/to/{to}")
CurrencyConversion retrieveExchangeValue(@PathVariable("from") String from, @PathVariable("to") String to);
}
Sequency in which applications should be run
Now if you hit localhost:8765/currency-conversion-service/currency-converter-feign/from/USD/to/INR/quantity/22000
you will be able to see logs for both currency-conversion-service and currency-exchange service
Git repo links