This is a continuation of previous blog posts:
- Spring Cloud step-by-step (part 1)
- Spring Cloud step-by-step (part 2)
- Spring Cloud step-by-step (part 3)
This time I will show you how to use Eureka discovery client to query other services without knowing their URLs.
In previous post we’ve seen how to configure Spring Cloud service discovery.
We used this for discovery of Config Server. It was handled automagically by Spring, but we have not seen how to call other services manually from java code (e.g. with RestTemplate).
Let’s add another microservice which will use Pricing Service to get the price which user has to pay for renting an item.
Let’s call it Rental Service.
It will have an API for renting an item for particular period and will return a price for rental. The API will work this way:
curl -H 'Content-Type:application/json' http://localhost/v1/rentals/{id}/charges
Where {id} is an id of rental. We will not create an API to create rentals for now. To demonstrate calling another service we will just pass any {id} and the API will respond with hard-coded value, like:
{"price" : "1231.00"}
Our Spring Cloud system will now consist of the services as in the image below:
There are different ways of using service discovery:
- With
org.springframework.cloud.client.discovery.DiscoveryClient
- With client-side load balancer called Ribbon
- With Feign REST client
In this post I will concentrate on the first example and show the others in separate posts.
Using EurekaClient API
com.netflix.discovery.EurekaClient
is an implementation of DiscoveryClient
by Netflix.
In the new service which we can create similarly we created other Spring Cloud services, we will add new dependency in pom.xml
:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.2.RELEASE</version>
</dependency>
We also need to add annotation on main class: @EnableEurekaClient
.
Finally, some configuration to let Eureka client know where discovery server is - in bootsrap.yaml
add:
eureka:
client:
serviceUrl:
defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}
instance:
preferIpAddress: true
This means we expect server at http://localhost:8761/eureka
but it can be also resolved from an environment variable EUREKA_URI
e.g. in production environment.
When to use preferIpAddress
property? When we want our application to register in Eureka server by IP and not hostname (see explanation here).
Now we can use EurekaClient
in new controller, e.g we could list all available services like this:
import com.example.rental.rentalservice.v1.dto.RentalChargesResponse;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.shared.Application;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.Iterator;
import java.util.List;
@RestController
@RequestMapping(path = "/v1")
public class RentalController {
private final EurekaClient eurekaClient;
public RentalController(EurekaClient eurekaClient) {
this.eurekaClient = eurekaClient;
}
@RequestMapping(path = "/rentals/{id}/charges", method = RequestMethod.GET)
public ResponseEntity getPossibleCharges(@PathVariable("id") String id) {
eurekaClient.getApplications();
Iterator i = eurekaClient.getApplications().getRegisteredApplications().iterator();
while (i.hasNext()) {
Application app = (Application) i.next();
System.out.println(app.getName());
}
return new ResponseEntity<>(new RentalChargesResponse(BigDecimal.ZERO), HttpStatus.OK);
}
}
When we start application and invoke endpoint with
curl localhost:8080/v1/rentals/123/charges
we would see in application logs
PRICING-SERVICE
CONFIG-SERVICE
RENTAL-SERVICE
So it returned also information about itself (RENTAL-SERVICE
). There is only one instance of each service, but in real system there would be more, because we would like to support some fault tolerance and scale application.
I leave trying what else we could get from EurekaClient
APIs to you, but let’s finally see how to query the Pricing Service.
Here we go:
@RequestMapping(path = "/rentals/{id}/charges", method = RequestMethod.GET)
public ResponseEntity getPossibleCharges(@PathVariable("id") String id) {
InstanceInfo instance = eurekaClient.getNextServerFromEureka("pricing-service", false);
ZonedDateTime startTime = ZonedDateTime.now();
ZonedDateTime endTime = startTime.plus(2, ChronoUnit.DAYS);
HttpEntity<PriceRequest> request = new HttpEntity<>(
new PriceRequest("vip", startTime, endTime)
);
PriceResponse response = restTemplate.postForObject(
instance.getHomePageUrl() + "/v1/prices", request, PriceResponse.class);
return new ResponseEntity<>(new RentalChargesResponse(response.getPrice()), HttpStatus.OK);
}
In the code above we are hardcoding input parameters for Pricing Service. We should first get the rental and extract these values from it, but since we do not have yet API to create rentals this is a shortcut.
In the next post I will show you a simplier way to invoke endpoint using Eureka with Ribbon.
comments powered by Disqus