In this blog post, we’ll walk through how to use @OpenFeign to connect one service with multiple microservices efficiently. We also see how to avoid hardcoded URLs using Eureka Server (service discovery).
Let’s get started! 🚀
What is OpenFeign?
OpenFeign is a declarative REST client that simplifies calling external services in a Spring Boot application. Instead of writing boilerplate code with RestTemplate
or WebClient
, you can just define an interface, annotate it with @FeignClient
, and let Spring handle the rest.
With OpenFeign, you can:
- Call multiple microservices using simple Java interfaces.
- Automatically handle HTTP requests and responses.
- Integrate with Spring Boot features like load balancing, retry mechanisms, and circuit breakers.
Why Use OpenFeign Instead of RestTemplate or WebClient?
Feature | OpenFeign | RestTemplate | WebClient |
---|---|---|---|
Ease of Use | ✅ Simple interface-based communication | ❌ Requires manual HTTP calls | ❌ Requires reactive programming |
Integration | ✅ Works with Spring Cloud features (Load Balancing, Circuit Breaker) | ❌ No built-in support for retries or resilience | ✅ Supports reactive programming |
Boilerplate Code | ✅ Minimal | ❌ More boilerplate | ✅ Less than RestTemplate but requires reactive concepts |
When to Choose OpenFeign?
- When your service needs to call multiple microservices frequently.
- When you want to minimize boilerplate code for making HTTP calls.
- When you need built-in support for load balancing and resilience.
Step 1: Create Spring Boot Projects
We will create three microservices:
- Customer Service – Calls two other services.
- User Service – Manages user details.
- Order Service – Manages orders placed by users.
For each service, create a Spring Boot project using Spring Initializr and include the following dependencies:
- Spring Web (for REST APIs)
- Spring Data JPA (for database interaction)
- Spring Boot DevTools (for development convenience)
- MySQL Driver (for database connection)
- Spring Cloud OpenFeign (for Customer Service only)
- Spring Boot Actuator (for monitoring)
2. Creating User Service
This service provides user information and interacts with a MySQL database.
User Entity:
import jakarta.persistence.*;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// add constructor, getter/setter methods
}
This class represents a User entity with an ID, name, and email.
User Repository:
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}
This repository handles database operations like fetching users by ID.
User Service:
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
}
This service contains business logic to retrieve a user from the database.
User Controller:
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public Optional<User> getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
}
The controller handles API requests and calls the service to fetch user data.
3. Creating Order Service
This service stores and retrieves order details.
Order Entity:
import jakarta.persistence.*;
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long orderId;
private Long userId;
private String product;
private Double price;
// add constructor, getter/setter methods
}
This class represents an Order, linked to a user ID.
Order Repository:
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByUserId(Long userId);
}
This repository fetches orders related to a specific user.
Order Service:
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public List<Order> getOrdersByUserId(Long userId) {
return orderRepository.findByUserId(userId);
}
}
This service handles business logic for fetching user orders.
Order Controller:
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/user/{userId}")
public List<Order> getOrdersByUserId(@PathVariable Long userId) {
return orderService.getOrdersByUserId(userId);
}
}
The controller manages API requests to retrieve user orders.
4. Creating Customer Service (Feign Client)
This service calls User Service and Order Service using OpenFeign.
Feign Clients:
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@FeignClient(name = "user-service", url = "http://localhost:8081")
public interface UserServiceClient {
@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Long id);
}
@FeignClient(name = "order-service", url = "http://localhost:8082")
public interface OrderServiceClient {
@GetMapping("/orders/user/{userId}")
List<Order> getOrdersByUserId(@PathVariable("userId") Long userId);
}
These interfaces define Feign clients for calling external services.
Customer Controller:
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/customers")
public class CustomerController {
private final UserServiceClient userServiceClient;
private final OrderServiceClient orderServiceClient;
public CustomerController(UserServiceClient userServiceClient, OrderServiceClient orderServiceClient) {
this.userServiceClient = userServiceClient;
this.orderServiceClient = orderServiceClient;
}
@GetMapping("/user/{id}/details")
public UserOrderResponse getUserDetailsWithOrders(@PathVariable Long id) {
User user = userServiceClient.getUserById(id);
List<Order> orders = orderServiceClient.getOrdersByUserId(id);
return new UserOrderResponse(user, orders);
}
}
Avoid hardcoded URLs using Eureka
To avoid hardcoded URLs, Eureka Server helps in service discovery, allowing microservices to dynamically find and communicate with each other.
Create Eureka Server
Add Dependencies (Eureka Server - pom.xml
)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
Enable Eureka Server
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);
}
}
Configure Eureka Server
server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
Run this service first.
Register Microservices in Eureka
Each microservice (User Service
, Order Service
, Customer Service
) must register with Eureka.
Add Eureka Client Dependency
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
Configure Eureka in application.properties
for Each Microservice
server.port=808X
spring.application.name=user-service
# Eureka Configuration
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
eureka.instance.prefer-ip-address=true
Replace 808X
with the actual service port (8081
for User Service, 8082
for Order Service, 8080
for Customer Service).
Updating Feign Clients to Use Eureka
With Eureka, we don’t need to hardcode service URLs. Instead, we specify the service name registered in Eureka.
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@FeignClient(name = "user-service")
public interface UserServiceClient {
@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Long id);
}
@FeignClient(name = "order-service")
public interface OrderServiceClient {
@GetMapping("/orders/user/{userId}")
List<Order> getOrdersByUserId(@PathVariable("userId") Long userId);
}
Why This Works?
- No Hardcoded URLs: Instead of
url = "http://localhost:8081"
, Eureka dynamically resolvesuser-service
. - Load Balancing: Spring Cloud LoadBalancer automatically distributes requests among service instances.
Creating Customer Service with OpenFeign
Enable Feign Clients and Eureka Discovery
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class CustomerServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CustomerServiceApplication.class, args);
}
}
Customer Controller Using Feign Clients
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/customers")
public class CustomerController {
private final UserServiceClient userServiceClient;
private final OrderServiceClient orderServiceClient;
public CustomerController(UserServiceClient userServiceClient, OrderServiceClient orderServiceClient) {
this.userServiceClient = userServiceClient;
this.orderServiceClient = orderServiceClient;
}
@GetMapping("/user/{id}/details")
public UserOrderResponse getUserDetailsWithOrders(@PathVariable Long id) {
User user = userServiceClient.getUserById(id);
List<Order> orders = orderServiceClient.getOrdersByUserId(id);
return new UserOrderResponse(user, orders);
}
}
Updated application.properties
for Customer Service
server.port=8080
spring.application.name=customer-service
# Eureka Configuration
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
eureka.instance.prefer-ip-address=true
Running and Testing the Microservices
Step 1: Start Eureka Server
Run EurekaServerApplication
. Access http://localhost:8761/ to see registered services.
Step 2: Start Microservices
Run User Service
, Order Service
, and Customer Service
. Ensure they register in Eureka Dashboard.
Step 3: Test API Gateway via Feign Clients
GET http://localhost:8080/customers/user/1/details
Expected Response:
{
"user": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
},
"orders": [
{"orderId": 101, "product": "Laptop", "price": 1200.00},
{"orderId": 102, "product": "Mouse", "price": 25.00}
]
}
Conclusion
With OpenFeign and Eureka, we removed hardcoded URLs and enabled dynamic service discovery. Now, microservices communicate seamlessly without knowing actual IPs or ports.
Key Takeaways:
✅ @FeignClient simplifies HTTP calls between microservices.
✅ Eureka Server enables dynamic service discovery.
✅ Spring Cloud LoadBalancer ensures requests are efficiently distributed.
By following this approach, you ensure scalability, maintainability, and reliability in microservices communication. Try implementing this setup and let us know your thoughts! 🚀
Comments
Post a Comment
Leave Comment