avatarNnyw

Summarize

Spring Boot | Microservices

Feign for Intercommunication in Spring Boot Microservices 🚀 — Part 2

In this section, we’ll introduce a new Spring Boot microservice for order management. We’ll demonstrate how this service can seamlessly communicate with our existing product management service using Feign.

Introduction to Feign

Feign is a declarative web service client, simplifying the process of connecting one microservice to another. Here’s why it stands out:

  • Declarative Nature: Instead of writing verbose HTTP client code, you declare a Java interface representing the remote service. Annotations guide how requests are made.
  • Built on Robust Foundations: Feign integrates with Ribbon for load balancing and Hystrix for circuit breaking, ensuring resilience in a microservices ecosystem.
  • Consistency and Reusability: By defining services as interfaces, you can ensure consistent communication patterns and reuse these interfaces across multiple services.

If you followed the tutorial in Part I, you should now have a working order management service. In this tutorial, we will jump straight to understanding how to communicate between two microservices in Spring Boot.

Here’s a step-by-step guide to setting up Feign in a Spring Boot application:

1. Add Dependencies:

Add the necessary dependencies to your pom.xml (assuming you're using Maven):

<!-- Spring Cloud Starter OpenFeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>3.1.8</version>
</dependency>

<!-- Spring Cloud Dependencies (BOM) -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2020.0.3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

2. Enable Feign:

In your main Spring Boot application class (the one annotated with @SpringBootApplication), add the @EnableFeignClients annotation:

@SpringBootApplication
@EnableFeignClients
public class OrdermgmtApplication {

 public static void main(String[] args) {
  SpringApplication.run(OrdermgmtApplication.class, args);
 }
}

3. Define Feign Client:

Create two new variables in application.properties to define the product service URL.

server.port=8082
spring.profiles.active=dev

products.url=http://localhost:8081/api/products
product-service.name=product-service

Create an interface to represent the Feign client. Use annotations to define the service you want to call:

@FeignClient(name = "${product-service.name}", url = "${products.url}")
public interface ProductClient {
    @GetMapping("/{id}")
    ResponseEntity<Map<String, Object>> getProductById(@PathVariable("id") Long id);
}

Here, @FeignClient denotes the remote service. If url is provided, Feign directly uses it, bypassing service discovery. If only name is given, Feign employs service discovery to locate the service.

The method inside the interface represents the endpoint you want to call. Use Spring MVC annotations like @GetMapping, @PathVariable, etc., to define the endpoint.

4. Use the Feign Client:

You can now @Autowire the Feign client in the order services and use it to make HTTP calls:

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private ProductClient productClient;
    
    @Autowired
    private ObjectMapper objectMapper;
    ...
 
    @Override
    public OrderDto getOrderById(Long id) {
        logger.info("Fetching order by ID: {}", id);
        Order order = orderRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Order not found"));

        OrderDto orderResponse = new ModelMapper().map(order, OrderDto.class);

        ResponseEntity<Map<String, Object>> response = productClient.getProductById(id);
        if (response != null && response.getBody() != null) {
            // Extract the ProductDto list from the response
            ProductDto productDto = objectMapper.convertValue(response.getBody().get("data"), ProductDto.class);
            orderResponse.setProductDto(productDto);
        }

        return orderResponse;
    }
}

5. Configure Feign (Optional):

We can customize Feign’s behaviour by setting properties in the application.properties file.

Timeouts: You can configure the connect and read timeouts for Feign clients using the following properties:

feign.client.config.default.connectTimeout=5000 # (ms)
feign.client.config.default.readTimeout=5000
  • Connect timeout: The connect timeout is the maximum amount of time (in milliseconds) that Feign will wait for a connection to be established to the remote server. If the connection is not established within the specified timeout, a FeignException will be thrown.
  • Read timeout: The read timeout is the maximum amount of time (in milliseconds) that Feign will wait for a response from the remote server. If the response is not received within the specified timeout, a FeignException will be thrown.

By default, the connect timeout is 1000 milliseconds and the read timeout is 60000 milliseconds.

Logger Levels: Feign provides logging capabilities to help you debug requests:

logging.level.<your-feign-client-class-name>=DEBUG  
feign.client.config.default.loggerLevel=full

Feign’s logger levels include:

  • NONE: No logging (default).
  • BASIC: Log only the request method and URL and the response status code and execution time.
  • HEADERS: Log the basic information along with request and response headers.
  • FULL: Log the headers, body, and metadata for both request and response.

