Commit 4a112bbf authored by Ashok Kumar K's avatar Ashok Kumar K

implemented place order endpoint and added spring microservices

parent d12232aa
plugins { plugins {
id 'org.springframework.boot' version '2.3.0.RELEASE' id 'org.springframework.boot' version '2.3.0.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE' id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java' id 'java'
} }
group = 'com.nisum' group = 'com.nisum'
...@@ -9,27 +9,44 @@ version = '0.0.1-SNAPSHOT' ...@@ -9,27 +9,44 @@ version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8' sourceCompatibility = '1.8'
configurations { configurations {
compileOnly { compileOnly {
extendsFrom annotationProcessor extendsFrom annotationProcessor
} }
} }
repositories { repositories {
mavenCentral() mavenCentral()
}
ext {
set('springCloudVersion', "Hoxton.SR4")
} }
dependencies { dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok' implementation 'org.springframework.cloud:spring-cloud-starter-config'
developmentOnly 'org.springframework.boot:spring-boot-devtools' implementation 'org.springframework.boot:spring-boot-starter-actuator'
annotationProcessor 'org.projectlombok:lombok' implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
testImplementation('org.springframework.boot:spring-boot-starter-test') { implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' compile group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
} compileOnly 'org.projectlombok:lombok'
// developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation "org.mapstruct:mapstruct:1.3.1.Final"
annotationProcessor "org.mapstruct:mapstruct-processor:1.3.1.Final"
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
} }
test { test {
useJUnitPlatform() useJUnitPlatform()
} }
#Fri May 29 12:02:22 IST 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
...@@ -2,12 +2,26 @@ package com.nisum.ecomorder; ...@@ -2,12 +2,26 @@ package com.nisum.ecomorder;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.data.mongodb.config.EnableMongoAuditing;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication @SpringBootApplication
@EnableMongoAuditing
@EnableFeignClients
public class EcomOrderApplication { public class EcomOrderApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(EcomOrderApplication.class, args); SpringApplication.run(EcomOrderApplication.class, args);
} }
@Bean
@LoadBalanced
public RestTemplate geRestTemplates(){
return new RestTemplate();
}
} }
package com.nisum.ecomorder.client;
import com.nisum.ecomorder.model.Customer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@Component
@Slf4j
public class CustomerClient {
@Autowired
private RestTemplate restTemplate;
private final String baseUrl = "http://ecom-customer/";
public Customer getCustomerById(String id) {
return restTemplate.getForEntity(baseUrl + "id/" + id, Customer.class).getBody();
}
}
package com.nisum.ecomorder.client;
import com.nisum.ecomorder.model.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "ecom-product", decode404 = true)
public interface ProductClient {
@GetMapping(value = "/id/{id}")
Product getProductById(@PathVariable("id") String id);
}
package com.nisum.ecomorder.config.mongodb;
import com.nisum.ecomorder.model.Order;
import com.nisum.ecomorder.repository.OrderLineRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
import org.springframework.stereotype.Component;
@Component
public class OrderDbOpsEventListener extends AbstractMongoEventListener<Order> {
@Autowired
OrderLineRepository orderLineRepository;
@Override
public void onBeforeConvert(BeforeConvertEvent<Order> event) {
super.onBeforeConvert(event);
Order order = event.getSource();
order.setLineItems(orderLineRepository.saveAll(order.getLineItems()));
}
}
package com.nisum.ecomorder.controller;
import com.nisum.ecomorder.model.Order;
import com.nisum.ecomorder.model.dto.PlaceOrderRequestDto;
import com.nisum.ecomorder.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RefreshScope
@RestController
public class OrderController {
@Autowired
OrderService orderService;
@PostMapping("/place-order")
public ResponseEntity<Order> placeOrder(@RequestBody @Valid PlaceOrderRequestDto placeOrderRequestDto) {
return ResponseEntity.ok(orderService.placeOrder(placeOrderRequestDto));
}
}
package com.nisum.ecomorder.exception;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import org.springframework.http.HttpStatus;
@Data
public class ClientError implements SubError{
private HttpStatus status;
private String message;
}
package com.nisum.ecomorder.exception;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatus;
import java.time.LocalDateTime;
import java.util.List;
@NoArgsConstructor
@Data
public class ErrorResponse {
private HttpStatus status;
private LocalDateTime timestamp;
private String message;
private String path;
private List<SubError> subErrors;
}
\ No newline at end of file
package com.nisum.ecomorder.exception;
public class NotFoundException extends OrderNotFoundException{
public NotFoundException() {
super();
}
public NotFoundException(String message) {
super(message);
}
public NotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
package com.nisum.ecomorder.exception;
public class OrderNotFoundException extends RuntimeException {
public OrderNotFoundException() {
super("Order not found");
}
public OrderNotFoundException(String message) {
super(message);
}
public OrderNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
package com.nisum.ecomorder.exception;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.FeignException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.client.HttpClientErrorException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@ControllerAdvice(basePackages = {"com.nisum.ecomorder"})
@Slf4j
public class OrderServiceExceptionHandler {
@Autowired
ObjectMapper objectMapper;
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFoundException(NotFoundException ex, HttpServletRequest httpServletRequest) {
return buildErrorResponseEntity(ex.getLocalizedMessage(), HttpStatus.NOT_FOUND, httpServletRequest);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest httpServletRequest) {
ErrorResponse errorResponse = buildErrorResponse("Invalid payload or params", HttpStatus.BAD_REQUEST, httpServletRequest);
List<SubError> subErrors = ex.getBindingResult().getFieldErrors()
.stream()
.map(fieldError -> new ValidationError(fieldError.getObjectName(), fieldError.getField(), fieldError.getRejectedValue(), fieldError.getDefaultMessage()))
.collect(Collectors.toList());
errorResponse.setSubErrors(subErrors);
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Object> handleConstraintViolationException(ConstraintViolationException ex, HttpServletRequest httpServletRequest) {
ErrorResponse errorResponse = buildErrorResponse("Invalid payload or params", HttpStatus.BAD_REQUEST, httpServletRequest);
List<SubError> subErrors = ex.getConstraintViolations().stream()
.map(violation -> new ValidationError(violation.getPropertyPath().toString(), violation.getPropertyPath().toString(), violation.getInvalidValue(), violation.getMessage()))
.collect(Collectors.toList());
errorResponse.setSubErrors(subErrors);
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException ex, HttpServletRequest httpServletRequest) {
return buildErrorResponseEntity(ex.getLocalizedMessage(), HttpStatus.BAD_REQUEST, httpServletRequest);
}
@ExceptionHandler(FeignException.class)
public ResponseEntity<ErrorResponse> handleFeignException(FeignException ex, HttpServletRequest httpServletRequest) throws JsonProcessingException {
String responseBodyString = ex.getMessage();
log.error(responseBodyString);
return new ResponseEntity<>(objectMapper.readValue(responseBodyString, ErrorResponse.class), HttpStatus.valueOf(ex.status()));
}
@ExceptionHandler(HttpClientErrorException.class)
public ResponseEntity<ErrorResponse> handleHttpClientErrorException(HttpClientErrorException ex, HttpServletRequest httpServletRequest) throws JsonProcessingException {
ErrorResponse errorResponse = buildErrorResponse(ex.getLocalizedMessage(), HttpStatus.INTERNAL_SERVER_ERROR, httpServletRequest);
// errorResponse.getSubErrors().add(objectMapper.readValue(ex.getMessage(), ClientError.class));
return new ResponseEntity<>(errorResponse, ex.getStatusCode());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception ex, HttpServletRequest httpServletRequest) {
return buildErrorResponseEntity(ex.getLocalizedMessage(), HttpStatus.INTERNAL_SERVER_ERROR, httpServletRequest);
}
private ResponseEntity<ErrorResponse> buildErrorResponseEntity(String message, HttpStatus httpstatus, HttpServletRequest httpServletRequest) {
ErrorResponse response = buildErrorResponse(message, httpstatus, httpServletRequest);
return new ResponseEntity<>(response, httpstatus);
}
private ErrorResponse buildErrorResponse(String message, HttpStatus httpstatus, HttpServletRequest servletRequest) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setStatus(httpstatus);
errorResponse.setTimestamp(LocalDateTime.now());
errorResponse.setMessage(message);
errorResponse.setPath(servletRequest.getRequestURI());
errorResponse.setSubErrors(Collections.emptyList());
return errorResponse;
}
}
package com.nisum.ecomorder.exception;
public interface SubError {
}
package com.nisum.ecomorder.exception;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Data
public class ValidationError implements SubError {
private String object;
private String field;
private Object rejectedValue;
private String message;
}
package com.nisum.ecomorder.model;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.springframework.data.mongodb.core.mapping.Field;
import javax.validation.constraints.NotBlank;
@Data
public class Address {
@Field("line1")
private String addressLine1;
@Field("line2")
private String addressLine2;
private String city;
private String state;
private String country;
@NotBlank(message = "zip code is mandatory")
@Length(max = 6, min = 5, message = "valid zip code is of 5 or 6 letters")
private String zipCode;
}
package com.nisum.ecomorder.model;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Setter;
@Data
@Setter(value = AccessLevel.PRIVATE)
public class Customer {
private String id;
private String firstName;
private String lastName;
private String email;
private String dayPhone;
}
package com.nisum.ecomorder.model;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;
import java.time.LocalDateTime;
import java.util.List;
@NoArgsConstructor
@Data
@TypeAlias("order")
@Document("orders")
public class Order {
@Id
private String id;
@CreatedDate
@Setter(value = AccessLevel.PRIVATE)
private LocalDateTime orderDate;
private String customerId;
private String customerName;
private String email;
private Address shipTo;
@DBRef
private List<OrderLine> lineItems;
private Double orderTotal;
}
package com.nisum.ecomorder.model;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
@Data
@TypeAlias("order_line")
@Document("orderlines")
public class OrderLine {
@Field("id")
@Id
private String id;
private int quantity;
private String productId;
private String productName;
private Double price;
private Double subTotal;
}
package com.nisum.ecomorder.model;
import lombok.Data;
@Data
public class Product {
private String id;
private String name;
private String description;
private Double price;
}
package com.nisum.ecomorder.model.dto;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class OrderLineDto {
private String id;
@NotBlank(message = "missing product id")
private String productId;
@Min(value = 1, message = "quantity must be at least one")
private int quantity;
private String productName;
@NotNull(message = "price can't be null")
@Min(value = 1, message = "price can't be negative")
private Double price;
}
package com.nisum.ecomorder.model.dto;
public class OrderResponseDto {
}
package com.nisum.ecomorder.model.dto;
import com.nisum.ecomorder.model.Address;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.Valid;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
@NoArgsConstructor
@Data
public class PlaceOrderRequestDto {
@NotBlank(message = "missing customer id")
private String customerId;
@Email
private String email;
@NotNull
@Valid
private Address shipTo;
@Size(min = 1, message = "order should contain at least one item")
@Valid
private List<OrderLineDto> lineItems;
}
package com.nisum.ecomorder.model.mapper;
import com.nisum.ecomorder.model.OrderLine;
import com.nisum.ecomorder.model.dto.OrderLineDto;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public abstract class OrderLineMapper {
public abstract OrderLine orderLineDtoToOrderLine(OrderLineDto orderLineDto);
}
package com.nisum.ecomorder.model.mapper;
import com.nisum.ecomorder.model.Order;
import com.nisum.ecomorder.model.dto.PlaceOrderRequestDto;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE, uses = {OrderLineMapper.class})
public abstract class OrderMapper {
public abstract Order placeOrderRequestDtoToOrder(PlaceOrderRequestDto requestDto);
}
package com.nisum.ecomorder.repository;
import com.nisum.ecomorder.model.OrderLine;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface OrderLineRepository extends MongoRepository<OrderLine,String> {
}
package com.nisum.ecomorder.repository;
import com.nisum.ecomorder.model.Order;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface OrderRepository extends MongoRepository<Order,String> {
}
package com.nisum.ecomorder.service;
import com.nisum.ecomorder.model.Order;
import com.nisum.ecomorder.model.dto.PlaceOrderRequestDto;
import org.springframework.stereotype.Service;
@Service
public interface OrderService {
Order placeOrder(PlaceOrderRequestDto placeOrderRequestDto);
}
package com.nisum.ecomorder.service.impl;
import com.nisum.ecomorder.client.CustomerClient;
import com.nisum.ecomorder.client.ProductClient;
import com.nisum.ecomorder.exception.NotFoundException;
import com.nisum.ecomorder.model.Customer;
import com.nisum.ecomorder.model.Order;
import com.nisum.ecomorder.model.OrderLine;
import com.nisum.ecomorder.model.Product;
import com.nisum.ecomorder.model.dto.PlaceOrderRequestDto;
import com.nisum.ecomorder.model.mapper.OrderMapper;
import com.nisum.ecomorder.repository.OrderRepository;
import com.nisum.ecomorder.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
OrderRepository orderRepository;
@Autowired
OrderMapper orderMapper;
@Autowired
ProductClient productClient;
@Autowired
CustomerClient customerClient;
@Override
public Order placeOrder(PlaceOrderRequestDto placeOrderRequestDto) {
Order order = orderMapper.placeOrderRequestDtoToOrder(placeOrderRequestDto);
order.setId(null);
try {
Customer customer = customerClient.getCustomerById(order.getCustomerId());
order.setCustomerName(customer.getFirstName() + " " + customer.getLastName());
} catch (HttpClientErrorException.NotFound ex) {
throw new NotFoundException("customer not found with ID: " + order.getCustomerId());
}
order.getLineItems().forEach(orderLine -> {
orderLine.setId(null);
String productId = orderLine.getProductId();
Product product = productClient.getProductById(productId);
if (product.getId() == null) throw new NotFoundException("product not found with ID: " + productId);
orderLine.setSubTotal(product.getPrice() * orderLine.getQuantity());
});
order.setOrderTotal(order.getLineItems().stream().mapToDouble(OrderLine::getSubTotal).sum());
return orderRepository.save(order);
}
}
logging:
level:
web: debug
com.nisum.ecomorder.client.ProductClient: debug
\ No newline at end of file
spring:
application:
name: ecom-order
profiles:
active: development
cloud:
config:
uri: http://localhost:8888
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment