This tutorial is the continuation of the previous tutorial of Spring Boot Microservices Tutorial (Part 1)
Custom Routes in API Gateway :
One of the option to create custom routes is by creating a configuration file. Lets create a new class: ApiGatewayConfiguration
@Configuration public class ApiGatewayConfiguration { @Bean public RouteLocator gatewayRouter(RouteLocatorBuilder builder) { Function<PredicateSpec, Buildable<Route>> routeFunction = p -> p.path("/get").uri("http://httpbin.org/"); //any working URL. return builder.routes() .route(routeFunction) .build(); } }
Now we will customize our earlier URLs.
Function<PredicateSpec, Buildable<Route>> routeFunction2 = p -> p.path("/get").uri("http://heapsteep.com"); //any working URL. Function<PredicateSpec, Buildable<Route>> routeFunction3 = p -> p.path("/microservice-2/**").uri("lb://microservice-2"); Function<PredicateSpec, Buildable<Route>> routeFunction4 = p -> p.path("/microservice-2-feign/**").uri("lb://microservice-2");
Not only I am redirecting to a particular naming server, we are also load balancing it.
I will comment out the below 2 lines in application.properties :
#spring.cloud.gateway.discovery.locator.enabled=true #spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
So, for the below URL:
http://localhost:8765/microservice-2/microservice-2/from/USD/to/INR/quantity/10
the new URL would be:
http://localhost:8765/microservice-2/from/USD/to/INR/quantity/10
And for below URL:
http://localhost:8765/microservice-2/microservice-2-feign/from/USD/to/INR/quantity/10
the new URL would be:
http://localhost:8765/microservice-2-feign/from/USD/to/INR/quantity/10
Spring Cloud Sleuth :
It helps in assigning a TraceId and SpanId to each request.
Lets do a hands-on. Create a project having below dependency:
* Web
* Spring Cloud Sleuth
Below lines will be automatically added for Spring Cloud Sleuth in the pom.xml:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency>
Lets create a REST method:
@RestController public class MyController { private static final Logger logger=LoggerFactory.getLogger(TestController.class); @GetMapping("/service1") public String service1() { logger.info("service1 called ...."); return "service1..."; } }
And create a configuration class:
@Configuration public class CloudConfig { @Bean public Sampler defaultSampler() { return Sampler.ALWAYS_SAMPLE; } }
That’s all…
Just call any url from a browser: http://localhost:8080/service1
In the log one can see below text:
2020-07-23 19:36:18.414 INFO [, 4bfc21f0583bc3e9 , 4bfc21f0583bc3e9 ,true]
In the above text one can see 4 parameters- the 1st one is blank, the 2nd and 3rd are some alpha numeric value and 4th one is a boolean value. so those values are basically :
[application-name, traceId, spanId, export flag]
Since I had not mentioned the application name in application.properties file, its coming as blank. If you had mentioned the same then you may see like below:
2020-07-23 19:36:18.414 INFO [MyApplication_1, 4bfc21f0583bc3e9 , 4bfc21f0583bc3e9,true]
Now lets do a change….
Lets call this service from another service. For simplicity, here we will not call from another microservice application, rather we will give a call from another API endpoint.
So, add another method – service2() as shown below in the code:
@RestController public class MyController { private static final Logger logger=LoggerFactory.getLogger(TestController.class); @Autowired private RestTemplate restTemplate; @GetMapping("/service1") public String service1() { logger.info("service1 called ...."); return "service1..."; } @GetMapping("/service2") public String service2() { logger.info("service2 called ...."); return restTemplate.getForObject("http://localhost:8080/service1", String.class); } }
Some more changes:
@Configuration public class CloudConfig { @Bean public RestTemplate template() { return new RestTemplate(); } @Bean public Sampler defaultSampler() { return Sampler.ALWAYS_SAMPLE; } }
Once you run the application and call the service-2, you will see below things in the console log:
INFO [, 2cc8cdabafe90344 , 2cc8cdabafe90344 , true] 17444 service1 called ….
INFO [, 2cc8cdabafe90344 , 2723470714fe6043 , true] 17444 service2 called ….
If you see the SpanId of the first call becomes the TraceId of the 2nd call.
The above logs are now visible in the logs of local machine. Lets go a step further to post these Ids to a server called – Zipkin.
The above source can be found at : https://github.com/heapsteep/spring-cloud-sleuth.git
Zipkin :
It is the server for distributed tracing system.
You need to download the zipkin jar from the official site.
Then just run it wherever you want (even in download folder as well):
jar -jar zipkin.jar
To verify whether Zipkin is running call the below URL:
http://localhost:9411
One can see the home page of the Zipkin server.
To add the call traces of your microservices to the Zipkin server, you need to add Zipkin Client dependency to the same microservice.
So, add below dependency from Spring Initializr:
After adding the above dependency one can see below item in pom.xml:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
Add below item in application.properties:
spring.zipkin.base-url=http://localhost:9411
call the service from browser: http://localhost:8080/service2
You can see the traces in Zipkin dashboard.
That’s all !…
Feign :
Feign is a REST Service Client. Generally when we have to call a microservice from another microservice we take help of RestTemplate. But using a RestTemplate make it kinda hardcoding and we have to write a lot of stuff. We can avoid that by using Feign.
Lets see an example.
Suppose there is a microservice called – book-service. And it has below APIs:
http://localhost:8000/api/getAllBooks
http://localhost:8000/api/getBookDetail/1
You can check out the source code of book-service application here: https://github.com/heapsteep/book-service.git
You may find some code commented in the repository, just un-comment it to test it.
Now if we want to call the above book-service from customer-service, we will first use RestTemplate as show below:
The controller:
@RestController public class CustomerController { @GetMapping("/getBookDetail") public BookInfo getBookDetail() { ResponseEntity responseEntity= new RestTemplate().getForEntity("http://localhost:8000/api/getBookDetail/1", BookInfo.class); BookInfo bookInfo=(BookInfo)responseEntity.getBody(); return bookInfo; }
@Data @Setter @Getter public class BookInfo { private String bookTitle; private int port; }
This customer-service runs on port: 8100.
Call this URL to see the result: http://localhost:8100/getBookDetailFeign/1
In the above example we can replace it with Feign.
Lets follow the steps to enable Feign in customer-service. You don’t have to do anything in the book-service, unless and until you have to call another service from book-service.
- Add below Feign dependency. Feign dependencies are not available in Spring Initializer. You need to manually add it.
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
2. In main class add this: @EnableFeignClients(“com.heapsteep”)
3. Create a proxy interface. The name is the application you are supposed to call- this name you will find from application.properties. Give the signature and GetMapping parameters of the method same as of the calling method.
@FeignClient(name="book-service", url="localhost:8000") public interface BookServiceProxy{ @GetMapping("/api/getBookDetail/{id}") public BookInfo getBookDetail(@PathVariable Long id); }
4. In the controller add below method:
@GetMapping("/getBookDetailFeign/{id}") public BookInfo getBookDetailFeign(@PathVariable("id") Long id) { return proxy.getBookDetail(id); }
Thats it….
customer-service source code can be found here: https://github.com/heapsteep/customer-service.git
Netflix Ribbon :
Ribbon is used for client side Load Balancing.
In Springboot 2.4 version and beyond, Ribbon is not required. Only using Feign will do load balancing. Just register the microservices in Naming Server, rest feign will take care. Basically the spring-cloud-starter-loadbalancer is present in the Eureka Client.
Ribbon has to be used with Feign, why because if you are using RestTemplate you are hard coding the port in the call, so you can’t see the effect of Ribbon. So, before proceeding further you should have idea about Feign.
Below are the steps to add Ribbon to your calling application(the client):
- Add Ribbon dependency from spring Initializer.
- In application.propperties add the 2 instances of the server application: (make sure 2 server application is running)
Thats it…
As usual, call this URL: http://localhost:8100/getBookDetailFeign/1
We can see that the Ribbon is distributing load between 8000 and 8001 port.
Customer-service source code:
The controller:
@RestController public class CustomerController { @Autowired private BookServiceProxy proxy; @GetMapping("/getBookDetailFeign/{id}") public BookInfo getBookDetailFeign(@PathVariable("id") Long id) { return proxy.getBookDetail(id); } }
The model: BookInfo.java
@Data @Setter @Getter public class BookInfo { private String bookTitle; private int port; }
The proxy:
@FeignClient(name="book-service") public interface BookServiceProxy { @GetMapping("/api/getBookDetail/{id}") public BookInfo getBookDetail(@PathVariable Long id); }
application.properties:
service-name.ribbon.listOfServers=http://localhost:8000,http://localhost:8001
Netflix Eureka Naming Server :
Eureka Naming Server is used to register different services.
Lets do a demo of it.
Create a new spring boot project. Add “Eureka Server” from Spring Initializer.
Add @EnableEurekaServer to the main class.
In application.properties:
spring.application.name=eureka-naming-server server.port=8761 eureka.client.register-with-eureka=false eureka.client.fetch-registry=false
Below changes to be done in other microservices:
Add this dependency in pom.xml: Eureka Discovery Client
P.S. : Whenever you are adding any “Cloud” specific dependency, don’t forget to add below items as well:
<spring-cloud.version> Hoxton.SR7</spring-cloud.version> <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>
Add in main class: @EnableDiscoveryClient
eureka.client.service-url.default-zone=http://localhost:8761/eureka
Disable this:
#springboot-jpa-mysql.ribbon.listOfServers=http://localhost:8000,http://localhost:8001
Here is the source code of the eureka-naming-server application in GitHub:
https://github.com/heapsteep/eureka-naming-server.git