In this post, we will take a look at how we can make services be aware of each other without knowing their exact location. We will make use of Eureka Server which will act as a Discovery Server. Being Spring fans, we will do so by means of Spring Eureka.

1. Introduction

Spring Eureka allows you to create a Eureka Server where services can register and discover themselves without knowledge about where the other services are running. Spring Eureka is part of Spring Cloud Netflix and Eureka is the Netflix Service Discovery Server and Client. The official Spring reference documentation can be found here.

We will explore how this all works by means of an example. We will create a Maven multi module project containing three modules:

  • eurekaserver: which will serve as a standalone Eureka Server;
  • carservice: a service which will list a number of car types;
  • brandservice: a service which will provide all cars of a specific brand. The Brand Service will make use of the information the Car Service will provide.

The setup of our example is shown in the overview below. The Car Service and Brand Service will register themselves with the Eureka Server. When invoking the URL to retrieve the cars of a specific brand from the Brand Service, the Brand Service will invoke the URL of the Car Service in order to retrieve the cars. We will also run multiple instances of the Car Service and make use of load balancing.

example-overview

Source code for the example can be found at GitHub.

2. Eureka Server

First, we will start with creating the Eureka Server. We will make use of a Maven multi module project, the parent pom is:

<groupId>com.mydeveloperplanet</groupId>
<artifactId>myeurekaplanet</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
  <module>eurekaserver</module>
  <module>carservice</module>
  <module>brandservice</module>
</modules>

In order to make use of Spring Eureka Server, we just need to add the spring-cloud-starter-netflix-eureka-server dependency. Notice that we also make use of the BOM, this will automatically include the correct Spring Cloud dependencies. In our project, we will also make use of Java 11.

...
<properties>
  <java.version>11</java.version>
  <spring-cloud.version>Greenwich.RELEASE</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>
...

We will create a Spring Boot Application and the only thing we need to do in order to create a Eureka Server is to annotate our main class with @EnableEurekaServer.

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

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

}

Last thing to do, is to add an application.properties (or application.yml if you like). We will run the Eureka Server and services locally. In standalone mode, we disable the client side behavior, otherwise the Eureka Server will try to reach its peer instances.

spring.application.name=eureka-server
server.port=8761
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

Let’s start the server with mvn spring-boot:run. Unfortunately, starting the application fails:

2019-06-23 11:52:36.065 ERROR 6558 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Exception starting filter [servletContainer]

java.lang.TypeNotPresentException: Type javax.xml.bind.JAXBContext not present

Reason for this error is because the J2EE modules are removed from Java 11. Java 11 removed the support for java.xml.bind – which defines the Java Architecture for XML Binding (JAXB) API. More information can be found in this issue. According to the Spring documentation, we should add the jaxb-api, jaxb-core and jaxb-impl dependencies.

<dependency>
  <groupId>javax.xml.bind</groupId>
  <artifactId>jaxb-api</artifactId>
  <version>2.3.0</version>
</dependency>
<dependency>
  <groupId>com.sun.xml.bind</groupId>
  <artifactId>jaxb-core</artifactId>
  <version>2.3.0</version>
</dependency>
<dependency>
  <groupId>com.sun.xml.bind</groupId>
  <artifactId>jaxb-impl</artifactId>
  <version>2.3.0</version>
</dependency>

Starting the server still fails because of the following error:

2019-06-23 12:03:18.022 ERROR 7397 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Exception starting filter [servletContainer]

java.lang.NoClassDefFoundError: javax/activation/DataSource

Apparently, we also need to add the javax.activation-api dependency.

<dependency>
  <groupId>javax.activation</groupId>
  <artifactId>javax.activation-api</artifactId>
  <version>1.2.0</version>
</dependency>

After adding this dependency, the server starts successfully. Go to the Eureka Server URL http://localhost:8761/ which will show us the Eureka Server page. It shows us that no instances are registered at the moment.

eureka-initial-startup

3. Car Service

The Car Service will serve a single Rest endpoint where a list of cars of a specific brand can be retrieved. We will therefore make use of Spring Web MVC. Besides the Spring Web MVC dependency, we also need to add the spring-cloud-starter-netflix-eureka-client in order to make it possible to register the service with the Eureka Server.

...
<dependencies>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>
...

3.1 Enabling the Eureka Client

Just like the Eureka Server, we need to annotate the Spring Boot main application. Because the Car Service is a client, we add the @EnableEurekaClient annotation. This way, the application will register itself automatically with the Eureka Server.

@SpringBootApplication
@EnableEurekaClient
public class CarServiceApplication {

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

}

Configuration in the application.properties file is necessary in order to locate the Eureka Server. The other properties define the name of the Spring application and the server port of the Car Service.

spring.application.name=car-service
server.port=8081
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka

3.2 Creating the Rest Service

We will create a very simplified Rest service in order to retrieve the cars based on a brand. The Car POJO contains a brand and a type.

public class Car {

  String brand;
  String type;
  ...
}

The CarController contains a static list of cars, in real life this information would probably be available in a database of some kind. The Rest endpoint receives a brand parameter and then traverses the complete list of cars to verify whether cars for this brand can be found. At the end, the list of found cars is returned to the requester.

@RestController
public class CarController {

private static final ArrayList carsDb = new ArrayList();

  static {
    carsDb.add(new Car("Renault", "Clio"));
    carsDb.add(new Car("Renault", "Kadjar"));
    carsDb.add(new Car("Ford", "Mondeo"));
    carsDb.add(new Car("Ford", "Mustang"));
  }

  @RequestMapping(value= "/getCars/{brand}", method = RequestMethod.GET)
  public List getCars(@PathVariable String brand) {

    System.out.println("Retrieve Cars");

    ArrayList carsList = new ArrayList();
    for (Car car : carsDb) {
      if (brand.equalsIgnoreCase(car.getBrand())) {
        carsList.add(car);
      }
    }
    return carsList;
  }
}

3.3 Test the Car Service

Start the Eureka Server and afterwards the Car Service with mvn spring-boot:run. Go to the Eureka web page and we know notice that the Car Service is registered with the Eureka Server.

eureka-single-car-service

We also check whether the Rest endpoint is working correctly by invoking http://localhost:8081/getCars/renault. The response is as expected and returns two cars:

[{"brand":"Renault","type":"Clio"},
 {"brand":"Renault","type":"Kadjar"}]

4. Brand Service

The Brand Service will serve a single Rest endpoint where all the cars of a specific brand can be retrieved. It is a bit of trivial example, because we will just pass the results retrieved from the Car Service. In the real world, you can think of parsing the results of the Car Service, maybe enrich it with content of another service and publish that to a Rest endpoint. For experimenting with Eureka, passing the results will do just fine.

The Brand Service will also make use of Spring Web MVC. Besides the Spring Web MVC dependency, we again need to add the spring-cloud-starter-netflix-eureka-client in order to make it possible to register the service with the Eureka Server.

...
<dependencies>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>
...

4.1 Enabling the Eureka Client

The Brand Service will act as a Eureka Client, so we add the @EnableEurekaClient annotation to the Spring Boot main class:

@SpringBootApplication
@EnableEurekaClient
public class BrandServiceApplication {

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

}

The application.properties are similar as for the Car Service, the Brand Service will run on port 8082:

spring.application.name=brand-service
server.port=8082
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka

4.2 Creating the Rest Service

The BrandController calls the Car Service in order to retrieve the cars of a specific brand and just returns the result. As you can see, we don’t need to call the exact URL of the Car Service. We just refer to car-service and the Eureka Server will execute the lookup for us and transfers the request to the correct service. We also annotate the RestTemplate with the @LoadBalanced annotation. This will allow us to use load balancing when more than one Car Service is running.

@RestController
public class BrandController {

  @Autowired
  RestTemplate restTemplate;

  @RequestMapping(value= "/getBrands/{brand}", method = RequestMethod.GET)
  public String getBrands(@PathVariable String brand) {

    String response = restTemplate.exchange("http://car-service/getCars/{brand}",
          HttpMethod.GET, null, new ParameterizedTypeReference() {}, brand).getBody();

    return "Brand - " + brand + " \n Cars " + response;
  }

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

4.3 Test the Brand Service

Start the Eureka Server, the Car Service and the Brand Service with mvn spring-boot:run. Go to the Eureka web page and we know notice that the Car and Brand Service are registered with the Eureka Server.

eureka-single-car-brand-service

We also check whether the Rest endpoint is working correctly by invoking http://localhost:8082/getBrands/renault. The response is as expected and returns two cars:

Brand - renault Cars 
[{"brand":"Renault","type":"Clio"},
 {"brand":"Renault","type":"Kadjar"}]

4.4 Load Balancing

We also configured load balancing for the Car Service. Let’s start a second instance of the Car Service on port 8083:

mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=8083"

When we take a look at the Eureka web page, we now notice two instances of the Car Service.

eureka-multiple-car-service

In the CarController we added a System.out line. When we now invoke the Brand Service URL after each other, we notice that both Car Service instances are invoked one after each other in a round robin fashion.

5. Conclusion

In this post, we created a Eureka Server and Clients by means of Spring Eureka. Implementation went very smoothly and without much effort which makes this an excellent choice when you want to make use of a Discovery Server for your microservices. What we didn’t do is configuring the health check and secure the Discovery Server, but this can easily accomplished by means of some extra configuration.