Friday, July 28, 2023

Spring Boot Microservice - Service Registration and Discovery With Eureka

In this article we'll see how to do service registration and discovery using the Netflix Eureka service registry in Spring Boot microservices development.

What is service registration and discovery

In the post Spring Boot Microservices Example we have seen how to have interservice communication using RestTemplate.

ResponseEntity<List<AccountDto>> response = restTemplate.exchange("http://localhost:8082/account/customer/"+id, HttpMethod.GET, null, new ParameterizedTypeReference<List<AccountDto>>(){});

But the problem here is the hardcoding of IP address and port.

To communicate with other service, you do need to know where it is deployed and the port it is listening to but at the same time number of instances of Microservice and their locations are dynamic. Therefore, it is not at all advisable to hardcode this data.

Hardcoding the location and port has the following side effects-

  1. If URL changes that would mean change inside the code.
  2. You are coupling with a specific instance of the microservice.
  3. If you are tightly coupled with a specific instance of the microservice that means no load balancing too.
  4. With microservices deployed in cloud trying to hardcode instances is an even bigger problem.

Now, when we have established that hardcoding location of the microservice instances is not good question arise how will I know about the location of any microservice instance.

What if there is a tool that can keep track of all the service instances and inform about their current location when inquired. That's what SpringCloud Netflix Eureka brings to the table.

Using Eureka for Service registration and discovery

SpringCloud Netflix Eureka provides functionality to act as a service registry. Each microservice registers itself with the service registry which means providing data about its location- host name, port number, node name and any other data. Microservice registers itself to the service registry under the name you specify in the spring.application.name property.

Since Eureka service registry has the location information of each microservice instance, any microservice which needs to communicate with other services can use Eureka as service discovery to discover location information about the microservices.

Here note that SpringCloud Netflix Eureka provides client-side discovery implementation. In client-side discovery, client (service consumer) has the responsibility of determining the network location of other services. Client queries the service registry (Eureka server) to get that information, receives it from the registry and then send request directly to the microservice.

Eureka service discovery

The steps in the image can be explained as-

  1. Microservices register itself with Eureka service registry.
  2. Service consumer wishes to communicate with Microservice-1 so it sends a request to Eureka to get location information of Microservice-1.
  3. Server sends back a response to service consumer with required information.
  4. Client may also use load-balancing to choose one of the available service instances and performs a request directly.

Spring Boot Microservice with Eureka example

We'll use the example used in Spring Boot Microservices Example as our base example and make changes to it to include Eureka for Service registration and discovery.

Setting up the Eureka server

To setup Eureka server we'll create a new Spring Boot project with eureka server starter as dependency.

In STS create a new Spring Starter Project and give details as given here.

Spring Boot STS

Select "Eureka Server" as dependency under "Spring Cloud Discovery". With that the created pom.xml should look like this-

<?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 https://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>3.1.2</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.netjstech</groupId>
  <artifactId>discoveryservice</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>eureka-discovery-service</name>
  <description>Eureka Discovery Service</description>
  <properties>
    <java.version>17</java.version>
    <spring-cloud.version>2022.0.3</spring-cloud.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>
In the properties you can notice the cloud version used for this example
<spring-cloud.version>2022.0.3</spring-cloud.version>

Adding configuration

In src/main/resources create a application.yml file and the following properties.

server:
  port:
    8761

spring:
  application:
    name:
      discovery-service
      
eureka:
  client:
    register-with-eureka:
      false
    fetch-registry:
      false
  instance:
    hostname: localhost  

By default, the registry also tries to register itself, so you need to disable that behavior by giving the following two properties-

eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

I am running Eureka on the same system so instance is localhost and the port Eureka server listens to is 8761.

Adding @EnableEurekaServer annotation

In the application class add @EnableEurekaServer annotation along with @SpringBootApplication which tells Spring to configure this service as a Eureka server which can act as a service registry and discovery.

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

@EnableEurekaServer
@SpringBootApplication
public class EurekaDiscoveryServiceApplication {

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

}

