One of the standard problems with Microservices Architecture is the issue of service discovery. Once we’ve decomposed our application into more than a handful of distinct microservices, it becomes difficult for every service to know the address of every other service it depends on. Configuring dependencies from inside a microservice is impractical – it distributes configuration among all the microservices. It also violates the DRY principle – multiple microservice instances will need access to the same configuration settings. What’s more, it goes against the Dependency Injection design that’s supposed to be one of the benefits of the Microservices Architecture.
The standard solution is to delegate location of microservices to a new microservice. In keeping with the Single Responsibility Principle, this ‘discovery’ microservice is responsible for tracking the locations of all the other microservices and nothing else.
Netflix’s Eureka is an implementation of a discovery server and integration is provided by Spring Boot. Using Spring Boot, we can build a Eureka discovery server and have our microservices register with it.
The code for the following example can be downloaded from the Spanners demo app, version 4.4 on GitHub. The full stack exists as Docker images at DockerHub and can be started with this docker-compose file.
Building a Eureka server
Spring Boot’s opinionated design makes it easy to create a Eureka server just by annotating the entry point class with @EnableEurekaServer:
@SpringBootApplication @EnableEurekaServer public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
So long as the spring-cloud-starter-eureka-server dependency is present in the Maven / Gradle build config, this will start the application as a Eureka server. The starter dependency is part of the Spring Cloud project so you’ll want to use the latest Spring Cloud release train (currently Brixton.SR5) to manage your Maven dependency versions:
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Brixton.SR5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Spring’s Getting Started with Service Registration and Discovery describes how the same can be managed with Gradle.
Some basic configuration is required in the application.properties (or application.yml) file:
# Not a client, don't register with yourself eureka.client.registerWithEureka: false eureka.client.fetchRegistry: false server.port=8761
When this Eureka server starts, it will listen for registrations on port 8761. When our microservices start, they’ll make a call to Eureka to register themselves. Then they can query Eureka to find other registered servers. Eureka also provides a simple status console which can be viewed on http://localhost:8761.
This console shows that Eureka is running and that no instances are currently registered.
Registering services with Eureka
Now that we have a centralized discovery server, we need every service to register with it. This is about as easy as creating the server. First, we need to add the spring-cloud-starter-eureka dependency to Maven / Gradle. Again, use the spring-cloud release train to manage versions:
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Brixton.SR5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Then, enable discovery with the @EnableDiscoveryClient annotation on a @Configuration class or the @SpringBootApplication entry point class:
@Configuration @EnableDiscoveryClient public class RestConfig { // Application beans configured here }
And finally, add a couple of configuration settings to the application.properties (or application.yml) config file. This tells the application where Eureka is and how this service should be named in Eureka:
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/ spring.application.name=spanners-api
Now, when we start the spanners-api service, we can see it registered in Eureka:
Querying Eureka
In my application, our front end component (spanners-mvc) depends on two back end microservices (spanners-api and spanners-users). Instead of hard coding the locations of the two microservices in our front end component, we want it to ask Eureka. To do this we first follow the steps above to register the spanners-mvc component with Eureka. Just to check this has worked, we can look at the Eureka console to confirm that spanners-mvc and the two microservices are all registered:
Now, we can refer to the back end microservices by using their names rather than their server addresses. The current examples from Spring suggests something like this:
@Service public class WebAccountsService { @Autowired @LoadBalanced protected RestTemplate restTemplate; protected String serviceUrl = "http://ACCOUNTS-SERVICE"; // ACCOUNTS-SERVICE is the name of the microservice we're calling public Account getByNumber(String accountNumber) { Account account = restTemplate.getForObject(serviceUrl + "/accounts/{number}", Account.class, accountNumber); if (account == null) throw new AccountNotFoundException(accountNumber); else return account; } ... }
The @LoadBalanced annotated RestTemplate will resolve application names (ACCOUNTS-SERVICE) to a real server name / port by querying Eureka. The @LoadBalanced annotation tells Spring Boot to customize the RestTemplate with a ClientHttpRequestFactory that does a Eureka lookup before making the HTTP call. To make this work, you’ll need to add a new config setting to application.properties:
ribbon.http.client.enabled=true
This just switches on the Ribbon load balancing behind the @LoadBalanced annotation.
If you’re interested, RibbonAutoConfiguration does the customization of the RestTemplate and RibbonClientHttpRequestFactory does the Eureka lookup. This is all done for us, just by adding the @LoadBalanced annotation to a RestTemplate bean.
Eureka with Spring Boot 1.4 RestTemplateBuilder
As of Spring Boot 1.4, it’s no longer recommended to directly @Autowire an instance of RestTemplate into a Rest client class. Instead, we can use the RestTemplateBuilder to give us some more flexibility in configuring the RestTemplate. It also allows us to use the new @RestClientTest annotation to test Rest clients. More details on the advantages of the new RestTemplateBuilder in my previous post on the subject.
If we don’t @Autowire a RestTemplate bean though, we can’t use the @LoadBalanced annotation to customize the RestTemplate for Eureka lookups. At the time of writing, Spring has no built in solution for this as the current Spring Cloud release train (Brixton) was built for Spring Boot 1.3 – without RestTemplateBuilder support. If you want to use the RestTemplateBuilder with Eureka, you’ll need to customize the RestTemplate yourself. This is very straightforward:
@Configuration @ConditionalOnClass(HttpRequest.class) @ConditionalOnProperty(value = "ribbon.http.client.enabled", matchIfMissing = false) public class RestClientConfig { /** * Customize the RestTemplate to use Ribbon load balancer to resolve service endpoints */ @Bean public RestTemplateCustomizer ribbonClientRestTemplateCustomizer( final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) { return new RestTemplateCustomizer() { @Override public void customize(RestTemplate restTemplate) { restTemplate.setRequestFactory(ribbonClientHttpRequestFactory); } }; } }
This is pretty much a copy of the RibbonAutoConfiguration.RibbonClientConfig bean used to power the @LoadBalanced annotation except that it configures an instance of RestTemplateCustomizer. The RestTemplateBuilder automatically pulls in all configured RestTemplateCustomizers when it initializes so we don’t need to manually inject this into anything.
The conditional annotations on this configuration class create the RestTemplateCustomizer only if Ribbon (and Eureka) is enabled.
With this bean present, we can use the RestTemplateBuilder in our Rest client code and it will resolve our application names, just as if we had use the @LoadBalanced annotation.
Very informative article! Thank you for writing it.
You say that “As of Spring Boot 1.4, it’s no longer recommended to directly @Autowire an instance of RestTemplate into a Rest client class.”
Can you provide a source for this recommendation?
Ah, I think I’ve found my answer. It’s “not recommended” because “A RestTemplate bean is no longer created via auto configuration. It must be created by individual applications.”
http://cloud.spring.io/spring-cloud-static/spring-cloud.html#_spring_resttemplate_as_a_load_balancer_client
However, you do go on to mention how instead you can use RestTemplateBuilder. Instead of doing that, why not just do what the spring docs recommend, and do something like this in a @Configuration class?
@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
There’s nothing at all wrong with doing that. I chose to keep the RestTemplateBuilder as it ‘s a little easier to customize and to test. See my previous post on this: https://www.dontpanicblog.co.uk/2016/07/16/resttemplatebuilder-and-restclienttest-in-spring-boot-1-4-0/
The disadvantage though is that you can’t (yet) just stick a @LoadBalanced annotation on it and expect it to work with Eureka. So if you don’t need to customize your builder and are happy to test without the nice @RestClientTest stuff introduced in Spring Boot 1.4, you are indeed best sticking with a simple @LoadBalanced RestTemplate.
Hi Stuart,
I am new to this microservices and eureka configuration.
Can you please help me by sharing some sample code or link from where i can get understanding reference for creating structure with spring boot and eureka without docker.
As even i am newer to docker.
But for now as per the requirement i need to create a structure with spring boot fusing microservices and eureka.
Please help me.It would be really great if you give some solution.
Thanks,
Arpit Thakkar
Hi Arpit
So glad to hear you’re looking at Microservices and Eureka! The sample code at Spanners Demo App, version 4.4 was built with Docker but should work just fine without it. Alternatively, I found the Getting Started guide at Spring.io very helpful.
Finally, I found Josh Long’s Cloud Native Java presentation from SpringOne Platform 2016 excellent. It’s worth watching the whole thing but he talks about Eureka specifically from around 33:00.
Good luck!
Thanks for your informative article.
I have one question. When we have more than one Eureka server instance running for scalability and failover reasons, how will the client make a lookup to the Eureka via the Resttemplate.
Since there are more than one Eureka Server instances how does the client decide which one. Does Ribbon does the load balancing for that too similar to the way it does for target service being consumed?
Is there any way to hide or restrict the Eureka server status page to outside world ?
I would expect that you’d want to protect your Eureka server at the network / firewall level. If you’re on a traditional LAN, you’d put it on an internal (not internet facing) server. If you’re in a cloud infrastructure such as AWS, you’d put it on an internal security group.
If you need this to be on a server exposed to the outside world, you may be able to use Spring Security to restrict access. If you’re using Spring Boot, add the spring-boot-starter-security starter and you can then use all the Spring Security features. You could restrict the status page using a simple username / password or you could restrict by other means – for example by IP address.
I have one Question ?
If i want to do Integration Testing of The Controller present in your Account-Service using @SpringBootTest ,then is there is need of registration of Account-Service to Discovery Server for Integration Testing ??
Crystal clear write-up!