The Problem
In a monolith, everything is in one place. But with microservices, you have dozens of services running on different servers, different ports, constantly scaling up and down. How does the Order Service know where the User Service is?
// BAD: Hardcoded addresses
String userServiceUrl = "http://192.168.1.100:8080/users";
// What if the IP changes? What if there are 5 instances?
// GOOD: Service discovery
String userServiceUrl = discoveryClient.getService("user-service").getUri();
// Let the system figure out where it is!
Dynamic Registration
Services register themselves when they start, deregister when they stop.
Load Balancing
Multiple instances? Discovery returns one - with built-in load balancing.
Health Checking
Automatically removes unhealthy instances from the registry.
Netflix Eureka Setup
1. Eureka Server
<!-- pom.xml for Eureka Server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
# application.yml for Eureka Server
server:
port: 8761
eureka:
client:
register-with-eureka: false # Server doesn't register itself
fetch-registry: false
server:
enable-self-preservation: false # For development
2. Eureka Client (Your Services)
<!-- pom.xml for any microservice -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
# application.yml for User Service
spring:
application:
name: user-service # This is how other services find you
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
That's it! Your service now registers with Eureka automatically.
Calling Other Services
@Service
public class OrderService {
@Autowired
private DiscoveryClient discoveryClient;
// Option 1: Manual discovery
public User getUser(Long userId) {
List<ServiceInstance> instances =
discoveryClient.getInstances("user-service");
if (instances.isEmpty()) {
throw new RuntimeException("No user-service available");
}
ServiceInstance instance = instances.get(0);
String url = instance.getUri() + "/users/" + userId;
return restTemplate.getForObject(url, User.class);
}
}
Better: Load-Balanced RestTemplate
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced // Magic annotation!
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Service
public class OrderService {
@Autowired
private RestTemplate restTemplate;
public User getUser(Long userId) {
// Use service name instead of URL!
return restTemplate.getForObject(
"http://user-service/users/" + userId,
User.class
);
}
}
Best: OpenFeign Client
// Just declare an interface - Spring handles the rest
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable Long id);
@PostMapping("/users")
User createUser(@RequestBody User user);
}
@Service
public class OrderService {
@Autowired
private UserClient userClient;
public User getUser(Long userId) {
return userClient.getUser(userId); // So clean!
}
}
Consul: Alternative to Eureka
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
# application.yml
spring:
application:
name: user-service
cloud:
consul:
host: localhost
port: 8500
discovery:
health-check-interval: 10s
instance-id: ${spring.application.name}:${random.value}
Eureka vs Consul
| Feature | Eureka | Consul |
|---|---|---|
| Setup | Need to run Eureka Server | Standalone binary |
| Health Checks | Client-side heartbeat | Active health checks |
| Key-Value Store | No | Yes (config storage) |
| Multi-Datacenter | Limited | Built-in support |
High Availability
# Eureka Server 1 (peer with Server 2)
eureka:
instance:
hostname: eureka1
client:
service-url:
defaultZone: http://eureka2:8761/eureka/
# Eureka Server 2 (peer with Server 1)
eureka:
instance:
hostname: eureka2
client:
service-url:
defaultZone: http://eureka1:8761/eureka/
# Client connects to both
eureka:
client:
service-url:
defaultZone: http://eureka1:8761/eureka/,http://eureka2:8761/eureka/
Kubernetes Service Discovery
In Kubernetes, you often don't need Eureka - K8s has built-in service discovery:
# Kubernetes handles discovery via DNS
# Just use the service name:
spring:
cloud:
kubernetes:
discovery:
enabled: true
# Or simply use K8s Service DNS:
user-service.default.svc.cluster.local