Service registration & discovery with Spring Cloud Netflix - Eureka

Microservices architecture is not about building individual and isolated services, but instead it aims to make the interactions between services reliable and failure-tolerant. A microservice ecosystem may consist of a high number of services that need to know each other’s location. If we have multiple environments (Dev, QA, Prod ...) then configuring all these services can be very time consuming and error prone. Even in a cloud environment the ip address and port of the services are not known in advance. Based on demand new service instances can be added or removed on the fly.

In this post, we’ll look at how Spring Cloud helps you manage that complexity with a service registry like Eureka.
With Netflix Eureka each client can simultaneously act as a server, to replicate its status to a connected peer. In other words, a client retrieves a list of all connected peers of a service registry and makes all further requests to any other services through a load-balancing algorithm. To keep track about the presence of a client, they have to send a heartbeat signal to the registry.

In this post, we will implement three microservices:

  • a service registry (Eureka Server),
  • a REST service which registers itself at the registry (Eureka Client). It has the role to fetch tweets from twitter API.
  • a web-application, which is consuming the REST service as a registry-aware client (Spring Cloud Netflix Feign Client) and display tweets.

The complete is available on github :)

Eureka Server

Standing up an instance of the Eureka service registry is easy. All you need to do is to add spring-cloud-starter-eureka-server to your dependencies!

  @SpringBootApplication
  @EnableEurekaServer
  public class EurekaServerApplication {

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

The configuration file looks like this:

  server:
    port: ${PORT:8761}

  eureka:
    client:
      registerWithEureka: false
      fetchRegistry: false
      server:
        waitTimeInMsWhenSyncEmpty: 0

The service’s port is defaulted to the well-known 8761 if the PORT environment variable isn’t available. The rest of the configuration simply tells this instance to not register itself with the Eureka instance it finds, because that instance is.. itself. Now we will point our browser to http://localhost:8761 to view the Eureka dashboard, where we will later inspecting the registered instances.

Eureka Client: Twitter Service

This service performs a search on Twitter API based on a keyword and then expose results. I've used Twitter4J to communicate with twitter API. To configure Twitter4J follow this link.

      @RestController
      @RequestMapping("/twitter")
      public class TwitterController {
         Logger logger = LoggerFactory.getLogger(TwitterController.class);

          @RequestMapping(value = "/search/{hashtag}", method = RequestMethod.GET)
          public List<Tweet> searchOnTwitter(@PathVariable String hashtag){
              QueryResult result;
              try {
                  Twitter twitter = TwitterFactory.getSingleton();
                  Query query = new Query(hashtag);
                   result = twitter.search(query);
                   return result.getTweets().stream()
                           .map(status -> new Tweet(status.getText(), status.getUser().getName(),  status.getUser().getScreenName(), status.getUser().getOriginalProfileImageURL(), status.getFavoriteCount(), status.getRetweetCount()))
                           .collect(Collectors.toList());
              } catch (TwitterException e) {
                  logger.error("Ooops", e);
                  throw new SomeException();
              }
          }
      }

Also, we have to include some Spring Discovery Client (for example spring-cloud-starter-eureka) into our Service classpath to be discovery-aware. Then we need to annotate it with either @EnableDiscoveryClient or @EnableEurekaClient. The latter tells Spring Boot to use Spring Netflix Eureka for service discovery explicitly.

  @SpringBootApplication
  @EnableEurekaClient
  public class EurekaClientApplication {

  	public static void main(String[] args) {
  		SpringApplication.run(EurekaClientApplication.class, args);
  	}
  }

Next, we have to set-up an application.yml with a configured Spring application name to uniquely identify our client in the list of registered applications. I specified explicitly the app port, but we can let Spring Boot choose a random port for us because later we are accessing this service with its name, and finally, we have to tell our client, where it has to locate the registry:

    spring:
      application:
        name: twitter-service
    server:
      port: ${PORT:8910}
    eureka:
      client:
        fetchRegistry: true
        registryFetchIntervalSeconds: 5
        serviceUrl:
          defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}
      instance:
        preferIpAddress: true

Now, if you point the browser to http://localhost:8761 again, you'll see the new service status on the Eureka Dashboard.

Feign Client: Web App

Feign is a handy project from Netflix that lets you describe a REST API client declaratively with annotations on an interface. Java developers spend a lot of time writing write boilerplate code for REST clients. Feign offers a solution to dynamically generate clients from the interface. Additional benefit is that we can keep the signature of the service and client identical. Without Feign we would have to autowire an instance of EurekaClient into our controller with which we could receive service-information by service-name as an Application object.

To start using Feign we need to add spring-cloud-starter-feign to our classpath. To enable it, we have to add @EnableFeignClients annotation. To use it, we simply annotate an interface with @FeignClient("service-name") and auto-wire it into a controller. Also, the spring-cloud-starter-eureka package needs to be included in the project and enabled by annotating the main application class with @EnableEurekaClient.

Our Feign Client interface looks like this:

  @FeignClient("twitter-service")
  public interface TwitterClient {
      @RequestMapping(value = "/twitter/search/{hashtag}", method = RequestMethod.GET)
      List<Tweet> getTweets(@PathVariable("hashtag") String hashtag);
  }

The Controller is very basic one, nothing magical:

  @Controller
  public class Twitter {

      @Autowired
      TwitterClient twitterClient;

      @RequestMapping("/{hashtag}")
      public String tweets(@PathVariable String hashtag, Model model){
          model.addAttribute("tweets", twitterClient.getTweets(hashtag));
          model.addAttribute("hashtag", hashtag);
          return "tweets-view";
      }
  }

The application.yml file looks very much like the previous one:

    spring:
      application:
        name: twitter-feign-client
    server:
      port: ${PORT:8765}
    eureka:
      client:
        fetchRegistry: true
        registryFetchIntervalSeconds: 5
        serviceUrl:
          defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}

Now, if you hit http://localhost:8765/{keyword} you should see something like:

That's pretty much it! Please feel free to comment below if you've any suggestion, remark or question. Again, the code is available on github ;)