Spring Cloud : Service discovery with Eureka

When we use microservices, we have typically a large number of services that are interrelated to each other. So the problem is how can one application easily find all of the other runtime dependencies. You could maually configure like giving a harcoded url in some configuration file but this seems impracticle when you are in cloud where you have a large number of service that could run anywhere and on any port.
Service dicovery provides a single ‘lookup’ service. Clients registers themselves and discover other clients. Common service dicovery clients are Eureka, Consul, Zookeeper etc.

Eureka provides a ‘lookupserver. Its generally made higly available by running multiple copies. Multipliple copies replicate state of registered services. Client services register themself with Eureka. They provide metadata on host, port, health indicator URL, etc. Client services send hearbeats to Eureka to let the server know that its up. Eureka remove services without heartbeats.

Lets create a Eureka server. Its just a spring boot application. pom.xml:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.2.RELEASE</version>
<relativePath />
</parent>

<groupId>com.mynotes.spring.cloud</groupId>
<artifactId>eureka-server-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>eureka-server-demo</name>
<description>Demo project for Spring Cloud cloud eureka</description>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
</project>

Notice that we have only one dependency for eureka server.
Now the starting class EurekaServerApplication.java


package com.mynotes.spring.cloud.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

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

}

We just have @EnableEurekaServer  to tell that this application will be discovery server. Lets look into some of the configuration we use in application.yml:


server:
  port: 8761

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

Above file we mentioned the eureka server port. Eureka can register to other eureka server but we dont want this behaviour here, hence we kept it false. Lets run it and check in browser

spring-eureka1

Now Lets create a user-service and register it to Eureka. pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.2.RELEASE</version>
<relativePath />
</parent>

<groupId>com.mynotes.spring.cloud</groupId>
<artifactId>eureka-user-service</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>eureka-user-service</name>
<description>Demo project for Spring Cloud cloud eureka-user-service</description>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

As you can see we have the same dependency plus the starter web since its going to be a rest service. Lets write the entry point UserServiceApplication.java.


package com.mynotes.spring.cloud.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {

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

}

Just a simple@EnableDiscoveryClient annotation will make this service register itself with eureka. You will find@EnableEurekaClient too but its good to use the generic versions as you may want to swtich to a different version of Discovery client down the line.
Lets write a simple controller UserServiceRestController.java


package com.mynotes.spring.cloud.eureka;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/users")
public class UserServiceRestController {

@RequestMapping(value = "/getPublicMailingAddress", method = RequestMethod.GET)
@ResponseBody
public String getContact() {
return "Public mail Address";
}

}

application.yml to set the port and eureka registry url. Its higly recommneded that these properties comes from a seperate config service. Since its just a demo I am writing it here.


server:
  port: 8090

eureka:
  client:
    serviceUrl:
    defaultZone: http://localhost:8761/eureka/

bootstrap.yml for application name and config service is using:


spring:
  application:
    name: user-service

Running and checking eureka :

spring-eureka2

Now lets another service contact-service that uses user-service for some data. It will also register itself in the same way as user-service so the pom.xml, application.yml, bootstrap.yml, Application.java are same except the service/project names. Lets focus on the controller where it will call the user-service. ContactUsController.java:


package com.mynotes.spring.cloud.eureka;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/contactUs")
public class ContactUsController {

@Autowired
DiscoveryClient client;

RestTemplate rest=new RestTemplate();

@RequestMapping(value = "/getMailingAddress", method = RequestMethod.GET)
@ResponseBody
public String getContactUsDetails() {
List<ServiceInstance> serviceList=client.getInstances("user-service");
if(serviceList!=null && serviceList.size()>0){
System.out.println("Sevice list===>"+serviceList.size());
String result=rest.getForObject(serviceList.get(0).getUri()+"/users/getPublicMailingAddress", String.class);
return "Contact Address ==> "+ result;
}
return "Error: Please Try again later";
}

}

Notice that we Autowired the DiscoveryClient which will be injected by the spring and will have the list of all the registered clients in Eureka. We can then find the client by name and get the URI. Thus we do not have to take care of what the hostname and ports are for the user-service. Ofcourse we must know the actual endpoint of the rest service. Running the application and checking Eureka

spring-eureka3

Hitting the contact service enpoint:

spring-eureka4

Now, as I told you that its recommended that all the properties comes from a config-server, so which will come first if we have both.
The default is the Config first Bootstrap. This means that config server is used to configure location of eureka server. This means we have to have a spring.cloud.config.uri configured in each apps bootstrap.yml.
Another is Eureka first bootstrap. Here we use eureka to expose the config server. Config server is just  client. This means we must have a spring.clound.config.discovery.enabled=true and eureka.client.serviceUrl.defaultZone configured in each app. Clients here makes two network trips to obtain configuration.

Thats it for Eureka. You can get the whole code in my github repo.

Advertisements
%d bloggers like this: