Spring Cloud: Service Discovery with Eureka

Overview

In this article, we'll get introduced to client-side service discovery and load balancing via Spring Cloud Netflix Eureka.

In a typical microservice architecture we have many small applications deployed separately and they often need to communicate with each other. Specifically, when we say client service, we mean a service that needs to make REST calls to some other end service.

The problem in this type of architecture is how the client service finds all of its end services. We could hardcode the hostname/port in some property file, but this isn't always practical or feasible in a cloud environment. There could be any number of microservices, and it's time and resource-consuming to hard-code when there's an uncertain amount of them, and when their locations may change.

To further add to the complexity, services could have multiple instances of themselves (based on the load). Which instance will actually serve the response could be challenging as we want to have equal load distribution.

Netflix Eureka

Netflix Eureka is a lookup server (also called a registry). All the microservices in the cluster register themselves to this server.

When making a REST call to another service, instead of providing a hostname and port, they just provide the service name.

The actual routing is done at runtime along with equally distributing the load among the end services. There are other service discovery clients like Consul, Zookeeper etc, but we will be using Eureka in this article.

To understand this concept we will be building three services in our example:

  • Eureka Server: acts as a service registry.
  • Movie Service: a simple REST service that provides movie information.
  • Recommendation Service: a simple REST service but it internally calls the Movie Service to complete its requests.

Eureka Server Setup

The best way to start with a skeleton project is to use Spring Initializr. Select your preferred version of Spring Boot and add the "Eureka Server" dependency and generate as a Maven project:

spring-eureka-spring-initializer

To make a Eureka server, all we need to do is add the @EnableEurekaServer annotation to our main class:

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

We will be running the server on port 8761, which is the recommended port by the Spring team. So in application.properties we'll add:

server.port = 8761

To test this endpoint, navigate your browser to http://localhost:8761/:

spring-eureka-server-display

End Service Setup (Movie Service)

Again, we are using Spring Initializr to create our project. Select your preferred version of Spring Boot and add the "Web" and "Eureka Discovery" dependencies and generate as a Maven project:

spring-eureka-movie-service-init

To make this a client, all we need to do is add the @EnableEurekaClient annotation on the class level:

@SpringBootApplication
@EnableEurekaClient
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

As an alternative, we could use the @EnableDiscoveryClient annotation, which comes from spring-cloud-commons. It picks the implementation (Consul, Zookeeper, Eureka) according to the classpath. In our case, it would automatically choose Eureka.

With that out of the way, we can define our Movie model:

public class Movie {
    private Integer id;

    private String name;

    private String synopsis;

    // getters and setters
}

And finally, we can define a controller:

@RestController
public class MovieController {

    private static List<Movie> movieList = new ArrayList<>();
    static {
        movieList.add(new Movie(1, "movie-1", "summary-1"));
        movieList.add(new Movie(2, "movie-2", "summary-2"));
        movieList.add(new Movie(3, "movie-3", "summary-3"));
    }

    @GetMapping("/movies")
    public ResponseEntity<?> getMovies() {
        return ResponseEntity.ok(movieList);
    }
}

Above, we created a simple endpoint that returns a list of Movie objects, simulating a call to a database.

We have the Eureka Discovery client jar in this project. Upon seeing this in the classpath, Spring will attempt to find a running Spring Eureka server. We need to specify this in the application.properties:

server.port = 8060
spring.application.name = movie-service
eureka.client.serviceUrl.defaultZone = http://localhost:8761/eureka/

Along with specifying the URL of the server, we also need to specify the application name. It will be this name that other service use for making REST calls.

With that, we've registered our service to the server, and any other registered service can call it using spring.application.name.

Let's refresh out Eureka server endpoint:

spring-eureka-movie-service-registers

Along with the registration with Eureka server, the Eureka client jar is also sending periodic heartbeats to Eureka server to let it know that it's still available.

Client Service Setup (Recommendation Service)

Now lets build our client service that will call movie-service. Once again let's create a new project with Spring Initializr with the same dependencies as earlier:

spring-eureka-recommendation-service-init

In its application.properties we again have to specify application name and Eureka details:

server.port = 8050
spring.application.name = recommendation-service
eureka.client.serviceUrl.defaultZone = http://localhost:8761/eureka/

Then we annotate the main class with @EnableEurekaClient:

@SpringBootApplication
@EnableEurekaClient
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    @LoadBalanced
    @Bean
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

We also need to create a RestTemplate bean and mark it as @LoadBalanced. This tells Spring that we want to take advantage of client-side load balancing, which is in this case done by Ribbon.

Client-side load balancing decides which instance (in case of multiple end service running in the cluster that client can call) to call.

Ribbon was developed by Netflix and later open sourced. It's dependency automatically comes with the Eureka Discovery dependency. It automatically integrates with Spring and distributes loads based on server health, performance, region, etc.

We won't be required to use Ribbon directly as it automatically integrates RestTemplate, Zuul, Feign, etc. Using @LoadBalanced we made RestTemplate ribbon aware.

Let's write RecommendationController class that internally calls our movie service:

@RestController
public class RecommendationController {
    @Autowired
    RestTemplate restTemplate;

    @RequestMapping(value = "/recommendations", method = RequestMethod.GET)
    @ResponseBody
    public Movie[] recommendations() {
        Movie[] result = restTemplate.getForObject("http://movie-service/movies", Movie[].class);
        return result;
    }
}

Above, we @Autowired the Ribbon-enabled RestTemplate and use it to call the movie-service. Note that we do not have to specify the hostname or the port anywhere.

What Spring internally does here is because it's registered with the Eureka server, it stores the list of all the services and its running instances locally. When we made a REST call to the movie-service like this (instead of providing a hostname and port), it substitutes the actual endpoint URLs from the previously stored list and then makes the REST call.

Of course, the stored list of service along with its running instances is periodically updated. The best part of all this is that we don't have to take care of this at all and all these things are handled by Spring internally.

Let's test the recommendation endpoint, navigate your browser (or use curl, postman, etc.) to http://localhost:8050/recommendations, you will see a response that looks something like:

spring-eureka-recommendation-service-result

Conclusion

In this article, we've covered how to use Spring Cloud Eureka for service discovery in the microservice/cloud environment. We created two simple REST services that communicate with each other without hardcoding any hostname/port while making REST calls.

As always, the code for the examples used in this article can be found on Github.