Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 80

Service Discovery with Eureka and Consul

Recommended resources

Hello! Welcome back to our journey into building microservices.

In our last lesson, we solved a major challenge by centralizing our application configurations with Spring Cloud Config. However, we left a loose end: our greeting-service still needed to know the hardcoded address of the config-server (http://localhost:8888). In a real-world, dynamic cloud environment where services are constantly starting, stopping, and scaling, relying on static IP addresses and ports is not a robust strategy. What happens if the config server's address changes? How does one service find another?

Today, we will solve this fundamental problem. This lesson addresses the learning outcome: Implement service discovery using a registry like Eureka or Consul. We will learn how to build a dynamic "phone book" for our microservices, allowing them to find and communicate with each other using logical names, without any hardcoded addresses.

By the end of this lesson, you will be able to:

  • Understand the role of a Service Registry in a microservices architecture.
  • Set up a Netflix Eureka Server to act as a service registry.
  • Configure a microservice to register itself as a Eureka Client.
  • Implement inter-service communication where one service dynamically discovers another through the registry.

Let's get started on making our services discoverable.

The Problem: Where Are You?

In a distributed system, services need to talk to each other. A user-service might need to call an order-service, which in turn might call a payment-service. When these services are deployed in containers, they get assigned dynamic IP addresses and ports. Hardcoding these locations is brittle and makes the system impossible to scale or maintain.

This is where the Service Discovery pattern comes in. The core idea is to have a central server, called a Service Registry, that keeps an up-to-date list of all available service instances and their network locations.

Simple Microservice Architecture with Spring Boot and Spring Cloud Netflix Eureka Server
This architectural diagram shows how a Discovery Server (like Eureka) fits into a microservices ecosystem. It acts as a central lookup point for all other services, which register themselves upon startup.

There are two primary models for service discovery:

  1. Client-Side Discovery: The client service is responsible for contacting the service registry, getting a list of available instances for the target service, and then choosing one to connect to. This pattern often includes client-side load balancing.
  2. Server-Side Discovery: The client service sends its request to a router or API Gateway. This gateway is responsible for querying the service registry and forwarding the request to a healthy service instance.

Netflix Eureka, which we will use today, implements the client-side discovery pattern.

Master Service Discovery in Microservices | Eureka and Java Spring Boot

To better understand these concepts, watch this segment from the video 'Master Service Discovery in Microservices' by ByteMonk. It provides an excellent explanation of why service discovery is needed and contrasts the client-side and server-side discovery models.

Watch from the beginning to 00:53 to understand the core problem, and then from 02:01 to 03:33 to see the comparison between client-side and server-side discovery.


Step 1: Building the Eureka Service Registry

Our first task is to set up the Eureka Server, which will be the heart of our discovery mechanism. This is a standalone Spring Boot application.

Microservices Service Registration and Discovery | Spring Cloud | Netflix Eureka

The video 'Microservices Service Registration and Discovery' by Think Constructive provides a fantastic real-world analogy of a mall map and then walks through creating a Eureka server. We'll follow these steps.

First, watch from 02:36 to 06:29 to understand the mall map analogy for a service registry. Then, follow along from 08:02 to 16:44 as the video demonstrates how to create and configure the Eureka Server project.

Let's build it ourselves.

  1. Create the Project: Navigate to start.spring.io and create a new Maven project with the Eureka Server dependency.

  2. Enable the Server: Open the main application class (e.g., EurekaServerApplication.java) and add the @EnableEurekaServer annotation above your class definition. This single annotation turns the application into a service registry.

    package com.example.eurekaserver;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    @SpringBootApplication
    @EnableEurekaServer
    public class EurekaServerApplication {
        public static void main(String[] args) {
            SpringApplication.run(EurekaServerApplication.class, args);
        }
    }
    
  3. Configure the Application: By default, a Eureka server is also a Eureka client and will try to register itself. We need to disable this behavior. Open src/main/resources/application.properties and add the following configuration. We will use port 8761, which is the conventional port for Eureka.

    # Set the server port
    server.port=8761
    
    # Give the server an application name
    spring.application.name=eureka-server
    
    # Configuration to prevent the server from registering itself
    eureka.client.register-with-eureka=false
    eureka.client.fetch-registry=false
    

Now, run your Eureka Server application. Once it's running, open your browser and navigate to http://localhost:8761. You should see the Eureka dashboard. Under "Instances currently registered with Eureka," it will be empty, which is exactly what we expect for now.


Step 2: Registering a Microservice as a Eureka Client

Now that our registry is running, let's make a microservice register itself. Any service that registers with Eureka is known as a "Eureka Client." We'll create a simple service for this demonstration.

Let's follow the steps outlined in the official Spring guide and the Think Constructive video.

Getting Started | Service Registration and Discovery

The official Spring guide 'Service Registration and Discovery' provides a concise, up-to-date walkthrough of setting up both the server and the clients. We'll reference it for the correct dependencies and configuration.

Read the sections 'Starting with Spring Initializr' and 'Talking to the Registry'. Focus on the dependencies required for a client ('Eureka Discovery Client' and 'Spring Web') and the basic application properties needed to name the service and connect it to the registry.

  1. Create a Client Project: Go to start.spring.io and create a new service. Let's call it service-a. Add two dependencies: Eureka Discovery Client and Spring Web.

  2. Configure the Client: In your new service-a project, open src/main/resources/application.properties and add the following:

    # Give the service a unique name. This is the ID it will use to register with Eureka.
    spring.application.name=service-a
    
    # Set a port for this service to avoid conflicts.
    server.port=8081
    
    # Tell the client where to find the Eureka server.
    eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
    

    With modern versions of Spring Cloud, you don't need to add an @EnableEurekaClient or @EnableDiscoveryClient annotation. Simply having the spring-cloud-starter-netflix-eureka-client dependency on the classpath is enough to enable registration.

  3. Run and Verify: Make sure your Eureka Server is still running. Now, start your service-a application. After a few moments, refresh the Eureka dashboard (http://localhost:8761). You should now see SERVICE-A listed under the registered instances!

Test your understanding!

In the client's application.properties, what is the purpose of spring.application.name? What would happen if two different microservices tried to register with the same name?

Show answer

The spring.application.name property provides the unique service ID that the microservice uses to register itself with the Eureka server. It's the logical name other services will use to find it. If two different services used the same name, Eureka would treat them as multiple instances of the same service. When another service tries to discover them by that name, Eureka would provide the locations of both, and the calling service would likely load-balance requests between them, leading to unpredictable behavior if the two services are not identical.


Step 3: Inter-Service Communication via Discovery

The real power of service discovery is realized when services can find and call each other. Let's demonstrate this.

We'll create a second service, service-b, that discovers and calls an endpoint on service-a.

  1. Create service-b: Follow the same steps as for service-a, but name the artifact service-b and configure its properties as follows:

    # In service-b/src/main/resources/application.properties
    spring.application.name=service-b
    server.port=8082
    eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
    
  2. Expose an Endpoint in service-a: In your service-a project, create a simple REST controller.

    // In service-a project
    package com.example.servicea;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class ServiceAController {
        @GetMapping("/hello")
        public String sayHello() {
            return "Hello from Service A!";
        }
    }
    
  3. Discover and Call from service-b: In service-b, we will use Spring's RestTemplate to call service-a. To make this work with service discovery, we need to do two things:

    • Create a RestTemplate bean.
    • Annotate that bean with @LoadBalanced. This annotation tells Spring Cloud to intercept requests made with this RestTemplate and resolve the service name (like service-a) into an actual host and port by querying Eureka.

    Create a configuration class and a controller in service-b:

    // In service-b project, e.g., in the main application class or a new @Configuration class
    package com.example.serviceb;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    @SpringBootApplication
    public class ServiceBApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ServiceBApplication.class, args);
        }
    
        @Bean
        @LoadBalanced // This is the magic!
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    
    // In service-b project
    package com.example.serviceb;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    @RestController
    public class ServiceBController {
    
        @Autowired
        private RestTemplate restTemplate;
    
        @GetMapping("/call-a")
        public String callServiceA() {
            // Use the service name 'service-a', not a hardcoded hostname or port.
            String url = "http://service-a/hello";
            return restTemplate.getForObject(url, String.class);
        }
    }
    