With these changes we are done with the service so you can run it and then access http://localhost:8761/ to see the UI for Eureka server.

Eureka Server UI

Right now, no instance is registered with Eureka server so now let's work on configuring our microservices as Eureka client so that the server can register them.

Registering Microservices as client

In our example there are two microservices Customer-Service and Account-Service so we need to add the following dependency in pom.xml of both services.

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

Also add dependencyManagement section.

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>${spring-cloud.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

And spring cloud version in the properties section.

<properties>
  <java.version>17</java.version>
  <spring-cloud.version>2022.0.3</spring-cloud.version>
</properties>

Now in the application.yml file of customer-service, add the following properties-

spring:
  application:
    name: customer-service

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

Same way in the application.yml file of account-service, add the following properties-

spring:
  application:
    name: account-service

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

A service with org.springframework.cloud:spring-cloud-starter-eureka on the classpath will be registered with the Eureka registry by its spring.application.name that is why this property is there.

Another Configuration is required to locate the Eureka server.

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

With these changes we have configured both the services so that they can register with Eureka server. Having spring-cloud-starter-netflix-eureka-client on the classpath lets the service register itself with Eureka and also let the services act as clients which can query the registry to locate other services.

Please start both services and ensure Eureka Server service is also running. Once customer-service and account-service start successfully you can verify the registration of both instances in the UI of Eureka server.

Eureka Service Registry

Now change the getCustomerById() method in CustomerServiceImpl. URL passed in resttemplate.exchange should be changed from "http://localhost:8082/account/customer/"+id to http://ACCOUNT-SERVICE/account/customer/"+id.

@Override
public CustomerDto getCustomerById(Long id) {
  Customer customer = customerRepository.findById(id)
               .orElseThrow(()-> new CustomerServiceException("No customer found for the given id - " + id));
  
  ResponseEntity<List<AccountDto>> response = restTemplate.exchange("http://ACCOUNT-SERVICE/account/customer/"+id, HttpMethod.GET, null, 
      new ParameterizedTypeReference<List<AccountDto>>(){});
  List<AccountDto> accounts = response.getBody();
  CustomerDto customerDto = CustomerDto.builder()
         .id(customer.getId())
         .name(customer.getName())
         .age(customer.getAge())
         .city(customer.getCity())
         .accounts(accounts)
         .build();
  return customerDto;
}

Also add @LoadBalanced annotation when configuring RestTemplate as a bean.

@Configuration
public class TemplateConfig {
	
  @Bean 
  @LoadBalanced
  RestTemplate restTemplate() { 
    return new RestTemplate(); 
  }
}

Now, if you access http://localhost:8081/customer/1 internally in making a call to account-service from customer-service

restTemplate.exchange("http://ACCOUNT-SERVICE/account/customer/"+id, HttpMethod.GET, null, 
        new ParameterizedTypeReference<List<AccountDto>>(){});

ACCOUNT_SERVICE in the URL will be resolved by querying Eureka server to provide network information for the service registered as ACCOUNT-SERVICE.

That's all for this topic Spring Boot Microservice - Service Registration and Discovery With Eureka. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Spring Tutorial Page


Related Topics

  1. Spring Boot Microservices Introduction
  2. Spring Boot Microservice Example Using WebClient
  3. Spring Boot Microservice - Eureka + LoadBalancer + Feign
  4. Spring Boot Microservice Circuit Breaker Using Resilience4j
  5. Spring Boot Observability - Distributed Tracing, Metrics

You may also like-

  1. Spring Constructor Based Dependency Injection
  2. Spring MVC PDF Generation Example
  3. Spring Transaction Management Example - @Transactional Annotation and JDBC
  4. @FunctionalInterface Annotation in Java
  5. Interface in Java With Examples
  6. Reading File in Java Using Files.lines And Files.newBufferedReader
  7. Angular Attribute Binding With Examples
  8. Controlled and Uncontrolled Components in React

No comments:

Post a Comment