Guided Exercise - Microservices - Declarative Programming - Spring OpenFeign #159
Replies: 24 comments
Deep-dive of Product Microservice ImplementationBelow is a detailed walk-through of a simplified yet production-grade Project Structure:POM.xml DependenciesYou would need to add these dependencies in your <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>Product.java (Model)Here we define our entity. package com.productservice.model;
import javax.persistence.Entity;
import javax.persistence.Id;
// This is our domain model
@Entity
public class Product {
@Id
private Long id;
private String name;
private double price;
// Getters and setters
// Override toString, equals and hashCode
}ProductRepository.java (Repository Interface)This is where Spring Data JPA magic happens. Just by extending package com.productservice.repository;
import com.productservice.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
// We can also add custom query methods here
}ProductService.java (Service Layer)package com.productservice.service;
import com.productservice.model.Product;
import com.productservice.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
public Product saveProduct(Product product) {
return productRepository.save(product);
}
// Implement other CRUD operations and any business logic here
}ProductController.java (Controller Layer)package com.productservice.controller;
import com.productservice.model.Product;
import com.productservice.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/products")
public class ProductController {
@Autowired
private ProductService productService;
@PostMapping
public ResponseEntity<Product> createProduct(@RequestBody Product product) {
Product savedProduct = productService.saveProduct(product);
return ResponseEntity.ok(savedProduct);
}
// Implement other CRUD APIs and possibly some APIs for querying
}Application.propertiesspring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2DialectIn this
With these, you have a production-grade |
Deep-dive of Order MicroserviceBelow is a deep dive into a simplified yet production-grade Project Structure:POM.xml DependenciesAdd the following dependencies to your <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>Order.java (Model)Here we define our entity package com.orderservice.model;
import javax.persistence.Entity;
import javax.persistence.Id;
// The @Entity annotation marks this class as a JPA entity
@Entity
public class Order {
@Id
private Long id;
private Long productId;
private int quantity;
// Getters and Setters, equals, hashCode, and toString
}OrderRepository.java (Repository)package com.orderservice.repository;
import com.orderservice.model.Order;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderRepository extends JpaRepository<Order, Long> {
}OrderService.java (Service Layer)This layer takes care of business logic. package com.orderservice.service;
import com.orderservice.model.Order;
import com.orderservice.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public Order saveOrder(Order order) {
return orderRepository.save(order);
}
// Implement other CRUD operations and business logic
}OrderController.java (Controller)Here we expose the API for orders. package com.orderservice.controller;
import com.orderservice.model.Order;
import com.orderservice.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody Order order) {
Order savedOrder = orderService.saveOrder(order);
return ResponseEntity.ok(savedOrder);
}
// Implement other CRUD APIs
}FeignConfig.java (Feign Configuration for Inter-Service Calls)In a more complex microservice system, the package com.orderservice.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import feign.Logger;
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}Application.propertiesspring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2DialectPoints to Note:
With these elements, you have a production-grade Best Practices:
This deep dive covers creating a production-grade microservice focusing on |
Deep-dive of the User MicroserviceLet's dive into the User Service and approach it from a production-grade angle. User Service Deep DiveCode Repository Structure:user-service
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── userservice
│ │ │ ├── config # Service Configuration
│ │ │ ├── controller # REST Controllers
│ │ │ ├── model # Data Models
│ │ │ ├── repository # Database Repositories
│ │ │ └── service # Business Logic
│ │ └── resources
│ │ └── application.properties # Application Properties
└── pom.xml # Project ConfigurationConfiguration:In your <!-- Add necessary dependencies here like JPA, Web, H2 Database -->application.properties:# Port and Database Configurations
server.port=8084
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2DialectData Models:@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
// getters and setters
}Repositories:public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}Business Logic:@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User createUser(User user) {
return userRepository.save(user);
}
public User findUserById(Long id) {
return userRepository.findById(id).orElseThrow(() -> new UserNotFoundException("User not found"));
}
}Controller:@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User newUser = userService.createUser(user);
return new ResponseEntity<>(newUser, HttpStatus.CREATED);
}
@GetMapping("/{id}")
public ResponseEntity<User> findUserById(@PathVariable Long id) {
User user = userService.findUserById(id);
return new ResponseEntity<>(user, HttpStatus.OK);
}
}Production-Grade Coding Angle:
public User createUser(User user) {
log.info("Creating user with username: {}", user.getUsername());
User createdUser = userRepository.save(user);
log.info("User created: {}", createdUser);
return createdUser;
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
By following these best practices, you ensure that the User Service is robust, maintainable, and fit for a production environment. Deep-dive of the Feign-client MicroserviceLet's deep-dive into a Feign client service, which acts as the client-side mechanism for communicating with other microservices. This service will be designed to talk to the Project Structure:POM.xml DependenciesHere is the minimum required dependency for a Spring Feign client: <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>OrderClient.java (Feign Client for Order Service)package com.feignclientservice.client;
import com.feignclientservice.model.Order;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
// Indicates that this interface is a Feign client with the service ID 'order-service'
@FeignClient(name = "order-service")
public interface OrderClient {
// Maps to /orders/{id} API of the order-service
@GetMapping("/orders/{id}")
Order getOrderById(@PathVariable("id") Long id);
// Implement other required APIs
}ProductClient.java (Feign Client for Product Service)package com.feignclientservice.client;
import com.feignclientservice.model.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
// Indicates that this is a Feign client with the service ID 'product-service'
@FeignClient(name = "product-service")
public interface ProductClient {
// Maps to /products/{id} API of the product-service
@GetMapping("/products/{id}")
Product getProductById(@PathVariable("id") Long id);
// Implement other required APIs
}FeignErrorDecoder.java (Feign Configuration)This class will handle error decoding when the Feign client encounters a problem. package com.feignclientservice.config;
import feign.Response;
import feign.codec.ErrorDecoder;
import org.springframework.context.annotation.Bean;
public class FeignErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
switch (response.status()) {
case 400:
// Return custom exception
case 404:
// Return custom exception
default:
return new Exception("Generic exception");
}
}
@Bean
public ErrorDecoder errorDecoder() {
return new FeignErrorDecoder();
}
}FeignClientServiceApplication.java (Main Application Class)package com.feignclientservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
// Enable Feign clients
@EnableFeignClients
@SpringBootApplication
public class FeignClientServiceApplication {
public static void main(String[] args) {
SpringApplication.run(FeignClientServiceApplication.class, args);
}
}Best Practices and Dos and Don'ts
Application.propertiesHere you can set various Feign client properties like timeouts: feign.client.config.default.connectTimeout=5000
feign.client.config.default.readTimeout=5000In this deep dive, you have seen how to set up a Feign client to communicate with other microservices in a production-grade manner. The Feign client service acts as the intermediary for invoking RESTful services in a type-safe way, abstracting the HTTP client code. |
Full workflowLet's continue by describing how this Feign client service can be integrated with other microservices and how it functions within a full workflow. After that, we'll discuss how to test the microservices. Full Workflow:
This workflow signifies a classic example where a Feign client is essential to enable communication between microservices. Test the Functionality:Test
|
|
Hi @akash-coded https://github.com/jay4tech/product-service http://localhost:8081/swagger-ui/index.html. -- Product Service |
|
Hi @akash-coded |
|
@akash-coded : https://github.com/arghyagiri/microservice-e2/blob/main/e-commerce-microservices-declarative-open-feign/Read.md |
|
@akash-coded |
|
Hi Akash, Nov 6th to Dec 6th Morning 7:30 batch |
|
Hello @akash-coded |
|
Hi Aakash, Attacing here the feign client project. Product and Order service is complete with Feign client connectivity which is working properly. The User service am yet to add full functionality yet. Name: Samik Thanks |
|
Name Bijumon Habeeb |
|
@akash_coded |
|
@akash-coded |
|
Hey Akash, dropping my implementation below. Name: Anubhav Ghosh |
|
Hi Akash,Please find implementation attached. |
|
Hi @akash-coded , Sharing my code in zip file for Microservices - Declarative Programming - Spring OpenFeign |
|
Hi @akash-coded , https://github.com/ranjanrkms/Microservices/tree/main/Ecom_FeignClient Name- Ranjan Kumar Pandit |
|
Hi Akash, Please find my code for the OpenFeign |
|
Hi Akash, Please find attached project for Open Feign Thanks, |
|
Hi @akash-coded Please find attached updated mid-assessment for author-service and book-service using Feign clients. Thanks |
|
Hi @akash-coded Thanks |
|
Hi @akash-coded Please find attached Feign client implementation. Thanks |
Uh oh!
There was an error while loading. Please reload this page.
Design a comprehensive Spring Boot microservices that incorporates Feign, custom error handling, and other aspects.
Scenario: E-commerce App
Imagine a simple e-commerce application where we have a
Product Service,Order Service, andUser Service.Product Service: Handles CRUD operations for products.Order Service: Handles CRUD operations for orders. Depends on theProduct Serviceto verify product availability.User Service: Manages user profiles.Architecture
Dependencies to include
For each microservice, include these dependencies in your
build.gradleorpom.xml:For Order Service, add OpenFeign:
Application Properties for Each Microservice
Product Service (src/main/resources/application.properties)
server.port=8081Order Service (src/main/resources/application.properties)
User Service (src/main/resources/application.properties)
server.port=8083Feign Configuration for Order Service
Create a custom
ErrorDecoderas previously described.Code Repository Structure
Testing the finished functionality
Here are some example API routes you could use to test:
POST http://localhost:8081/products{ "name": "iPhone", "price": 999.99 }POST http://localhost:8082/orders{ "userId": 1, "productId": 1 }GET http://localhost:8083/users/1You would expect:
Do's and Don'ts:
Optimization:
Rationalization & Internal Details
The
Order Serviceneeds to talk to theProduct Serviceto verify the availability of products. This is a classic use-case for using Spring Cloud Feign. Feign simplifies writing HTTP clients. The error decoder allows centralized control over exception handling.Note: This exercise should give you a holistic view of how to design your microservices with Spring Cloud Feign.
Below is a further detailed breakdown of each microservice, focusing on code snippets and how they interact.
1. Product Service
Model
Repository
Controller
2. User Service (Simplified)
Since the User service is not central to demonstrating Feign, we can assume it provides basic user profile functionality.
3. Order Service
Model
Repository
Feign Client to talk to Product Service
Controller
Error Decoder for Feign Client
Testing
You can start each service and then try to create an order using Postman or Curl. You should see that the Feign client is automatically handling the HTTP request to the Product service. If the product does not exist, the custom error decoder will kick in.
Postman Endpoints
http://localhost:8081/productshttp://localhost:8082/ordersCURL Example
Adding a product:
Creating an order:
Best Practices and Rationalization
By adhering to these practices, you can build a robust and maintainable microservices architecture. Feign plays a crucial role in simplifying HTTP calls between services, thus promoting maintainability and scalability.
Code Repository Structure
Here is a suggested code repository structure for each microservice:
How Microservices Will Interact
Order Serviceneeds product information before placing an order. It uses Feign to callProduct Service.Order ServiceandUser Service.Application Properties
For
Product Service(application.properties):server.port=8081For
Order Service(application.properties):Spring Open Feign
Spring Open Feign makes writing HTTP clients easier through declarative templates. Using annotations, you can create HTTP Requests—such as GET, POST—and automatically encode objects into JSON.
Advantages
Declarative Programming
Declarative programming is a paradigm where you express what you want to accomplish without specifying how to do it. In the context of Feign, you define a proxy interface and annotate it. Spring and Feign take care of the implementation.
Custom Error Handling
We have already implemented a
CustomErrorDecoderfor Feign in theOrder Service. This decodes the HTTP response status into custom exceptions.Best Practices, Dos, and Don'ts
Testing the Finished App
Use CURL or Postman to test the individual microservices, and then test them together.
http://localhost:8081/productswith JSON payload.http://localhost:8083/userswith JSON payload.http://localhost:8082/orderswith JSON payload.Each POST will return an object with an ID. Use these IDs to cross-verify if the services are interacting correctly.
This should provide a comprehensive view of how to build a simple yet educational set of microservices using Spring Boot and Feign.
All reactions