Skip to content

Latest commit

 

History

History
460 lines (361 loc) · 14.4 KB

File metadata and controls

460 lines (361 loc) · 14.4 KB

Lab 5: Inter-Microservice Communication - Calling Product and User Services from Order Service

Objective

Enhance the OrderService microservice to communicate with UserService and ProductService. This enables OrderService to retrieve user and product details, demonstrating inter-microservice communication with error handling and resilience.


Steps

1. Open OrderService in VS Code

  • Ensure the OrderService project is open in VS Code as part of the MicroservicesProject workspace.

2. Add OpenFeign and Resilience4j Dependencies

  • Open the pom.xml file for OrderService.

  • Add the following dependency management section to ensure compatibility with Spring Cloud and Resilience4j:

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2023.0.3</version> <!-- Adjust to match your Spring Boot version -->
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
  • Add the following dependencies for OpenFeign and Resilience4j:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>io.github.resilience4j</groupId>
        <artifactId>resilience4j-spring-boot3</artifactId>
        <version>2.0.2</version> <!-- Use the latest compatible version -->
    </dependency>

3. Enable Feign Clients in OrderService

  • In OrderServiceApplication.java, add the @EnableFeignClients annotation:

    package com.microservices.order_service;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @SpringBootApplication
    @EnableFeignClients(basePackages = "com.microservices.order_service.client")
    public class OrderServiceApplication {
        public static void main(String[] args) {
            SpringApplication.run(OrderServiceApplication.class, args);
        }
    }

4. Create Project Structure for Feign Clients and Models

  • In com.microservices.order_service, create the following folders:
    • client - For Feign clients.
    • model - For the Order, User, and Product models.
    • repository - For OrderRepository.
    • service - For OrderService.
    • controller - For OrderController.

5. Create Feign Client for UserService

  • In com.microservices.order_service.client, create an interface named UserClient.java to communicate with UserService:

    package com.microservices.order_service.client;
    
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    import com.microservices.order_service.model.User;
    
    @FeignClient(name = "user-service", url = "http://user-service:8081/api/users")
    public interface UserClient {
        @GetMapping("/{id}")
        User getUserById(@PathVariable("id") Long id);
    }

Explanation:

  • @FeignClient: Specifies that this is a Feign client for UserService, with the URL set to http://user-service:8081/api/users to match the Docker Compose configuration.
  • getUserById(Long id): Maps to the GET /api/users/{id} endpoint in UserService to retrieve user details by ID.

6. Create Feign Client for ProductService

  • In com.microservices.order_service.client, create an interface named ProductClient.java to communicate with ProductService:

    package com.microservices.order_service.client;
    
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    import com.microservices.order_service.model.Product;
    
    @FeignClient(name = "product-service", url = "http://product-service:8082/api/products")
    public interface ProductClient {
        @GetMapping("/{id}")
        Product getProductById(@PathVariable("id") Long id);
    }

Explanation:

  • @FeignClient: Specifies that this is a Feign client for ProductService, with the URL set to http://product-service:8082/api/products.
  • getProductById(Long id): Maps to the GET /api/products/{id} endpoint in ProductService to retrieve product details by ID.

7. Update Order and Product Classes with Necessary Getters and Setters

Order Class

  • In com.microservices.order_service.model, ensure the Order class includes getters and setters for all fields, including user and product. Here is an updated version of the Order class:

    package com.microservices.order_service.model;
    
    import jakarta.persistence.*;
    import java.time.LocalDateTime;
    
    @Entity
    public class Order {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private Long productId;
        private Long userId;
        private int quantity;
        private double totalAmount;
        private LocalDateTime orderDate;
        private String status;
    
        @Transient
        private User user;
    
        @Transient
        private Product product;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public Long getProductId() {
            return productId;
        }
    
        public void setProductId(Long productId) {
            this.productId = productId;
        }
    
        public Long getUserId() {
            return userId;
        }
    
        public void setUserId(Long userId) {
            this.userId = userId;
        }
    
        public int getQuantity() {
            return quantity;
        }
    
        public void setQuantity(int quantity) {
            this.quantity = quantity;
        }
    
        public double getTotalAmount() {
            return totalAmount;
        }
    
        public void setTotalAmount(double totalAmount) {
            this.totalAmount = totalAmount;
        }
    
        public LocalDateTime getOrderDate() {
            return orderDate;
        }
    
        public void setOrderDate(LocalDateTime orderDate) {
            this.orderDate = orderDate;
        }
    
        public String getStatus() {
            return status;
        }
    
        public void setStatus(String status) {
            this.status = status;
        }
    
        public User getUser() {
            return user;
        }
    
        public void setUser(User user) {
            this.user = user;
        }
    
        public Product getProduct() {
            return product;
        }
    
        public void setProduct(Product product) {
            this.product = product;
        }
    }

Product Class

  • In com.microservices.order_service.model, ensure the Product class has a price field and corresponding getter and setter methods for accessing it:

    package com.microservices.order_service.model;
    
    import jakarta.persistence.Entity;
    import jakarta.persistence.GeneratedValue;
    import jakarta.persistence.GenerationType;
    import jakarta.persistence.Id;
    
    @Entity
    public class Product {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String name;
        private String description;
        private double price;
    
        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 String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    
        public double getPrice() {
            return price;
        }
    
        public void setPrice(double price) {
            this.price = price;
        }
    }

