Pular para o conteúdo principal

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​

  1. Pare a instância do User Service na porta 8082 (Ctrl+C)
  2. Aguarde 30 segundos (tempo de evicção do Eureka)
  3. Verifique o dashboard do Eureka: apenas uma instância deve permanecer
  4. 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);
}

📚 Recursos​


Próximo TP: Config Server →

Voltar ao Módulo 13