Error Decoder: Customize how Feign handles responses with error status codes by providing a custom error decoder.

feign.client.config.default.errorDecoder=<your-custom-error-decoder-class-name>

By default, Feign throws a FeignException for responses with HTTP status 400 or higher. A custom error decoder can be used to handle these responses differently.

To create a custom error decoder, you need to implement the ErrorDecoder interface. The ErrorDecoder interface has a single method, decode, which takes a FeignException as its input and returns an object of type Response.

public class CustomErrorDecoder implements ErrorDecoder {
    @Override
    public Response decode(FeignException e) {
        // Get the status code from the FeignException
        int statusCode = e.status();

        // Check if the status code is an error code
        if (statusCode >= 400) {
            // Create a custom response object
            Response response = new Response();
            response.setStatusCode(statusCode);
            response.setBody(e.getMessage());

            // Return the custom response object
            return response;
        } else {
            // Rethrow the FeignException
            throw e;
        }
    }
}

Request Interceptors: Interceptors allow you to modify every request made by a Feign client. This can be useful for adding headers, authentication tokens, or any other modifications to every request.

feign.client.config.default.requestInterceptors=<your-custom-request-interceptor-cl

To create a custom request interceptor, you need to implement the RequestInterceptor interface. The RequestInterceptor interface has a single method, apply, which takes a RequestTemplate as its input and returns a RequestTemplate.

The following code shows an example of a custom request interceptor that adds an authorization header to every request:

@Component
public class CustomRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        // Add an Authorization header to every request
        requestTemplate.header("Authorization", "Bearer YOUR_TOKEN_HERE");
    }
}

To use this interceptor, you can simply annotate it with @Component, and Spring will automatically detect and apply it to all Feign clients in the application.

Testing

To test the microservices; First, we need to start the product service and insert one product record.

Once we have inserted one product record, we can now test the order service. Insert one order record with the productId set to 1.

Next, let’s test the order service by making a GET request to the /orders/1 endpoint to retrieve the order record with the ID of 1.

You should see the product details returned in the productDto object. This proves that our inter-communication using Feign is working correctly.

Now that we have completed the development of our microservices, we are ready to deploy them to the AWS cloud environment. However, before we do that, we need to create a MySQL database to persist the data. Currently, we are using the H2 in-memory database in a development environment. This means that the data will be lost when we restart or stop the service.

Create RDS MySQL Database

In production, we are going to connect our services to a MySQL database running on Amazon Relational Database Service (RDS). RDS is a fully managed service that makes it easy to set up, operate, and scale a relational database in the cloud.

Here are the steps involved:

  1. Go to the Amazon RDS console.
  2. Click on the “Databases” tab.
  3. Click on the “Create Database” button.
  4. In the “Create Database” dialog box, select the “Standard Create” option and the “MySQL” database engine.
  5. Choose a template, such as the “Free Tier” template.
  6. Provide a database instance identifier, such as “springboot-micros”.
  7. In the “Credential Settings” section, configure the master username and password.
  8. In the “Database Instance Type” section, select a database instance type, such as the “Micro” instance type.
  9. In the “Storage” section, do not change the settings.
  10. In the “Connectivity” section, select the default VPC and subnet group.
  11. In the “Public Access” section, do not enable public access.
  12. In the “Security Group” section, create a new security group named springboot-micros-sg.
  13. Do not select any preference for the availability zone.
  14. In the “Database Authentication” section, use password authentication.
  15. In the “Additional Configuration” section, provide the initial database name, such as springbootmicros.
  16. Leave the database parameter group and option group as is.
  17. Disable automated backups.
  18. Skip local instance maintenance.
  19. Click on the “Create Database” button.

Now we have created the database, but we are not ready to connect to it yet. We need to deploy the services into the AWS cloud, and then configure the security inbound rule to allow traffic from the microservices. We will do it in the next tutorial.

Next Steps

In the following tutorial, we will walk you through the process of deploying two microservices to AWS ECS with Fargate. We will also show you how to use service discovery mechanisms like CloudMap Service Connect to manage communication between services. Additionally, we will cover how to set up load balancing and autoscaling to handle traffic. Finally, we will optionally discuss how to use Route 53 to customize the domain name of the load balancer and configure HTTPS/SSL.

Happy coding! 🎉

  • Leave a comment if you need the full source code.
  • Clap if you find this tutorial useful.
  • Follow me to get notified when I publish new tutorials.
  • Please buy me a coffee to support me.
Spring Boot
Java
Microservices
Fargate
AWS
Recommended from ReadMedium