Spring Boot Microservices — Developing Service Discovery
By: Lal Verma
Tags: Microservices, Microservice Architecture, Spring Cloud, Service Discovery, Spring Boot

Spring Boot Microservices — Developing Service Discovery

In this article, we will learn an important patten in Microservices Architecture — Service Discovery. We will create a sample implementation based on Spring Boot, Spring Cloud, Netflix Eureka Server, and Netflix Zuul. This is the third part of our Spring Boot Microservices series.

Photo by David Torres on Unsplash

Many technologies are available to implement service discovery pattern in the Microservices world. This includes Netflix Eureka, Kubernetes, Istio, Consul and many other frameworks. With this article, I am trying to develop the understanding of this pattern with the basic implementation of it. The article is divided in following sections —

The need for Service Discovery

Microservices are dynamic in nature. This means multiple instances of a single Microservice will be co-existing. Most probably your instances will either have a different IP address or a different port or both. And the number of instances will be dynamic too. This brings up loads of questions —

How do I know the location of any Microservice instance?

How can I keep a track of all the instances?

How do I select a Microservice instance?

What happens if the Microservice instance goes down?

Let's understand the problem statement from a practical perspective. We already developed Product Catalog Service as part of the first exercise. Follow a similar approach to develop another Microservice — Shopping Cart Service. Let's implement addItem , the method responsible to add an item to the shopping cart.

This method needs to call the Product Catalog Service to get the product details. It will use this info to update other item attributes in the Shopping Cart. If you see the code below, we are passing the URL reference of Product Catalog Service.

We are assuming the URL ( http://localhost:8080/product/** ) to be constant but in reality, it is dynamic. All parts of it — be it hostname or the port .

Service Discovery comes to the rescue. This provides a mechanism that keeps track of all the services and their instances. All the Microservices register to it and keep updating their network information regularly.

An Illustration — Ecommerce Microservices registering to Discovery Server

The end to end solution has two prominent variations — Client-side Service Discovery and Server-side Service Discovery. We will discuss both these options in detail with the supporting examples.

Client-Side Service Discovery

The problem can be resolved by creating a Discovery Server where our microservices can register themselves. The discovery server will keep a track of all the instances, their hosts, ports, and health status. If a service needs to communicate with another service, it needs to get the instance info from this discovery server. Let's understand this through our example

All the instances of Product Catalog Service will register to the Discovery Server. If the shopping cart service needs to communicate to Product Catalog Service, it will first get its instances information from Discovery Server. Once the information is received, Shopping Cart Service can call the Product Catalog Service to get the product details. As a first step — Let's create the Discovery Server

Creating Discovery Server

Step 1: Go to spring initializer, add the dependency of Spring Cloud Eureka Server. This library is a wrapper over the Netflix OSS library. Download the archive and unpack it to your favorite location.

Step 2: Open the auto-generated java file — DiscoveryServerApplication.java and add the annotation @EnableEurekaServer to this class. This will ensure all the dependent beans for the eureka server are initialized.

@SpringBootApplication

@EnableEurekaServer public class DiscoveryServerApplication { public static void main(String[] args) {

SpringApplication.run(DiscoveryServerApplication.class, args);

} }

Step 3: By default, every Eureka server is also a Eureka client and requires (at least one) service URL to locate a peer. If you do not provide it, the service runs and works, but it fills your logs with a lot of noise about not being able to register with the peer. To avoid this noise and make our Discovery Server standalone, update src/main/resources/application.properties with the following property

server.port=8761 eureka.client.register-with-eureka=false

We are also setting the port to 8761 to avoid conflicts with other services.

Step 4: Run the maven command to start the discovery server mvn spring-boot:run and visit http://localhost:8761/. You will see something similar to —

Congratulations! Your discovery server is successfully started. Its time to register our Microservices.

Registering Microservices

We will extend the code of Product Catalog Service, developed as part of the very first exercise, to enable it, to communicate to the Discovery Server.

Step 1: Update pom.xml to include Spring Cloud Eureka Client dependency. As Spring Boot finds the library in the classpath, it registers automatically to the Eureka Server.

<project>

... <dependencies>

...

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>

</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>

... </project>

Step 2: Add src/main/resources/bootstrap.properties and the property as below. The discovery server will register the service with this name(id). All the services instances of the same service will be tagged with this.

spring.application.name=product_catalog

Step 3: Run the mvn command to start Product Catalog Service and visit eureka server page — http://localhost:8761/. You will see the entry of Product Catalog Service

Spring Boot is making life easier. With minimal change, we are able to implement our discovery server and the client. We are all set to see how the service interaction takes place.

Service Interaction

Shopping Cart Service is supposed to call the Product Catalog Service. Spring cloud discovery client helps in this communication. Similar to Product Catalog Service, include the discovery client library in Shopping Cart Service as well. Now we can use the client APIs to communicate with the Discovery Server. Let's modify our ShoppingCartService class.

We are calling the discovery server to get the instances for the “product_catalog” service. This is returning a list of instances. We are getting the service URL by picking up the first instance in the list. Once we get the URL, the rest of the code works with no change.

Here we are using Spring Discovery Client API which only returns registered instances. We need to implement the Load Balancing logic to get the most appropriate instance. Eureka also provides a native client API, which makes the call bit easier

InstanceInfo instance = discoveryClient.getNextServerFromEureka("product_catalog",false); String productCatalogUrl = instance.getHomePageUrl()+"/product/"+ item.getProductId();

Load balancing is also available through other more matured libraries like Feign,Robin, etc.

Challenges with Client-side service discovery

Load balancing is a must as the discovery server does not handle it automatically. But this adds up a new dependency on the client-side. Can we move this logic to the server-side and make our calls much simpler?

Server-Side Service Discovery

The picture looks a bit different with this option. It has the Discovery Server similar to the option above but the load balancing is not happening at the client side. We have a dedicated proxy server that takes care of this.

Server Side Discovery Illustration

In the above example, all the instances of Product Catalog Service register themselves with Discovery Server. When Shopping Cart Service needs to call Product Catalog Service, the call goes through the Proxy Server. The Proxy Server is responsible for two things

To route the requests

To do load-balancing

Let's implement this pattern for our use case. We have already built Discovery Server with, the client side option. We will be developing a proxy server based on Spring Boot, Spring Cloud Discovery Client, and Spring Cloud Netflix Zuul. Let's go to our one-stop shop — the Spring initializer. Add two dependencies — Spring Cloud Netflix Zuul and Spring Cloud Discovery Client. Generate the archive, download and unpack it to your favorite location.

Update ZuulServerApplication.java with @EnableZuulProxy and @EnableDiscoveryClient annotations. @EnableZuulProxy annotates the application to be the Zuul Proxy Server. @EnableDiscoveryClient makes the application as one of the discovery clients, which can communicate with the Discovery Server.

@SpringBootApplication

@EnableZuulProxy

@EnableDiscoveryClient public class ZuulServerApplication { public static void main(String[] args) {

SpringApplication.run(ZuulServerApplication.class, args);

} }

Update the src/main/resources/application.properties with the following entries

server.port=8762

spring.application.name=zuul-server

This first property tells to run the proxy server at port 8762 and the second property sets the service name. Our Discovery Client needs this property to register the service (proxy server) to the Discovery Server.

Add a new file — application.yml in src/main/resources/ . We are going to define routing behavior for the Product Catalog Service, in it.

zuul:

routes:

product:

path: /product_catalog/**

serviceId: product_catalog

With this definition, we are saying that when somebody calls the proxy server with the path /product/catalog/** , it should be redirected to product_catalog service. Product Catalog Service is registered with the Discovery Server with the id — product_catalog.

Start the Proxy Server — mvn spring-boot:run . Assuming the Discovery Server and Product Catalog Service are already running, you can access the product catalog service at — http://localhost:8762/product_catalog/product

As our other pieces are in place, let's see how the Shopping Cart Service is going to get product details. Update addItem in the ShoppingCartService as below.

We are using the proxy server URL now to connect to the Discovery Server. The Zuul based proxy server is doing the load balancing with the help of Netflix Ribbon. Spring recommends using Spring Cloud Load Balancer these days though in favor of Ribbon. If interested you can refer the guide to enable it here.

You can play around by starting multiple instances of Product Catalog Service with different ports. You can check the exact instance being called by calling actuator/env .

Comparison & Next Steps

Each of the options has its pros and cons. Client-side option is simpler but comes with an additional burden of load balancing. The load-balancing logic on the client-side is tightly coupled to the client service. If you have Microservices in different programming languages, you need to maintain consistent copies of it across all of them. This is quite a maintenance.

Server-side option resolves this issue but adds another moving part into the system. This adds another network hop in the end to end communication. Also, the proxy server needs to be highly available as this is the single entry point for all the microservices. It does make the client code simpler though, it just needs to refer the proxy server.

From a developer perspective, I would prefer a server-side option as it provides better maintainability on the development side.

We used the most basic frameworks to implement the service discovery pattern. Many new technologies have come up which can support the Service Discovery pattern along with other features. Kubernetes, Istio, Consul are some of the examples in this domain. If interested, do catch up with these technologies.

You can check the exercise on Kubernetes with Spring Boot Microservices in the 7th part of this series.