Service Discovery & Registration
Learn how services find and communicate with each other dynamically in microservices architectures.
1. Service Registration
Definition
Service registration is the process where a microservice announces its availability to a central registry when it starts up. The service provides essential information including its service name, IP address, port number, health status endpoint, and optional metadata that other services can use to locate and communicate with it. This registration acts like adding your phone number to a directory so others can find you when needed. The registry maintains a live inventory of all available service instances across your distributed system.
What information is shared
During registration, a service shares its service name (like "user-service"), its network location (IP address and port), health check URL for monitoring, and metadata such as version, environment, or zone information. This metadata helps with advanced routing decisions, such as directing traffic to services in the same data center or to specific versions during canary deployments. The health status indicates whether the service is ready to receive traffic, ensuring that only healthy instances are discoverable by other services. Additional custom metadata can include capacity information, supported protocols, or any business-specific attributes that influence routing decisions.
When registration happens
Registration occurs automatically when a Spring Boot service starts up and the web server becomes ready to accept requests. The service also updates its registration when its health status changes, such as when it becomes unhealthy due to database connectivity issues or when it recovers from problems. During graceful shutdown, services should deregister themselves to prevent other services from attempting to call instances that are no longer available. Some registries also require periodic heartbeat renewals to keep registrations active, automatically removing services that fail to check in within a specified time window.
Spring Boot Example
In Spring Boot, adding the @EnableEurekaClient annotation to your main application class automatically enables service registration with minimal configuration. The application.yml file needs to specify the service name and the Eureka server location, and Spring Cloud handles the rest of the registration process automatically. This approach eliminates boilerplate code and integrates seamlessly with Spring Boot's lifecycle management, ensuring proper registration timing and cleanup.
@SpringBootApplication
@EnableEurekaClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
spring:
application:
name: user-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
server:
port: 8080
2. Service Discovery
Definition
Service discovery is the process by which a service finds and connects to other services dynamically at runtime without using hard-coded network addresses. Instead of knowing specific IP addresses and ports, services query a registry using logical service names to get a list of available, healthy instances. This enables services to adapt automatically to changes in the infrastructure, such as when instances are scaled up, moved, or replaced. Service discovery is essential for building resilient microservices that can handle the dynamic nature of cloud environments.
Client-side vs Server-side Discovery
In client-side discovery, the calling service directly queries the registry to get a list of available instances and then selects one using its own load balancing logic, giving the client full control over routing decisions but adding complexity to each service. This approach is commonly used with Eureka where services use DiscoveryClient to fetch instance lists and make direct calls to chosen instances. Server-side discovery uses an intermediary component like a load balancer or API gateway that queries the registry and routes requests automatically, simplifying client code but adding network hops and potential bottlenecks. The choice depends on your architecture: client-side discovery provides more control and eliminates single points of failure, while server-side discovery centralizes routing logic and simplifies service implementation.
Why it matters
Service discovery removes the brittleness of hard-coded URLs and enables true elasticity in microservices architectures. It allows services to scale horizontally without requiring configuration changes across all dependent services, and it provides automatic failover when instances become unhealthy or unavailable. This dynamic capability is crucial for cloud-native applications where infrastructure is treated as cattle rather than pets, and instances are frequently created, destroyed, and moved. Without service discovery, microservices would be tightly coupled to specific infrastructure, making them difficult to deploy, scale, and maintain in production environments.
Spring Boot Example
The DiscoveryClient.getInstances() method provides a programmatic way to retrieve available instances of a service, returning a list that includes network locations and metadata for each healthy instance. This approach gives you full control over instance selection and allows you to implement custom load balancing logic based on your specific requirements. You can filter instances based on metadata, implement sticky sessions, or route traffic based on geographic proximity or other business rules.
@RestController
public class OrderController {
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/orders/{userId}")
public List getUserOrders(@PathVariable String userId) {
// Discover user-service instances
List instances = discoveryClient.getInstances("user-service");
if (instances.isEmpty()) {
throw new ServiceUnavailableException("User service not available");
}
// Select first available instance (or implement load balancing)
ServiceInstance instance = instances.get(0);
String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/users/" + userId;
// Make the call (simplified - use RestTemplate in practice)
return callUserService(url);
}
}
3. Eureka Server Setup
Maven Dependencies
To create a Eureka Server, you need to add the Spring Cloud Netflix Eureka Server dependency to your pom.xml. This dependency includes all the necessary components to run a service registry. Make sure to include the Spring Cloud BOM (Bill of Materials) to manage compatible versions of Spring Cloud dependencies.
<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-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Creating a Eureka Server Project
Setting up a Eureka Server requires creating a new Spring Boot project with the spring-cloud-starter-netflix-eureka-server dependency, which provides all the necessary components for running a service registry. The @EnableEurekaServer annotation on the main application class activates the Eureka Server functionality, transforming your Spring Boot application into a fully functional service registry. This server will handle service registrations, maintain the service directory, and provide discovery APIs for client applications. The setup is minimal and leverages Spring Boot's auto-configuration to handle most of the complex setup automatically.
Basic Configuration
The application.yml configuration disables the server's own registration with itself (since it's the registry) and sets up basic operational parameters like the port number. The registerWithEureka and fetchRegistry properties are set to false because the Eureka Server doesn't need to register with itself or fetch the registry. Additional configuration options allow you to tune heartbeat intervals, eviction policies, and self-preservation modes based on your network conditions and reliability requirements. These settings help optimize the balance between responsiveness to failures and stability during network partitions.
Dashboard Access
Eureka provides a built-in web dashboard accessible at http://localhost:8761 that displays all registered services, their instances, and health status in real-time. This dashboard is invaluable for debugging service discovery issues, monitoring service health, and understanding the current state of your microservices ecosystem. You can see which services are up, how many instances are running, and when they last renewed their leases, providing operational visibility into your distributed system. The dashboard also shows configuration details and can help identify issues like services failing to register or renew their leases properly.
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
server:
enable-self-preservation: true
4. Eureka Client Setup
Maven Dependencies
For services that need to register with Eureka and discover other services, add the Eureka Client dependency to your pom.xml. This dependency provides the client-side functionality for service registration and discovery. The starter-web dependency is included for REST endpoints, and the BOM ensures version compatibility.
<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>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Adding @EnableEurekaClient
The @EnableEurekaClient annotation on your Spring Boot application class automatically configures the service to register with Eureka and enables service discovery capabilities. This annotation triggers Spring Cloud's auto-configuration, which sets up the necessary beans for registration, heartbeat management, and service discovery without requiring manual configuration. The annotation also enables integration with other Spring Cloud components like load balancers and circuit breakers that rely on service discovery. Once added, your service will automatically handle the complete lifecycle of registration, renewal, and deregistration with the Eureka server.
Service Configuration
The application.yml file must specify the spring.application.name property, which becomes the service identifier used by other services for discovery - this name should be consistent across all instances of the same service. The eureka.client.service-url.defaultZone property tells the client where to find the Eureka server for registration and discovery operations. Additional configuration options allow you to customize instance metadata, health check URLs, and registration behavior to suit your specific deployment requirements. The server.port setting determines the port this service instance will run on and register with Eureka.
Service-to-Service Communication Example
This example shows how one service can discover and call another service using the DiscoveryClient, demonstrating the complete flow from discovery to invocation. The code handles the common scenario where a service might not be available by checking for empty instance lists and throwing appropriate exceptions. In production, you would typically use more sophisticated approaches like @LoadBalanced RestTemplate or OpenFeign clients that handle service discovery automatically, but this example illustrates the underlying mechanics. Error handling and retry logic should be added for production use to handle transient failures and service unavailability gracefully.
@SpringBootApplication
@EnableEurekaClient
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
spring:
application:
name: order-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
server:
port: 8081
@RestController
public class OrderController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/orders/user/{userId}")
public ResponseEntity> getOrdersForUser(@PathVariable String userId) {
// Discover user-service instances
List userServiceInstances =
discoveryClient.getInstances("user-service");
if (userServiceInstances.isEmpty()) {
return ResponseEntity.status(503).build(); // Service Unavailable
}
// Get first available instance
ServiceInstance userService = userServiceInstances.get(0);
String userServiceUrl = "http://" + userService.getHost() +
":" + userService.getPort() + "/users/" + userId;
// Call user service to validate user exists
try {
User user = restTemplate.getForObject(userServiceUrl, User.class);
if (user != null) {
// Fetch orders for this user
List orders = orderRepository.findByUserId(userId);
return ResponseEntity.ok(orders);
} else {
return ResponseEntity.notFound().build();
}
} catch (Exception e) {
return ResponseEntity.status(503).build(); // Service call failed
}
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
5. Lesson Summary
In this lesson, we covered the essential concepts of service discovery and registration in microservices architectures. Here's a recap of the key points:
Service Registration
- Purpose: Services announce their availability to a central registry when they start up
- Information shared: Service name, IP address, port, health check endpoint, and metadata
- Timing: Occurs at startup, health status changes, and graceful shutdown
- Spring Boot: Enabled with @EnableEurekaClient annotation and minimal configuration
Service Discovery
- Purpose: Services find and connect to other services dynamically at runtime
- Client-side discovery: Services query the registry directly and handle load balancing
- Server-side discovery: Load balancer or gateway handles registry queries and routing
- Benefits: Eliminates hard-coded URLs, enables elasticity, and provides automatic failover
Eureka Server Setup
- Dependencies: spring-cloud-starter-netflix-eureka-server with Spring Cloud BOM
- Configuration: @EnableEurekaServer annotation and application.yml settings
- Features: Built-in dashboard at http://localhost:8761 for monitoring
- Settings: Disable self-registration and configure self-preservation mode
Eureka Client Setup
- Dependencies: spring-cloud-starter-netflix-eureka-client for registration and discovery
- Configuration: @EnableEurekaClient annotation and service name in application.yml
- Discovery: Use DiscoveryClient.getInstances() to find available service instances
- Communication: Retrieve instance details and make REST calls to discovered services
Key Takeaways
- Service discovery is fundamental for building resilient, scalable microservices
- Eureka provides a simple, battle-tested solution for service registry and discovery
- Spring Cloud integration makes setup and usage straightforward with minimal boilerplate
- Proper error handling and health checks are essential for production deployments
- The Eureka dashboard provides valuable operational visibility into your service ecosystem