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.
- Ensure the OrderService project is open in VS Code as part of the MicroservicesProject workspace.
-
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>
-
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); } }
- In
com.microservices.order_service
, create the following folders:client
- For Feign clients.model
- For theOrder
,User
, andProduct
models.repository
- ForOrderRepository
.service
- ForOrderService
.controller
- ForOrderController
.
-
In
com.microservices.order_service.client
, create an interface namedUserClient.java
to communicate withUserService
: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); }
@FeignClient
: Specifies that this is a Feign client forUserService
, with the URL set tohttp://user-service:8081/api/users
to match the Docker Compose configuration.getUserById(Long id)
: Maps to theGET /api/users/{id}
endpoint inUserService
to retrieve user details by ID.
-
In
com.microservices.order_service.client
, create an interface namedProductClient.java
to communicate withProductService
: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); }
@FeignClient
: Specifies that this is a Feign client forProductService
, with the URL set tohttp://product-service:8082/api/products
.getProductById(Long id)
: Maps to theGET /api/products/{id}
endpoint inProductService
to retrieve product details by ID.
-
In
com.microservices.order_service.model
, ensure theOrder
class includes getters and setters for all fields, includinguser
andproduct
. Here is an updated version of theOrder
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; } }
-
In
com.microservices.order_service.model
, ensure theProduct
class has aprice
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; } }
-
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; } }
-
In
OrderService.java
, inject the Feign clients and modify thecreateOrder
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; } }
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());
}
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());
- 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
- Start all services with Docker Compose:
docker-compose up --build
-
In Postman, test the
POST /api/orders
endpoint forOrderService
with the following JSON body to confirm it retrieves data fromUserService
andProductService
:{ "userId": 1, "productId": 1, "quantity": 2 }
userId
: The ID of the user placing the order (assumes a user with ID1
exists inUserService
).productId
: The ID of the product being ordered (assumes a product with ID1
exists inProductService
).quantity
: The quantity of the product being ordered (in this example,2
).
- 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.
-
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.
- Extend
-
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.
-
Startup Delay for Dependencies:
- Add a delay for
OrderService
startup in Docker Compose to ensure dependencies (UserService
andProductService
) initialize first. - Test to confirm smooth inter-service communication.
- Add a delay for