DEV Community

Marcin Piczkowski
Marcin Piczkowski

Posted on

Spring Cloud step-by-step (part 4)

This is a continuation of previous blog posts:

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:

Spring Cloud system

There are different ways of using service discovery:

  1. With org.springframework.cloud.client.discovery.DiscoveryClient
  2. With client-side load balancer called Ribbon
  3. 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.

Top comments (0)