User Class

  • In com.microservices.order_service.model:

    package com.microservices.order_service.model;
    
    import jakarta.persistence.Entity;
    import jakarta.persistence.GeneratedValue;
    import jakarta.persistence.GenerationType;
    import jakarta.persistence.Id;
    
    @Entity
    public class User {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String name;
        private String email;
    
        public String getEmail() {
            return email;
        }
    
        public Long getId() {
            return id;
        }
        public String getName() {
            return name;
        }
    }

8. Update Order Service Layer to Use Feign Clients and Resilience4j Circuit Breaker

  • In OrderService.java, inject the Feign clients and modify the createOrder method to fetch product and user data. Use @CircuitBreaker to handle fallback when services are unavailable:

    package com.microservices.order_service.service;
    
    import com.microservices.order_service.client.UserClient;
    import com.microservices.order_service.client.ProductClient;
    import com.microservices.order_service.model.Order;
    import com.microservices.order_service.model.User;
    import com.microservices.order_service.model.Product;
    import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
    import org.springframework.stereotype.Service;
    import java.time.LocalDateTime;
    
    @Service
    public class OrderService {
        private final OrderRepository orderRepository;
        private final UserClient userClient;
        private final ProductClient productClient;
    
        public OrderService(OrderRepository orderRepository, UserClient userClient, ProductClient productClient) {
            this.orderRepository = orderRepository;
            this.userClient = userClient;
            this.productClient = productClient;
        }
    
        @CircuitBreaker(name = "orderServiceCircuitBreaker", fallbackMethod = "orderFallback")
        public Order createOrder(Order order) {
            User user = userClient.getUserById(order.getUserId());
            Product product = productClient.getProductById(order.getProductId());
            
            order.setUser(user);
            order.setProduct(product);
            order.setTotalAmount(order.getQuantity() * product.getPrice());
            order.setOrderDate(LocalDateTime.now());
            order.setStatus("Pending");
    
            return orderRepository.save(order);
        }
    
        public Order orderFallback(Order order, Throwable throwable) {
            // Handle fallback and return a default response
            order.setStatus("Service unavailable");
            order.setTotalAmount(0);
            return order;
        }
    }

9. In Product Service project add the following

In ProductService.java

   public Optional<Product> getProductById(Long id) {
      return productRepository.findById(id);
  }

In ProductController.java

   @GetMapping("/{id}")
    public ResponseEntity<Product> getProductById(@PathVariable Long id) {
        System.out.println("Fetching product with ID: " + id);
        return productService.getProductById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

10. In User Service project add the following

In UserService.java

   public Optional<User> getUserById(Long id) {
      return userRepository.findById(id);
  }

In UserController.java

  @GetMapping("/{id}")
      public ResponseEntity<User> getUserById(@PathVariable Long id) {
        System.out.println("Fetching user with ID: " + id);
        return userService.getUserById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());

10. Update Docker Compose for Inter-Microservice Communication

  • In the MicroservicesProject directory, open docker-compose.yml.
  • Configure all services to communicate by using service names instead of localhost, and add startup delays for dependency handling.
    order-service:
        build: ./OrderService/order-service
        environment:
          SPRING_DATASOURCE_URL: jdbc:mysql://mysql-order:3306/order_db
          SPRING_DATASOURCE_USERNAME: root
          SPRING_DATASOURCE_PASSWORD: Test1234!
          SPRING_JPA_HIBERNATE_DDL_AUTO: update
        ports:
          - "8083:8083"
        depends_on:
          - mysql-order
          - user-service
          - product-service

11. Run Docker Compose

  • Start all services with Docker Compose:
    docker-compose up --build

12. Verify Inter-Microservice Communication

  • In Postman, test the POST /api/orders endpoint for OrderService with the following JSON body to confirm it retrieves data from UserService and ProductService:

    {
        "userId": 1,
        "productId": 1,
        "quantity": 2
    }
    • userId: The ID of the user placing the order (assumes a user with ID 1 exists in UserService).
    • productId: The ID of the product being ordered (assumes a product with ID 1 exists in ProductService).
    • quantity: The quantity of the product being ordered (in this example, 2).

13. Summary of Lab

  • Review what was covered in this lab:
    • Created inter-microservice communication with Feign.
    • Enhanced Docker Compose for service discovery.
    • Implemented Circuit Breaker for handling communication errors.

Extra Exercise (20 minutes):

  1. Order Summary Endpoint:

    • Extend OrderService to include an endpoint that generates an order summary, displaying user and product information in a customized format.
    • Test this new endpoint in Postman.
  2. User and Product Validation:

    • Validate if the user and product exist before an order is created. Implement error handling for invalid IDs and test with Postman.
  3. Startup Delay for Dependencies:

    • Add a delay for OrderService startup in Docker Compose to ensure dependencies (UserService and ProductService) initialize first.
    • Test to confirm smooth inter-service communication.