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.
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.
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.
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.
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.
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.
Awesome. Simple and working as expected.
LikeLike
How do I deploy this in AWS?
Local machine is ok
LikeLike
Good question, but I do not have the answer, that is maybe something for a new blog. In the meantime, some information can be found here: https://cloud.spring.io/spring-cloud-netflix/multi/multi__service_discovery_eureka_clients.html (search for AWS)
LikeLike