TP13.1: Eureka Server e Clientes
🎯 Objetivos do TP
- Criar um Eureka Server (Service Registry)
- Criar dois serviços que se registram como Eureka Clients
- Testar o load balancing entre múltiplas instâncias
- Observar o comportamento durante falhas
Duração estimada: 1h30
📋 Arquitetura Final
📁 Estrutura dos Projetos
microservices-demo/
├── eureka-server/
│ ├── pom.xml
│ └── src/main/...
├── user-service/
│ ├── pom.xml
│ └── src/main/...
└── product-service/
├── pom.xml
└── src/main/...
Parte 1: Eureka Server (20 min)
1.1 Criar o Projeto
Utilize o Spring Initializr (https://start.spring.io) ou crie manualmente:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>eureka-server</artifactId>
<version>1.0.0</version>
<name>eureka-server</name>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
</properties>
<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-actuator</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.2 Aplicação Principal
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);
}
}
1.3 Configuração
# src/main/resources/application.yml
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://localhost:8761/eureka/
server:
enable-self-preservation: false
eviction-interval-timer-in-ms: 5000
management:
endpoints:
web:
exposure:
include: "*"
1.4 Testar
# Iniciar Eureka Server
cd eureka-server
mvn spring-boot:run
Abra: http://localhost:8761
Você deve ver o dashboard do Eureka sem nenhum serviço registrado.
Parte 2: User Service (30 min)
2.1 Criar o Projeto
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>user-service</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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-actuator</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.2 Aplicação Principal
package com.example.userservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
2.3 Model
package com.example.userservice.model;
public class User {
private Long id;
private String username;
private String email;
private String firstName;
private String lastName;
public User() {}
public User(Long id, String username, String email,
String firstName, String lastName) {
this.id = id;
this.username = username;
this.email = email;
this.firstName = firstName;
this.lastName = lastName;
}
// Getters et Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
}
2.4 Controller
package com.example.userservice.controller;
import com.example.userservice.model.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.PostConstruct;
import java.util.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
@Value("${server.port}")
private String serverPort;
private final Map<Long, User> users = new HashMap<>();
@PostConstruct
public void init() {
users.put(1L, new User(1L, "john", "john@example.com", "John", "Doe"));
users.put(2L, new User(2L, "jane", "jane@example.com", "Jane", "Smith"));
users.put(3L, new User(3L, "bob", "bob@example.com", "Bob", "Wilson"));
}
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
return ResponseEntity.ok(new ArrayList<>(users.values()));
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = users.get(id);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(user);
}
@GetMapping("/{id}/exists")
public ResponseEntity<Boolean> userExists(@PathVariable Long id) {
return ResponseEntity.ok(users.containsKey(id));
}
// Endpoint para identificar qual instância respondeu
@GetMapping("/info")
public ResponseEntity<Map<String, String>> getInfo() {
return ResponseEntity.ok(Map.of(
"service", "user-service",
"port", serverPort,
"timestamp", new Date().toString()
));
}
}
2.5 Configuração
# src/main/resources/application.yml
server:
port: 8081
spring:
application:
name: user-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.application.name}:${server.port}
management:
endpoints:
web:
exposure:
include: health,info
2.6 Testar
# Iniciar User Service (Instância 1)
cd user-service
mvn spring-boot:run
# Verificar no Eureka Dashboard
# http://localhost:8761 -> USER-SERVICE deve aparecer
# Testar os endpoints
curl http://localhost:8081/api/users
curl http://localhost:8081/api/users/1
curl http://localhost:8081/api/users/info
2.7 Iniciar uma Segunda Instância
# Em outro terminal
cd user-service
mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8082
Verifique no Eureka: USER-SERVICE deve ter 2 instâncias.
Parte 3: Product Service com Load Balancing (30 min)
3.1 Criar o Projeto
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>product-service</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.2 Configuração do RestTemplate com Load Balancing
package com.example.productservice.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
3.3 DTO para User
package com.example.productservice.dto;
public class UserDto {
private Long id;
private String username;
private String email;
private String firstName;
private String lastName;
// Getters et Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
}
3.4 Service para Chamar o User Service
package com.example.productservice.service;
import com.example.productservice.dto.UserDto;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Service
public class UserServiceClient {
private final RestTemplate restTemplate;
public UserServiceClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public UserDto getUserById(Long userId) {
// Usa o nome do serviço (não a URL!)
String url = "http://user-service/api/users/" + userId;
return restTemplate.getForObject(url, UserDto.class);
}
@SuppressWarnings("unchecked")
public Map<String, String> getUserServiceInfo() {
String url = "http://user-service/api/users/info";
return restTemplate.getForObject(url, Map.class);
}
}
3.5 Model Product
package com.example.productservice.model;
public class Product {
private Long id;
private String name;
private Double price;
private Long ownerId;
public Product() {}
public Product(Long id, String name, Double price, Long ownerId) {
this.id = id;
this.name = name;
this.price = price;
this.ownerId = ownerId;
}
// Getters et Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Double getPrice() { return price; }
public void setPrice(Double price) { this.price = price; }
public Long getOwnerId() { return ownerId; }
public void setOwnerId(Long ownerId) { this.ownerId = ownerId; }
}
3.6 DTO ProductWithOwner
package com.example.productservice.dto;
import com.example.productservice.model.Product;
public class ProductWithOwner {
private Product product;
private UserDto owner;
private String servedBy; // Para ver qual instância do User Service respondeu
public ProductWithOwner(Product product, UserDto owner, String servedBy) {
this.product = product;
this.owner = owner;
this.servedBy = servedBy;
}
// Getters et Setters
public Product getProduct() { return product; }
public void setProduct(Product product) { this.product = product; }
public UserDto getOwner() { return owner; }
public void setOwner(UserDto owner) { this.owner = owner; }
public String getServedBy() { return servedBy; }
public void setServedBy(String servedBy) { this.servedBy = servedBy; }
}
3.7 Controller
package com.example.productservice.controller;
import com.example.productservice.dto.ProductWithOwner;
import com.example.productservice.dto.UserDto;
import com.example.productservice.model.Product;
import com.example.productservice.service.UserServiceClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.PostConstruct;
import java.util.*;
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final UserServiceClient userServiceClient;
private final Map<Long, Product> products = new HashMap<>();
public ProductController(UserServiceClient userServiceClient) {
this.userServiceClient = userServiceClient;
}
@PostConstruct
public void init() {
products.put(1L, new Product(1L, "Laptop", 999.99, 1L));
products.put(2L, new Product(2L, "Phone", 699.99, 2L));
products.put(3L, new Product(3L, "Tablet", 499.99, 1L));
}
@GetMapping
public ResponseEntity<List<Product>> getAllProducts() {
return ResponseEntity.ok(new ArrayList<>(products.values()));
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
Product product = products.get(id);
if (product == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(product);
}
// Endpoint que chama o User Service com load balancing
@GetMapping("/{id}/with-owner")
public ResponseEntity<ProductWithOwner> getProductWithOwner(@PathVariable Long id) {
Product product = products.get(id);
if (product == null) {
return ResponseEntity.notFound().build();
}
// Chamada ao User Service (com load balancing!)
UserDto owner = userServiceClient.getUserById(product.getOwnerId());
// Ver qual instância respondeu
Map<String, String> serviceInfo = userServiceClient.getUserServiceInfo();
return ResponseEntity.ok(new ProductWithOwner(
product,
owner,
"user-service:" + serviceInfo.get("port")
));
}
// Testar load balancing - chama o User Service várias vezes
@GetMapping("/test-load-balancing")
public ResponseEntity<List<String>> testLoadBalancing() {
List<String> responses = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Map<String, String> info = userServiceClient.getUserServiceInfo();
responses.add("Call " + (i+1) + ": Served by port " + info.get("port"));
}
return ResponseEntity.ok(responses);
}
}
3.8 Configuração
# src/main/resources/application.yml
server:
port: 8083
spring:
application:
name: product-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.application.name}:${server.port}
3.9 Testar o Load Balancing
# Iniciar Product Service
cd product-service
mvn spring-boot:run
# Testar o load balancing
curl http://localhost:8083/api/products/test-load-balancing
Resultado esperado (Round Robin):
[
"Call 1: Served by port 8081",
"Call 2: Served by port 8082",
"Call 3: Served by port 8081",
"Call 4: Served by port 8082",
"Call 5: Served by port 8081",
"Call 6: Served by port 8082",
"Call 7: Served by port 8081",
"Call 8: Served by port 8082",
"Call 9: Served by port 8081",
"Call 10: Served by port 8082"
]
Parte 4: Teste de Resiliência (10 min)
4.1 Parar uma Instância
- Pare a instância do User Service na porta 8082 (Ctrl+C)
- Aguarde 30 segundos (tempo de evicção do Eureka)
- Verifique o dashboard do Eureka: apenas uma instância deve permanecer
- Teste novamente o load balancing:
curl http://localhost:8083/api/products/test-load-balancing
Resultado esperado:
[
"Call 1: Served by port 8081",
"Call 2: Served by port 8081",
"Call 3: Served by port 8081",
...
]
4.2 Reiniciar a Instância
cd user-service
mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8082
O load balancing volta automaticamente para as duas instâncias!
✅ Checklist de Validação
- Eureka Server inicia em http://localhost:8761
- User Service (instância 1) se registra na porta 8081
- User Service (instância 2) se registra na porta 8082
- Product Service se registra na porta 8083
- Dashboard do Eureka mostra 3 serviços (USER-SERVICE x2, PRODUCT-SERVICE x1)
- Load balancing funciona (alternância 8081/8082)
- Parada de uma instância → tráfego redirecionado para a outra
- Reinício → load balancing retomado
🎯 Exercícios Bônus
Exercício 1: Adicionar um Health Check Customizado
@Component
public class UserServiceHealthIndicator implements HealthIndicator {
@Override
public Health health() {
// Sua lógica de verificação
return Health.up()
.withDetail("users", "All users healthy")
.build();
}
}
Exercício 2: Adicionar Metadados no Eureka
eureka:
instance:
metadata-map:
version: "1.0.0"
region: "us-east-1"
Exercício 3: Configurar um Timeout Personalizado
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(3000);
factory.setReadTimeout(5000);
return new RestTemplate(factory);
}