Test Everything

  1. Make sure your Eureka Server is running.
  2. Start service-a.
  3. Start service-b.
  4. Wait about 30 seconds for both services to register with Eureka. You can verify this on the Eureka dashboard (http://localhost:8761).
  5. Now, open your browser and go to http://localhost:8082/call-a.

You should see the message: "Hello from Service A!". This proves that service-b successfully used the Eureka registry to find the location of service-a and make a REST call, all without any hardcoded addresses.

Microservices Service Registration and Discovery | Spring Cloud | Netflix Eureka

The 'Think Constructive' video beautifully demonstrates this exact transformation—replacing a hardcoded URL with a service name. This part of the video solidifies the concept we just implemented.

Watch from 28:12 to 33:11. Pay close attention to how the code is changed from using http://localhost:8081/... to http://post-service/.... This is precisely what the @LoadBalanced RestTemplate enables.


An Alternative: HashiCorp Consul

While Netflix Eureka is a popular choice within the Spring ecosystem, it's important to know that it is no longer actively developed by Netflix (though it's maintained by the Spring community). A powerful and widely used alternative is HashiCorp Consul.

Spring Boot with Consul Interaction Flow
This diagram shows how Spring Cloud Consul allows a Spring Boot application to interact with a Consul server for service discovery, configuration, and health checks.

Consul offers a richer feature set than Eureka, including:

  • Service Discovery: The same core functionality as Eureka.
  • Health Checking: Advanced mechanisms to check the health of services and automatically remove unhealthy instances.
  • Key-Value (KV) Store: A distributed key-value store that can be used for centralized configuration, similar to Spring Cloud Config.
  • Service Mesh: Features for securing and managing traffic between services.

Integrating Consul with Spring Boot is just as straightforward as Eureka. It involves adding the spring-cloud-starter-consul-discovery dependency and configuring the Consul agent's location in your application.properties.

Implementing Service Discovery and Configuration ... with HashiCorp Consul

To get a brief overview of Consul and its advantages, take a look at this article from Mehmet Bilgil.

Read the sections 'What is Consul and Why Do We Need It?' and 'Comparison with Alternative Solutions'. This will give you a good sense of where Consul stands in relation to Eureka and other tools.


Conclusion

Congratulations! You have successfully implemented a service discovery pattern, a cornerstone of any resilient and scalable microservices architecture. By decoupling services from network locations, you've made your system far more robust.

Key Takeaways:

  • Service Discovery is essential for allowing services to find each other in a dynamic environment.
  • A Service Registry (like Eureka Server) acts as a central phone book for all services.
  • Microservices act as Clients, registering themselves with the registry using a logical spring.application.name.
  • Using a @LoadBalanced RestTemplate, services can communicate using these logical names instead of hardcoded IPs and ports, enabling resilience and scalability.
  • Tools like Consul offer more advanced features beyond service discovery and are strong alternatives to Eureka.

What's Next?

Our services can now configure themselves and find each other. But how should external users (from a web or mobile app) access them? It would be inefficient and insecure to expose every single microservice to the outside world.

In the next lesson, you will implement a centralized API Gateway using Spring Cloud Gateway. This gateway will act as a single entry point into our system, handling routing, security, and other cross-cutting concerns.