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 ;)