Microservices with FeignClient and Data Provider

Gaining of knowledge

My career as a junior java developer is gaining momentum. I have recently learned a lot of new things. I am developing myself every day. I would like to share with you one scheme that I have been using every day lately.

In a microservices architecture, efficient communication between services is very important for a well-functioning system. Spring Boot provides several tools to make this process easier. One of them is FeignClient, which allows you to create HTTP clients that work like local method calls. When combined with a Data Provider, you can build a scalable and maintainable communication structure.

FeignClient is an interface that automatically generates HTTP requests to other microservices based on annotations. Instead of managing HTTP connections manually, you just define methods and their endpoints, and Feign does the rest.

A Data Provider is a pattern that centralizes the logic for delivering data. It can be an interface, with different implementations depending on the needs (e.g., different data sources). This pattern helps separate business logic from data access logic.

Example Implementation:

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import lombok.RequiredArgsConstructor;

// FeignClient to communicate with the UserService microservice
@FeignClient(name = "userService", url = "${user.service.url}")
public interface UserServiceClient {
    @GetMapping("/users/{id}")
    UserDTO getUserById(@PathVariable("id") Long id);
}

// Data Provider interface for user data
public interface UserDataProvider {
    UserDTO provideUser(Long userId);
}

// Implementation of the UserDataProvider interface
@Component
@RequiredArgsConstructor
public class UserDataProviderImpl implements UserDataProvider {

    private final UserServiceClient userServiceClient;

    @Override
    public UserDTO provideUser(Long userId) {
        return userServiceClient.getUserById(userId);
    }
}

// Service using UserDataProvider
@RequiredArgsConstructor
public class OrderService {

    private final UserDataProvider userDataProvider;

    public OrderDTO createOrder(Long userId) {
        UserDTO user = userDataProvider.provideUser(userId);
        // Logic to create an order based on user data
        return new OrderDTO(user, /* other order details */);
    }
}

UserServiceClient: A FeignClient that sends HTTP requests to the UserService microservice to get user data.
UserDataProvider: An interface that provides user data, making the service code more modular and easier to test.
UserDataProviderImpl: The implementation of UserDataProvider, which uses UserServiceClient to actually fetch the data.
OrderService: A class that uses UserDataProvider to get user data needed to create an order.
Why Is This Approach Beneficial?

  • Modularity: Using the UserDataProvider interface makes it easy to switch implementations (e.g., for testing purposes).
  • Easy Testing: By separating the data access logic into an interface, you can easily create mocks for UserDataProvider.
  • Clean Code: The OrderService class focuses on business logic, not on the details of communicating with other microservices.

With this approach, communication between microservices becomes simpler, more flexible, and scalable. You can easily manage complex dependencies and maintain high code quality.

Leave a Reply

Your email address will not be published. Required fields are marked *