In this tutorial, we will build a simple shopping cart microservices project using Spring boot and Spring Cloud.
Let's first build three core microservices using Spring boot:
1. product-service
2. payment-service
3. order-service
Each of these microservices has its own MySQL database.
Build product-service Microservice using Spring Boot
1. Create and setup a spring boot project (product-service) in IntelliJ IDEA
Let's create a Spring boot project using the spring initializr.
GroupId: com.microservice
Artifact: productservice
Name: productservice
Description: Product Service for Microservice
Java version: 11 (you can choose Java 17)
Packaging: Jar
Here is the pom.xml file for your reference:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.microservice</groupId>
<artifactId>productservice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Product Service</name>
<description>Product Service for Microservice</description>
<properties>
<java.version>11</java.version>
</properties>
<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>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. Configure MySQL Database
Since we’re using MySQL as our database, we need to configure the URL, username, and password so that our Spring boot can establish a connection with the database on startup.
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/productdb
username: root
password: Mysql@123
jpa:
database-platform: org.hibernate.dialect.MySQL8Dialect
hibernate:
ddl-auto: update
application:
name: PRODUCT-SERVICE
Notice that we have also changed the tomcat server port using the below property:
server:
port: 8081
application:
name: PRODUCT-SERVICE
3. Create Product JPA Entity
package com.microservice.productservice.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long productId;
@Column(name = "PRODUCT_NAME")
private String productName;
@Column(name = "PRICE")
private long price;
@Column(name = "QUANTITY")
private long quantity;
}
4. Create Spring Data JPA Repository
package com.microservice.productservice.repository;
import com.microservice.productservice.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product,Long> {
}
5. Create Request and Response Payload Classes
ProductRequest
package com.microservice.productservice.payload.request;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class ProductRequest {
private String name;
private long price;
private long quantity;
}
ProductResponse
package com.microservice.productservice.payload.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProductResponse {
private String productName;
private long productId;
private long quantity;
private long price;
}
ErrorResponse
package com.microservice.productservice.payload.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ErrorResponse {
private String errorMessage;
private String errorCode;
}
6. Create Custom Exception
ProductServiceCustomException
package com.microservice.productservice.exception;
import lombok.Data;
@Data
public class ProductServiceCustomException extends RuntimeException{
private String errorCode;
public ProductServiceCustomException(String message, String errorCode) {
super(message);
this.errorCode = errorCode;
}
}
7. Handling Custom Exception
RestResponseEntityExceptionHandler
package com.microservice.productservice.exception;
import com.microservice.productservice.payload.response.ErrorResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(ProductServiceCustomException.class)
public ResponseEntity<ErrorResponse> handleProductServiceException(ProductServiceCustomException exception) {
return new ResponseEntity<>(new ErrorResponse().builder()
.errorMessage(exception.getMessage())
.errorCode(exception.getErrorCode())
.build(), HttpStatus.NOT_FOUND);
}
}
8. Create Service Layer
ProductService interface
package com.microservice.productservice.service;
import com.microservice.productservice.payload.request.ProductRequest;
import com.microservice.productservice.payload.response.ProductResponse;
public interface ProductService {
long addProduct(ProductRequest productRequest);
ProductResponse getProductById(long productId);
void reduceQuantity(long productId, long quantity);
public void deleteProductById(long productId);
}
ProductServiceImpl class
package com.microservice.productservice.service.impl;
import com.microservice.productservice.entity.Product;
import com.microservice.productservice.exception.ProductServiceCustomException;
import com.microservice.productservice.payload.request.ProductRequest;
import com.microservice.productservice.payload.response.ProductResponse;
import com.microservice.productservice.repository.ProductRepository;
import com.microservice.productservice.service.ProductService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import static org.springframework.beans.BeanUtils.copyProperties;
@Service
@RequiredArgsConstructor
@Log4j2
public class ProductServiceImpl implements ProductService {
private final ProductRepository productRepository;
@Override
public long addProduct(ProductRequest productRequest) {
log.info("ProductServiceImpl | addProduct is called");
Product product
= Product.builder()
.productName(productRequest.getName())
.quantity(productRequest.getQuantity())
.price(productRequest.getPrice())
.build();
product = productRepository.save(product);
log.info("ProductServiceImpl | addProduct | Product Created");
log.info("ProductServiceImpl | addProduct | Product Id : " + product.getProductId());
return product.getProductId();
}
@Override
public ProductResponse getProductById(long productId) {
log.info("ProductServiceImpl | getProductById is called");
log.info("ProductServiceImpl | getProductById | Get the product for productId: {}", productId);
Product product
= productRepository.findById(productId)
.orElseThrow(
() -> new ProductServiceCustomException("Product with given Id not found","PRODUCT_NOT_FOUND"));
ProductResponse productResponse
= new ProductResponse();
copyProperties(product, productResponse);
log.info("ProductServiceImpl | getProductById | productResponse :" + productResponse.toString());
return productResponse;
}
@Override
public void reduceQuantity(long productId, long quantity) {
log.info("Reduce Quantity {} for Id: {}", quantity,productId);
Product product
= productRepository.findById(productId)
.orElseThrow(() -> new ProductServiceCustomException(
"Product with given Id not found",
"PRODUCT_NOT_FOUND"
));
if(product.getQuantity() < quantity) {
throw new ProductServiceCustomException(
"Product does not have sufficient Quantity",
"INSUFFICIENT_QUANTITY"
);
}
product.setQuantity(product.getQuantity() - quantity);
productRepository.save(product);
log.info("Product Quantity updated Successfully");
}
@Override
public void deleteProductById(long productId) {
log.info("Product id: {}", productId);
if (!productRepository.existsById(productId)) {
log.info("Im in this loop {}", !productRepository.existsById(productId));
throw new ProductServiceCustomException(
"Product with given with Id: " + productId + " not found:",
"PRODUCT_NOT_FOUND");
}
log.info("Deleting Product with id: {}", productId);
productRepository.deleteById(productId);
}
}
9. Controller Layer
package com.microservice.productservice.controller;
import com.microservice.productservice.payload.request.ProductRequest;
import com.microservice.productservice.payload.response.ProductResponse;
import com.microservice.productservice.service.ProductService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/product")
@RequiredArgsConstructor
@Log4j2
public class ProductController {
private final ProductService productService;
@PostMapping
public ResponseEntity<Long> addProduct(@RequestBody ProductRequest productRequest) {
log.info("ProductController | addProduct is called");
log.info("ProductController | addProduct | productRequest : " + productRequest.toString());
long productId = productService.addProduct(productRequest);
return new ResponseEntity<>(productId, HttpStatus.CREATED);
}
@GetMapping("/{id}")
public ResponseEntity<ProductResponse> getProductById(@PathVariable("id") long productId) {
log.info("ProductController | getProductById is called");
log.info("ProductController | getProductById | productId : " + productId);
ProductResponse productResponse
= productService.getProductById(productId);
return new ResponseEntity<>(productResponse, HttpStatus.OK);
}
@PutMapping("/reduceQuantity/{id}")
public ResponseEntity<Void> reduceQuantity(
@PathVariable("id") long productId,
@RequestParam long quantity
) {
log.info("ProductController | reduceQuantity is called");
log.info("ProductController | reduceQuantity | productId : " + productId);
log.info("ProductController | reduceQuantity | quantity : " + quantity);
productService.reduceQuantity(productId,quantity);
return new ResponseEntity<>(HttpStatus.OK);
}
@DeleteMapping("/{id}")
public void deleteProductById(@PathVariable("id") long productId) {
productService.deleteProductById(productId);
}
}
10. Demo
Add Product:
Get Product By ID:
Build payment-service Microservice using Spring Boot
1. Create and setup a spring boot project (payment-service) in IntelliJ IDEA
Let's create a Spring boot project using the spring initializr.
GroupId: com.microservice
Artifact: paymentservice
Name: paymentservice
Description: Payment Service for Microservice
Java version: 11 (you can choose Java 17)
Packaging: Jar
Here is the pom.xml file for your reference:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.microservice</groupId>
<artifactId>paymentservice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>paymentservice</name>
<description>Payment Service for Microservice</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. Configure MySQL Database
Since we’re using MySQL as our database, we need to configure the URL, username, and password so that our Spring boot can establish a connection with the database on startup.
server:
port: 8083
spring:
datasource:
url: jdbc:mysql://localhost:3306/paymentdb
username: root
password: Mysql@123
driverClassName: com.mysql.cj.jdbc.Driver
jpa:
database-platform: org.hibernate.dialect.MySQL8Dialect
hibernate:
ddl-auto: update
application:
name: PAYMENT-SERVICE
Notice that we have also changed the tomcat server port using the below property:
server:
port: 8083
application:
name: PAYMENT-SERVICE
3. Create TransactionDetails JPA Entity
package com.microservice.paymentservice.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.Instant;
@Entity
@Table(name = "TRANSACTION_DETAILS")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TransactionDetails {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Column(name = "ORDER_ID")
private long orderId;
@Column(name = "MODE")
private String paymentMode;
@Column(name = "REFERENCE_NUMBER")
private String referenceNumber;
@Column(name = "PAYMENT_DATE")
private Instant paymentDate;
@Column(name = "STATUS")
private String paymentStatus;
@Column(name = "AMOUNT")
private long amount;
}
4. Create Spring Data JPA Repository
package com.microservice.paymentservice.repository;
import com.microservice.paymentservice.model.TransactionDetails;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface TransactionDetailsRepository extends JpaRepository<TransactionDetails, Long> {
Optional<TransactionDetails> findByOrderId(long orderId);
}
5. Create Request and Response Payload Classes
PaymentRequest
package com.microservice.paymentservice.payload;
import com.microservice.paymentservice.utils.PaymentMode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PaymentRequest {
private long orderId;
private long amount;
private String referenceNumber;
private PaymentMode paymentMode;
}
PaymentResponse
package com.microservice.paymentservice.payload;
import com.microservice.paymentservice.utils.PaymentMode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PaymentResponse {
private long paymentId;
private String status;
private PaymentMode paymentMode;
private long amount;
private Instant paymentDate;
private long orderId;
}
ErrorResponse
package com.microservice.paymentservice.payload;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ErrorResponse {
private String errorMessage;
private String errorCode;
}
6. Create Custom Exception
PaymentServiceCustomException
package com.microservice.paymentservice.exception;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class PaymentServiceCustomException extends RuntimeException{
private final String errorCode;
public PaymentServiceCustomException(String message, String errorCode) {
super(message);
this.errorCode = errorCode;
}
}
7. Handling Custom Exception
RestResponseEntityExceptionHandler
package com.microservice.paymentservice.exception;
import org.springframework.web.bind.annotation.ControllerAdvice;
import com.microservice.paymentservice.payload.ErrorResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(PaymentServiceCustomException.class)
public ResponseEntity<ErrorResponse> handleProductServiceException(PaymentServiceCustomException exception) {
return new ResponseEntity<>(ErrorResponse.builder()
.errorMessage(exception.getMessage())
.errorCode(exception.getErrorCode())
.build(), HttpStatus.NOT_FOUND);
}
}
8. Create Service Layer
PaymentService interface
package com.microservice.paymentservice.service;
import com.microservice.paymentservice.payload.PaymentRequest;
import com.microservice.paymentservice.payload.PaymentResponse;
public interface PaymentService {
long doPayment(PaymentRequest paymentRequest);
PaymentResponse getPaymentDetailsByOrderId(long orderId);
}
ProductServiceImpl class
package com.microservice.paymentservice.service;
import com.microservice.paymentservice.exception.PaymentServiceCustomException;
import com.microservice.paymentservice.model.TransactionDetails;
import com.microservice.paymentservice.payload.PaymentRequest;
import com.microservice.paymentservice.payload.PaymentResponse;
import com.microservice.paymentservice.repository.TransactionDetailsRepository;
import com.microservice.paymentservice.utils.PaymentMode;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import java.time.Instant;
@Service
@Log4j2
@RequiredArgsConstructor
public class PaymentServiceImpl implements PaymentService{
private final TransactionDetailsRepository transactionDetailsRepository;
@Override
public long doPayment(PaymentRequest paymentRequest) {
log.info("PaymentServiceImpl | doPayment is called");
log.info("PaymentServiceImpl | doPayment | Recording Payment Details: {}", paymentRequest);
TransactionDetails transactionDetails
= TransactionDetails.builder()
.paymentDate(Instant.now())
.paymentMode(paymentRequest.getPaymentMode().name())
.paymentStatus("SUCCESS")
.orderId(paymentRequest.getOrderId())
.referenceNumber(paymentRequest.getReferenceNumber())
.amount(paymentRequest.getAmount())
.build();
transactionDetails = transactionDetailsRepository.save(transactionDetails);
log.info("Transaction Completed with Id: {}", transactionDetails.getId());
return transactionDetails.getId();
}
@Override
public PaymentResponse getPaymentDetailsByOrderId(long orderId) {
log.info("PaymentServiceImpl | getPaymentDetailsByOrderId is called");
log.info("PaymentServiceImpl | getPaymentDetailsByOrderId | Getting payment details for the Order Id: {}", orderId);
TransactionDetails transactionDetails
= transactionDetailsRepository.findByOrderId(orderId)
.orElseThrow(() -> new PaymentServiceCustomException(
"TransactionDetails with given id not found",
"TRANSACTION_NOT_FOUND"));
PaymentResponse paymentResponse
= PaymentResponse.builder()
.paymentId(transactionDetails.getId())
.paymentMode(PaymentMode.valueOf(transactionDetails.getPaymentMode()))
.paymentDate(transactionDetails.getPaymentDate())
.orderId(transactionDetails.getOrderId())
.status(transactionDetails.getPaymentStatus())
.amount(transactionDetails.getAmount())
.build();
log.info("PaymentServiceImpl | getPaymentDetailsByOrderId | paymentResponse: {}", paymentResponse.toString());
return paymentResponse;
}
}
9. Controller Layer
package com.microservice.paymentservice.controller;
import com.microservice.paymentservice.payload.PaymentRequest;
import com.microservice.paymentservice.payload.PaymentResponse;
import com.microservice.paymentservice.service.PaymentService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/payment")
@RequiredArgsConstructor
@Log4j2
public class PaymentController {
private final PaymentService paymentService;
@PostMapping
public ResponseEntity<Long> doPayment(@RequestBody PaymentRequest paymentRequest) {
log.info("PaymentController | doPayment is called");
log.info("PaymentController | doPayment | paymentRequest : " + paymentRequest.toString());
return new ResponseEntity<>(
paymentService.doPayment(paymentRequest),
HttpStatus.OK
);
}
@GetMapping("/order/{orderId}")
public ResponseEntity<PaymentResponse> getPaymentDetailsByOrderId(@PathVariable long orderId) {
log.info("PaymentController | doPayment is called");
log.info("PaymentController | doPayment | orderId : " + orderId);
return new ResponseEntity<>(
paymentService.getPaymentDetailsByOrderId(orderId),
HttpStatus.OK
);
}
}
Build order-service Microservice using Spring Boot
1. Create and setup a spring boot project (product-service) in IntelliJ IDEA
Let's create a Spring boot project using the spring initializr.
GroupId: com.microservice
Artifact: orderservice
Name: orderservice
Description: Order Service for Microservice
Java version: 11 (you can choose Java 17)
Packaging: Jar
Here is the pom.xml file for your reference:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.microservice</groupId>
<artifactId>orderservice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>orderService</name>
<description>Order Service for Microservice</description>
<properties>
<java.version>11</java.version>
</properties>
<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>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. Configure MySQL Database
Since we’re using MySQL as our database, we need to configure the URL, username, and password so that our Spring boot can establish a connection with the database on startup.
server:
port: 8082
spring:
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:3306/orderdb
username: root
password: Mysql@123
driverClassName: com.mysql.cj.jdbc.Driver
jpa:
database-platform: org.hibernate.dialect.MySQL8Dialect
hibernate:
ddl-auto: update
application:
name: ORDER-SERVICE
Notice that we have also changed the tomcat server port using the below property:
server:
port: 8082
application:
name: ORDER-SERVICE
3. Create Order JPA Entity
package com.microservice.orderservice.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.Instant;
@Entity
@Table(name = "ORDER_DETAILS")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Column(name = "PRODUCT_ID")
private long productId;
@Column(name = "QUANTITY")
private long quantity;
@Column(name = "ORDER_DATE")
private Instant orderDate;
@Column(name = "STATUS")
private String orderStatus;
@Column(name = "TOTAL_AMOUNT")
private long amount;
}
4. Create Spring Data JPA Repository
package com.microservice.orderservice.repository;
import com.microservice.orderservice.model.Order;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderRepository extends JpaRepository<Order,Long> {
}
5. Create Request and Response Payload Classes
OrderRequest
package com.microservice.orderservice.payload.request;
import com.microservice.orderservice.utils.PaymentMode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class OrderRequest {
private long productId;
private long totalAmount;
private long quantity;
private PaymentMode paymentMode;
}
PaymentRequest
package com.microservice.orderservice.payload.request;
import com.microservice.orderservice.utils.PaymentMode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PaymentRequest {
private long orderId;
private long amount;
private String referenceNumber;
private PaymentMode paymentMode;
}
OrderResponse
package com.microservice.orderservice.payload.response;
import com.microservice.orderservice.utils.PaymentMode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class OrderResponse {
private long orderId;
private Instant orderDate;
private String orderStatus;
private long amount;
private ProductDetails productDetails;
private PaymentDetails paymentDetails;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class ProductDetails {
private String productName;
private long productId;
private long quantity;
private long price;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class PaymentDetails{
private long paymentId;
private PaymentMode paymentMode;
private String paymentStatus;
private Instant paymentDate;
}
}
PaymentResponse
package com.microservice.orderservice.payload.response;
import com.microservice.orderservice.utils.PaymentMode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PaymentResponse {
private long paymentId;
private String status;
private PaymentMode paymentMode;
private long amount;
private Instant paymentDate;
private long orderId;
}
ErrorResponse
package com.microservice.orderservice.payload.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ErrorResponse {
private String errorMessage;
private String errorCode;
}
6. Create Custom Exception
OrderServiceCustomException
package com.microservice.orderservice.exception;
import lombok.Data;
@Data
public class OrderServiceCustomException extends RuntimeException{
private String errorCode;
private int status;
public OrderServiceCustomException(String message, String errorCode, int status) {
super(message);
this.errorCode = errorCode;
this.status = status;
}
}
7. Handling Custom Exception
RestResponseEntityExceptionHandler
package com.microservice.orderservice.exception;
import com.microservice.orderservice.payload.response.ErrorResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(OrderServiceCustomException.class)
public ResponseEntity<ErrorResponse> handleCustomException(OrderServiceCustomException exception) {
return new ResponseEntity<>(new ErrorResponse().builder()
.errorMessage(exception.getMessage())
.errorCode(exception.getErrorCode())
.build(),
HttpStatus.valueOf(exception.getStatus()));
}
}
8. Create Service Layer
OrderService interface
package com.microservice.orderservice.service;
import com.microservice.orderservice.payload.request.OrderRequest;
import com.microservice.orderservice.payload.response.OrderResponse;
public interface OrderService {
long placeOrder(OrderRequest orderRequest);
OrderResponse getOrderDetails(long orderId);
}
OrderServiceImpl class
package com.microservice.orderservice.service.impl;
import com.microservice.orderservice.exception.OrderServiceCustomException;
import com.microservice.orderservice.external.client.PaymentService;
import com.microservice.orderservice.external.client.ProductService;
import com.microservice.orderservice.model.Order;
import com.microservice.orderservice.payload.request.OrderRequest;
import com.microservice.orderservice.payload.request.PaymentRequest;
import com.microservice.orderservice.payload.response.OrderResponse;
import com.microservice.orderservice.payload.response.PaymentResponse;
import com.microservice.orderservice.repository.OrderRepository;
import com.microservice.orderservice.service.OrderService;
import com.microservice.productservice.payload.response.ProductResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.time.Instant;
@Service
@Log4j2
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final OrderRepository orderRepository;
private final RestTemplate restTemplate;
@Override
public long placeOrder(OrderRequest orderRequest) {
log.info("OrderServiceImpl | placeOrder is called");
//Order Entity -> Save the data with Status Order Created
//Product Service - Block Products (Reduce the Quantity)
//Payment Service -> Payments -> Success-> COMPLETE, Else
//CANCELLED
log.info("OrderServiceImpl | placeOrder | Placing Order Request orderRequest : " + orderRequest.toString());
log.info("OrderServiceImpl | placeOrder | Creating Order with Status CREATED");
Order order = Order.builder()
.amount(orderRequest.getTotalAmount())
.orderStatus("CREATED")
.productId(orderRequest.getProductId())
.orderDate(Instant.now())
.quantity(orderRequest.getQuantity())
.build();
order = orderRepository.save(order);
log.info("OrderServiceImpl | placeOrder | Calling Payment Service to complete the payment");
PaymentRequest paymentRequest
= PaymentRequest.builder()
.orderId(order.getId())
.paymentMode(orderRequest.getPaymentMode())
.amount(orderRequest.getTotalAmount())
.build();
String orderStatus = null;
try {
log.info("OrderServiceImpl | placeOrder | Payment done Successfully. Changing the Oder status to PLACED");
orderStatus = "PLACED";
} catch (Exception e) {
log.error("OrderServiceImpl | placeOrder | Error occurred in payment. Changing order status to PAYMENT_FAILED");
orderStatus = "PAYMENT_FAILED";
}
order.setOrderStatus(orderStatus);
orderRepository.save(order);
log.info("OrderServiceImpl | placeOrder | Order Places successfully with Order Id: {}", order.getId());
return order.getId();
}
@Override
public OrderResponse getOrderDetails(long orderId) {
log.info("OrderServiceImpl | getOrderDetails | Get order details for Order Id : {}", orderId);
Order order
= orderRepository.findById(orderId)
.orElseThrow(() -> new OrderServiceCustomException("Order not found for the order Id:" + orderId,
"NOT_FOUND",
404));
log.info("OrderServiceImpl | getOrderDetails | Invoking Product service to fetch the product for id: {}", order.getProductId());
ProductResponse productResponse
= restTemplate.getForObject(
"http://PRODUCT-SERVICE/product/" + order.getProductId(),
ProductResponse.class
);
log.info("OrderServiceImpl | getOrderDetails | Getting payment information form the payment Service");
PaymentResponse paymentResponse
= restTemplate.getForObject(
"http://PAYMENT-SERVICE/payment/order/" + order.getId(),
PaymentResponse.class
);
OrderResponse.ProductDetails productDetails
= OrderResponse.ProductDetails
.builder()
.productName(productResponse.getProductName())
.productId(productResponse.getProductId())
.build();
OrderResponse.PaymentDetails paymentDetails
= OrderResponse.PaymentDetails
.builder()
.paymentId(paymentResponse.getPaymentId())
.paymentStatus(paymentResponse.getStatus())
.paymentDate(paymentResponse.getPaymentDate())
.paymentMode(paymentResponse.getPaymentMode())
.build();
OrderResponse orderResponse
= OrderResponse.builder()
.orderId(order.getId())
.orderStatus(order.getOrderStatus())
.amount(order.getAmount())
.orderDate(order.getOrderDate())
.productDetails(productDetails)
.paymentDetails(paymentDetails)
.build();
log.info("OrderServiceImpl | getOrderDetails | orderResponse : " + orderResponse.toString());
return orderResponse;
}
}
9. Controller Layer
package com.microservice.orderservice.controller;
import com.microservice.orderservice.payload.request.OrderRequest;
import com.microservice.orderservice.payload.response.OrderResponse;
import com.microservice.orderservice.service.OrderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/order")
@Log4j2
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@PostMapping("/placeorder")
public ResponseEntity<Long> placeOrder(@RequestBody OrderRequest orderRequest) {
log.info("OrderController | placeOrder is called");
log.info("OrderController | placeOrder | orderRequest: {}", orderRequest.toString());
long orderId = orderService.placeOrder(orderRequest);
log.info("Order Id: {}", orderId);
return new ResponseEntity<>(orderId, HttpStatus.OK);
}
@GetMapping("/{orderId}")
public ResponseEntity<OrderResponse> getOrderDetails(@PathVariable long orderId) {
log.info("OrderController | getOrderDetails is called");
OrderResponse orderResponse
= orderService.getOrderDetails(orderId);
log.info("OrderController | getOrderDetails | orderResponse : " + orderResponse.toString());
return new ResponseEntity<>(orderResponse,
HttpStatus.OK);
}
}
Comments
Post a Comment
Leave Comment