Service Registry and Discovery in Microservices: Connecting the Dots

Microservices architecture has transformed the way software is built, enabling applications to be scalable, maintainable, and deployable independently. However, breaking an application into multiple independent services introduces a challenge: how do services find and communicate with each other efficiently? This is where service registry and discovery come into play.

What is Service Discovery?

In a microservices ecosystem, services are dynamic—they can be scaled up or down, moved across different servers, or replaced during updates. Hardcoding service addresses is not practical. Service discovery is the mechanism that allows services to dynamically locate and communicate with each other without knowing their physical locations beforehand.

Key Components

  1. Service Registry
    A central database or directory that maintains a list of all available services and their instances along with network locations (IP addresses, ports).

    Examples:

    • Eureka (Netflix)

    • Consul

    • Zookeeper

  2. Service Discovery Mechanism
    There are two main approaches to service discovery:

    • Client-Side Discovery
      The client queries the service registry to find the network location of the service it wants to call. It then sends the request directly to the service instance.
      Example: Netflix Eureka with Spring Cloud.

    • Server-Side Discovery
      The client makes a request to a load balancer or API gateway, which queries the service registry and forwards the request to an available service instance.
      Example: AWS Elastic Load Balancer (ELB) or Kubernetes Service.

How Service Registry Works

  1. Service Registration: When a microservice starts, it registers itself with the service registry along with metadata like IP, port, and health check URL.

  2. Service Heartbeat: Services send regular heartbeat signals to let the registry know they are alive.

  3. Service Lookup: Clients or API gateways query the registry to get a list of active service instances.

  4. Deregistration: If a service shuts down or fails to send heartbeats, it is automatically removed from the registry.

Benefits of Service Registry and Discovery

  1. Dynamic Scalability
    Services can scale horizontally without requiring changes in client code.

  2. Fault Tolerance
    Clients can automatically avoid failed service instances, improving system resilience.

  3. Decoupling of Services
    Services don’t need hardcoded knowledge of other service locations, reducing tight coupling.

  4. Load Balancing
    Service discovery can help route requests to multiple instances, distributing traffic evenly.

Real-World Examples

  • Netflix Eureka: Provides client-side service discovery with automatic registration and health checks.

  • Consul: Offers both service discovery and key-value storage for configuration management.

  • Kubernetes Service: Provides built-in service discovery via DNS and environment variables, simplifying communication in containerized microservices.

Microservices Service Discovery with Eureka: Example in Spring Boot

Eureka, from Netflix, is a popular service registry for microservices. It allows services to register themselves and discover other services dynamically.

We’ll create a simple setup with:

  1. Eureka Server – Central registry.

  2. Service A (Client) – Registers itself to Eureka.

  3. Service B (Client) – Registers itself and calls Service A via Eureka.


1. Setting Up Eureka Server

Step 1: Create a Spring Boot Project
Dependencies: Eureka Server, Spring Web

application.yml

server: port: 8761 eureka: client: register-with-eureka: false fetch-registry: false server: enable-self-preservation: false

EurekaServerApplication.java

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); } }

Run this application, and Eureka dashboard will be available at: http://localhost:8761


2. Setting Up Service A

Step 1: Create a Spring Boot Project
Dependencies: Eureka Discovery Client, Spring Web

application.yml

spring: application: name: service-a server: port: 8081 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/

ServiceAController.java

import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class ServiceAController { @GetMapping("/hello") public String hello() { return "Hello from Service A!"; } }

ServiceAApplication.java

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class ServiceAApplication { public static void main(String[] args) { SpringApplication.run(ServiceAApplication.class, args); } }

Run Service A, it will register itself to the Eureka server. You should see it listed on the dashboard.


3. Setting Up Service B (Client that discovers Service A)

Step 1: Create a Spring Boot Project
Dependencies: Eureka Discovery Client, Spring Web, Spring Cloud OpenFeign (optional for simplified calls)

application.yml

spring: application: name: service-b server: port: 8082 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/

Option 1: Using RestTemplate with Eureka Discovery

ServiceBController.java

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.List; @RestController public class ServiceBController { @Autowired private DiscoveryClient discoveryClient; private RestTemplate restTemplate = new RestTemplate(); @GetMapping("/call-service-a") public String callServiceA() { List<org.springframework.cloud.client.ServiceInstance> instances = discoveryClient.getInstances("service-a"); if (instances.isEmpty()) { return "Service A not available"; } String serviceAUrl = instances.get(0).getUri().toString() + "/hello"; return restTemplate.getForObject(serviceAUrl, String.class); } }

Option 2: Using Feign Client (Simpler)

ServiceAClient.java

import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; @FeignClient(name = "service-a") public interface ServiceAClient { @GetMapping("/hello") String hello(); }

ServiceBController.java (Feign version)

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class ServiceBController { @Autowired private ServiceAClient serviceAClient; @GetMapping("/call-service-a") public String callServiceA() { return serviceAClient.hello(); } }

ServiceBApplication.java

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableEurekaClient @EnableFeignClients public class ServiceBApplication { public static void main(String[] args) { SpringApplication.run(ServiceBApplication.class, args); } }

4. Test the Setup

  1. Start Eureka Server (port 8761)

  2. Start Service A (port 8081)

  3. Start Service B (port 8082)

  4. Visit: http://localhost:8082/call-service-a

You should see:

Hello from Service A!

This demonstrates service discovery in action—Service B dynamically finds Service A via Eureka without hardcoding the URL.

Conclusion

Service registry and discovery are critical pillars of microservices architecture. They enable services to find each other dynamically, maintain resilience, and scale efficiently. Without service discovery, managing hundreds of microservices would be chaotic and error-prone.

By implementing a robust service registry and discovery mechanism, organizations can build highly dynamic, resilient, and scalable applications that truly harness the power of microservices.

Comments