Skip to content

Commit

Permalink
Use Request and Response POJO instead of exposing Entity
Browse files Browse the repository at this point in the history
  • Loading branch information
mounikachindam3 committed Nov 11, 2023
1 parent bde7822 commit 3639cbb
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli.
package com.example.catalogservice.mapper;

import com.example.catalogservice.entities.Product;
import com.example.catalogservice.model.request.ProductRequest;
import com.example.catalogservice.model.response.ProductResponse;
import com.example.common.dtos.ProductDto;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;

@Mapper(componentModel = "spring")
public interface ProductMapper {
Expand All @@ -20,4 +22,14 @@ public interface ProductMapper {
Product toEntity(ProductDto productDto);

ProductResponse toProductResponse(Product product);

@Mapping(target = "id", ignore = true)
@Mapping(target = "inStock", ignore = true)
Product toEntity(ProductRequest productRequest);

Product toEntity(ProductResponse productResponse);

@Mapping(target = "inStock", ignore = true)
@Mapping(target = "id", ignore = true)
void mapProductWithRequest(ProductRequest productRequest, @MappingTarget Product product);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/***
<p>
Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli.
</p>
***/

package com.example.catalogservice.model.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Positive;

public record ProductRequest(
@NotBlank(message = "Product code can't be blank") String code,
String productName,
String description,
@Positive Double price) {}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ public record PagedResult<T>(
@JsonProperty("isLast") boolean isLast,
@JsonProperty("hasNext") boolean hasNext,
@JsonProperty("hasPrevious") boolean hasPrevious) {
public PagedResult(List<T> data, Page<T> page) {
public PagedResult(Page<T> page) {
this(
data,
page.getContent(),
page.getTotalElements(),
page.getNumber() + 1,
page.getTotalPages(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli.
import com.example.catalogservice.exception.ProductAlreadyExistsException;
import com.example.catalogservice.exception.ProductNotFoundException;
import com.example.catalogservice.mapper.ProductMapper;
import com.example.catalogservice.model.request.ProductRequest;
import com.example.catalogservice.model.response.InventoryDto;
import com.example.catalogservice.model.response.PagedResult;
import com.example.catalogservice.model.response.ProductResponse;
import com.example.catalogservice.repositories.ProductRepository;
import com.example.common.dtos.ProductDto;
import io.micrometer.observation.annotation.Observed;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.stream.function.StreamBridge;
Expand Down Expand Up @@ -73,56 +74,55 @@ public Mono<PagedResult<ProductResponse>> findAllProducts(
.flatMap(
tuple -> {
long count = tuple.getT1();
List<Product> products =
count == 0 ? Collections.emptyList() : tuple.getT2();

List<ProductResponse> productResponses =
products.isEmpty()
List<ProductResponse> products =
count == 0
? Collections.emptyList()
: products.stream()
: tuple.getT2().stream()
.map(productMapper::toProductResponse)
.toList();

if (count == 0) {
return Mono.just(
new PagedResult<>(
productResponses,
new PageImpl<>(productResponses, pageable, count)));
new PageImpl<>(products, pageable, count)));
}

List<String> productCodeList =
products.stream().map(Product::getCode).toList();
products.stream().map(ProductResponse::code).toList();

return getInventoryByProductCodes(productCodeList)
.collectMap(
InventoryDto::productCode,
InventoryDto::availableQuantity)
.map(
inventoriesMap -> {
updateProductAvailability(products, inventoriesMap);
List<ProductResponse> productResponseList =
products.stream()
.map(
productMapper
::toProductResponse)
.toList();
List<ProductResponse> updatedProducts =
updateProductAvailability(
products, inventoriesMap);
return new PagedResult<>(
productResponseList,
new PageImpl<>(
productResponseList,
pageable,
count));
updatedProducts, pageable, count));
});
});
}

private void updateProductAvailability(
List<Product> products, Map<String, Integer> inventoriesMap) {
products.forEach(
product -> {
int availableQuantity = inventoriesMap.getOrDefault(product.getCode(), 0);
product.setInStock(availableQuantity > 0);
});
private List<ProductResponse> updateProductAvailability(
List<ProductResponse> productResponses, Map<String, Integer> inventoriesMap) {
return productResponses.stream()
.map(
productResponse -> {
int availableQuantity =
inventoriesMap.getOrDefault(productResponse.code(), 0);
return new ProductResponse(
productResponse.id(),
productResponse.code(),
productResponse.productName(),
productResponse.description(),
productResponse.price(),
availableQuantity > 0);
})
.collect(Collectors.toList());
}

private Flux<InventoryDto> getInventoryByProductCodes(List<String> productCodeList) {
Expand Down Expand Up @@ -161,27 +161,33 @@ public Mono<ProductResponse> findProductByProductCode(String productCode) {

// saves product to db and sends message that new product is available for inventory
@Observed(name = "product.save", contextualName = "saving-product")
public Mono<ProductResponse> saveProduct(ProductDto productDto) {
return Mono.just(this.productMapper.toEntity(productDto))
public Mono<ProductResponse> saveProduct(ProductRequest productRequest) {
return Mono.just(this.productMapper.toEntity(productRequest))
.flatMap(productRepository::save)
.map(
savedProduct -> {
streamBridge.send("inventory-out-0", productDto);
streamBridge.send("inventory-out-0", productRequest);
return savedProduct;
})
.onErrorResume(
DuplicateKeyException.class,
e ->
// Handle unique key constraint violation here
Mono.error(new ProductAlreadyExistsException(productDto.code())))
Mono.error(
new ProductAlreadyExistsException(productRequest.code())))
.map(productMapper::toProductResponse);
}

public Mono<ProductResponse> updateProduct(ProductDto productDto, Long id) {
Product product = productMapper.toEntity(productDto);
product.setId(id);
Mono<Product> savedProduct = productRepository.save(product);
return savedProduct.map(productMapper::toProductResponse);
public Mono<ProductResponse> updateProduct(
ProductRequest productRequest, ProductResponse productResponse) {

Product product = productMapper.toEntity(productResponse);
// Update the post object with data from postRequest
productMapper.mapProductWithRequest(productRequest, product);

// Save the updated post object
Mono<Product> updatedProduct = productRepository.save(product);
return updatedProduct.map(productMapper::toProductResponse);
}

@Observed(name = "product.deleteById", contextualName = "deleteProductById")
Expand All @@ -199,7 +205,7 @@ public Mono<Boolean> productExistsByProductCodes(List<String> productCodes) {

@Transactional(readOnly = true)
@Observed(name = "product.findById", contextualName = "findById")
public Mono<Product> findById(Long id) {
return productRepository.findById(id);
public Mono<ProductResponse> findById(Long id) {
return productRepository.findById(id).map(productMapper::toProductResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli.
package com.example.catalogservice.web.controllers;

import com.example.catalogservice.config.logging.Loggable;
import com.example.catalogservice.mapper.ProductMapper;
import com.example.catalogservice.model.request.ProductRequest;
import com.example.catalogservice.model.response.PagedResult;
import com.example.catalogservice.model.response.ProductResponse;
import com.example.catalogservice.services.ProductService;
import com.example.catalogservice.utils.AppConstants;
import com.example.common.dtos.ProductDto;
import jakarta.validation.Valid;
import java.net.URI;
import java.util.List;
Expand All @@ -34,11 +33,9 @@ Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli.
public class ProductController {

private final ProductService productService;
private final ProductMapper productMapper;

public ProductController(ProductService productService, ProductMapper productMapper) {
public ProductController(ProductService productService) {
this.productService = productService;
this.productMapper = productMapper;
}

@GetMapping
Expand Down Expand Up @@ -92,9 +89,9 @@ public Mono<ResponseEntity<Boolean>> productExistsByProductCodes(

@PostMapping
public Mono<ResponseEntity<ProductResponse>> createProduct(
@RequestBody @Valid ProductDto productDto) {
@RequestBody @Valid ProductRequest productRequest) {
return productService
.saveProduct(productDto)
.saveProduct(productRequest)
.map(
product ->
ResponseEntity.created(
Expand All @@ -104,13 +101,13 @@ public Mono<ResponseEntity<ProductResponse>> createProduct(

@PutMapping("/{id}")
public Mono<ResponseEntity<ProductResponse>> updateProduct(
@PathVariable Long id, @RequestBody ProductDto productDto) {
@PathVariable Long id, @RequestBody ProductRequest productRequest) {
return productService
.findById(id)
.flatMap(
catalogObj ->
productService
.updateProduct(productDto, id)
.updateProduct(productRequest, catalogObj)
.map(ResponseEntity::ok))
.switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
}
Expand All @@ -119,7 +116,6 @@ public Mono<ResponseEntity<ProductResponse>> updateProduct(
public Mono<ResponseEntity<ProductResponse>> deleteProduct(@PathVariable Long id) {
return productService
.findById(id)
.map(productMapper::toProductResponse)
.flatMap(
product ->
productService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli.
import static org.hamcrest.CoreMatchers.is;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;

import com.example.catalogservice.entities.Product;
import com.example.catalogservice.exception.ProductNotFoundException;
import com.example.catalogservice.mapper.ProductMapper;
import com.example.catalogservice.model.request.ProductRequest;
import com.example.catalogservice.model.response.PagedResult;
import com.example.catalogservice.model.response.ProductResponse;
import com.example.catalogservice.services.ProductService;
Expand All @@ -41,7 +40,6 @@ class ProductControllerTest {
@Autowired private WebTestClient webTestClient;

@MockBean private ProductService productService;
@MockBean private ProductMapper productMapper;

private List<ProductResponse> productResponseList;

Expand All @@ -59,7 +57,7 @@ void setUp() {
@Test
void shouldFetchAllProducts() {
Page<ProductResponse> page = new PageImpl<>(productResponseList);
PagedResult<ProductResponse> pagedResult = new PagedResult<>(productResponseList, page);
PagedResult<ProductResponse> pagedResult = new PagedResult<>(page);
given(productService.findAllProducts(0, 10, "id", "asc"))
.willReturn(Mono.just(pagedResult));

Expand Down Expand Up @@ -131,7 +129,7 @@ void shouldReturn404WhenFetchingNonExistingProduct() {
void shouldCreateProduct() {
ProductResponse productResponse =
new ProductResponse(1L, "code 1", "name 1", "description 1", 9.0, true);
given(productService.saveProduct(any(ProductDto.class)))
given(productService.saveProduct(any(ProductRequest.class)))
.willReturn(Mono.just(productResponse));

ProductDto productDto = new ProductDto("code 1", "name 1", "description 1", 9.0);
Expand Down Expand Up @@ -194,8 +192,8 @@ void shouldUpdateProduct() {
ProductDto productDto = new ProductDto("code 1", "Updated name", "description 1", 9.0);
ProductResponse productResponse =
new ProductResponse(1L, "code 1", "Updated name", "description 1", 9.0, true);
given(productService.findById(productId)).willReturn(Mono.just(product));
given(productService.updateProduct(any(ProductDto.class), eq(1L)))
given(productService.findById(productId)).willReturn(Mono.just(productResponse));
given(productService.updateProduct(any(ProductRequest.class), any(ProductResponse.class)))
.willReturn(Mono.just(productResponse));

webTestClient
Expand Down Expand Up @@ -242,9 +240,8 @@ void shouldDeleteProduct() {
Product product = new Product(1L, "code 1", "Updated name", "description 1", 9.0, true);
ProductResponse productResponse =
new ProductResponse(1L, "code 1", "Updated name", "description 1", 9.0, true);
given(productService.findById(productId)).willReturn(Mono.just(product));
given(productService.findById(productId)).willReturn(Mono.just(productResponse));
given(productService.deleteProductById(product.getId())).willReturn(Mono.empty());
given(productMapper.toProductResponse(product)).willReturn(productResponse);

webTestClient
.delete()
Expand Down

0 comments on commit 3639cbb

Please sign in to comment.