From b377cdb5c4af6f1e7fa26c8d4b91395a7035c729 Mon Sep 17 00:00:00 2001 From: abdotalaat Date: Mon, 13 Apr 2020 23:15:37 +0400 Subject: [PATCH 1/7] Set mongodb auto-index-creation true --- product-service/src/main/resources/application.yaml | 4 ++++ .../java/com/siriusxi/ms/store/ps/PersistenceTests.java | 3 --- recommendation-service/src/main/resources/application.yaml | 1 + .../java/com/siriusxi/ms/store/rs/PersistenceTests.java | 7 +++---- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/product-service/src/main/resources/application.yaml b/product-service/src/main/resources/application.yaml index 0c7cdc9d..298a30ee 100644 --- a/product-service/src/main/resources/application.yaml +++ b/product-service/src/main/resources/application.yaml @@ -6,6 +6,10 @@ spring: host: localhost port: 27017 database: product-db + auto-index-creation: true + + + server: port: 9081 diff --git a/product-service/src/test/java/com/siriusxi/ms/store/ps/PersistenceTests.java b/product-service/src/test/java/com/siriusxi/ms/store/ps/PersistenceTests.java index 87f5deb6..6996252e 100644 --- a/product-service/src/test/java/com/siriusxi/ms/store/ps/PersistenceTests.java +++ b/product-service/src/test/java/com/siriusxi/ms/store/ps/PersistenceTests.java @@ -4,7 +4,6 @@ import com.siriusxi.ms.store.ps.persistence.ProductRepository; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; @@ -76,9 +75,7 @@ public void getByProductId() { assertEqualsProduct(savedEntity, entity.get()); } - //FIXME error which is not thrown @Test - @Disabled public void duplicateError() { Assertions.assertThrows( diff --git a/recommendation-service/src/main/resources/application.yaml b/recommendation-service/src/main/resources/application.yaml index f7b79587..02460b21 100644 --- a/recommendation-service/src/main/resources/application.yaml +++ b/recommendation-service/src/main/resources/application.yaml @@ -6,6 +6,7 @@ spring: host: localhost port: 27017 database: recommendation-db + auto-index-creation: true server: port: 9082 diff --git a/recommendation-service/src/test/java/com/siriusxi/ms/store/rs/PersistenceTests.java b/recommendation-service/src/test/java/com/siriusxi/ms/store/rs/PersistenceTests.java index 53123a84..7c258600 100644 --- a/recommendation-service/src/test/java/com/siriusxi/ms/store/rs/PersistenceTests.java +++ b/recommendation-service/src/test/java/com/siriusxi/ms/store/rs/PersistenceTests.java @@ -1,14 +1,14 @@ package com.siriusxi.ms.store.rs; -import com.mongodb.DuplicateKeyException; + import com.siriusxi.ms.store.rs.persistence.RecommendationEntity; import com.siriusxi.ms.store.rs.persistence.RecommendationRepository; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; +import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.OptimisticLockingFailureException; import java.util.List; @@ -73,9 +73,8 @@ public void getByProductId() { assertEqualsRecommendation(savedEntity, entityList.get(0)); } - //FIXME error which is not thrown + @Test - @Disabled public void duplicateError() { Assertions.assertThrows(DuplicateKeyException.class, From 66b017a52b8cbb9ec0a444e0a082ce4977d51994 Mon Sep 17 00:00:00 2001 From: Mohamed Taman Date: Mon, 13 Apr 2020 22:16:57 +0200 Subject: [PATCH 2/7] Update application.yaml --- product-service/src/main/resources/application.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/product-service/src/main/resources/application.yaml b/product-service/src/main/resources/application.yaml index 298a30ee..11a6c7ba 100644 --- a/product-service/src/main/resources/application.yaml +++ b/product-service/src/main/resources/application.yaml @@ -7,10 +7,7 @@ spring: port: 27017 database: product-db auto-index-creation: true - - - - + server: port: 9081 @@ -43,4 +40,4 @@ spring: host: mongodb server: - port: 8080 \ No newline at end of file + port: 8080 From 2de670a106e1bab93934f6cf128f36b2ba0aef8f Mon Sep 17 00:00:00 2001 From: Mohamed Taman Date: Tue, 14 Apr 2020 00:07:14 +0200 Subject: [PATCH 3/7] Product service decomposed. --- .../ms/store/ps/api/ProductController.java | 56 ++++++++++++++++++ .../ProductMapper.java | 4 +- .../ProductServiceImpl.java | 12 ++-- .../com/siriusxi/ms/store/ps/MapperTests.java | 6 +- .../ps/ProductServiceApplicationTests.java | 13 ++--- .../revs/ReviewServiceApplicationTests.java | 8 +-- .../api/core/product/ProductService.java | 45 +++++++------- .../api/core/product/ProductServiceApi.java | 58 +++++++++++++++++++ .../api/core/product/{ => dto}/Product.java | 2 +- .../pcs/controller/StoreServiceImpl.java | 2 +- .../pcs/integration/StoreIntegration.java | 2 +- .../pcs/StoreServiceApplicationTests.java | 2 +- 12 files changed, 164 insertions(+), 46 deletions(-) create mode 100644 product-service/src/main/java/com/siriusxi/ms/store/ps/api/ProductController.java rename product-service/src/main/java/com/siriusxi/ms/store/ps/{controller => service}/ProductMapper.java (83%) rename product-service/src/main/java/com/siriusxi/ms/store/ps/{controller => service}/ProductServiceImpl.java (88%) create mode 100644 store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductServiceApi.java rename store-api/src/main/java/com/siriusxi/ms/store/api/core/product/{ => dto}/Product.java (83%) diff --git a/product-service/src/main/java/com/siriusxi/ms/store/ps/api/ProductController.java b/product-service/src/main/java/com/siriusxi/ms/store/ps/api/ProductController.java new file mode 100644 index 00000000..f5486fbf --- /dev/null +++ b/product-service/src/main/java/com/siriusxi/ms/store/ps/api/ProductController.java @@ -0,0 +1,56 @@ +package com.siriusxi.ms.store.ps.api; + +import com.siriusxi.ms.store.api.core.product.ProductService; +import com.siriusxi.ms.store.api.core.product.ProductServiceApi; +import com.siriusxi.ms.store.api.core.product.dto.Product; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.web.bind.annotation.RestController; + +/** + * Class ProductController is the implementation of the main product API definition. + * @see com.siriusxi.ms.store.api.core.product.ProductServiceApi + * + * @author mohamed.taman + * @since v3.0 codename Storm + * @version 1.0 + */ +@RestController +@Log4j2 +public class ProductController implements ProductServiceApi { + + /** + * Product service business logic interface. + */ + private final ProductService prodService; + + @Autowired + public ProductController(@Qualifier("ProductServiceImpl") ProductService prodService) { + this.prodService = prodService; + } + + /** + * {@inheritDoc} + */ + @Override + public Product getProduct(int id) { + return prodService.getProduct(id); + } + + /** + * {@inheritDoc} + */ + @Override + public Product createProduct(Product body) { + return prodService.createProduct(body); + } + + /** + * {@inheritDoc} + */ + @Override + public void deleteProduct(int id) { + prodService.deleteProduct(id); + } +} diff --git a/product-service/src/main/java/com/siriusxi/ms/store/ps/controller/ProductMapper.java b/product-service/src/main/java/com/siriusxi/ms/store/ps/service/ProductMapper.java similarity index 83% rename from product-service/src/main/java/com/siriusxi/ms/store/ps/controller/ProductMapper.java rename to product-service/src/main/java/com/siriusxi/ms/store/ps/service/ProductMapper.java index 25a34994..d0834c49 100644 --- a/product-service/src/main/java/com/siriusxi/ms/store/ps/controller/ProductMapper.java +++ b/product-service/src/main/java/com/siriusxi/ms/store/ps/service/ProductMapper.java @@ -1,6 +1,6 @@ -package com.siriusxi.ms.store.ps.controller; +package com.siriusxi.ms.store.ps.service; -import com.siriusxi.ms.store.api.core.product.Product; +import com.siriusxi.ms.store.api.core.product.dto.Product; import com.siriusxi.ms.store.ps.persistence.ProductEntity; import org.mapstruct.Mapper; import org.mapstruct.Mapping; diff --git a/product-service/src/main/java/com/siriusxi/ms/store/ps/controller/ProductServiceImpl.java b/product-service/src/main/java/com/siriusxi/ms/store/ps/service/ProductServiceImpl.java similarity index 88% rename from product-service/src/main/java/com/siriusxi/ms/store/ps/controller/ProductServiceImpl.java rename to product-service/src/main/java/com/siriusxi/ms/store/ps/service/ProductServiceImpl.java index c2152311..9bcd57ad 100644 --- a/product-service/src/main/java/com/siriusxi/ms/store/ps/controller/ProductServiceImpl.java +++ b/product-service/src/main/java/com/siriusxi/ms/store/ps/service/ProductServiceImpl.java @@ -1,7 +1,7 @@ -package com.siriusxi.ms.store.ps.controller; +package com.siriusxi.ms.store.ps.service; import com.mongodb.DuplicateKeyException; -import com.siriusxi.ms.store.api.core.product.Product; +import com.siriusxi.ms.store.api.core.product.dto.Product; import com.siriusxi.ms.store.api.core.product.ProductService; import com.siriusxi.ms.store.ps.persistence.ProductEntity; import com.siriusxi.ms.store.ps.persistence.ProductRepository; @@ -10,9 +10,9 @@ import com.siriusxi.ms.store.util.http.ServiceUtil; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.stereotype.Service; -@RestController +@Service("ProductServiceImpl") @Log4j2 public class ProductServiceImpl implements ProductService { @@ -60,6 +60,10 @@ public Product getProduct(int productId) { return response; } + /* + Implementation is idempotent, that is, + it will not report any failure if the entity is not found Always 200 + */ @Override public void deleteProduct(int productId) { log.debug("deleteProduct: tries to delete an entity with productId: {}", productId); diff --git a/product-service/src/test/java/com/siriusxi/ms/store/ps/MapperTests.java b/product-service/src/test/java/com/siriusxi/ms/store/ps/MapperTests.java index 7933fd07..0d0ee1cc 100644 --- a/product-service/src/test/java/com/siriusxi/ms/store/ps/MapperTests.java +++ b/product-service/src/test/java/com/siriusxi/ms/store/ps/MapperTests.java @@ -1,7 +1,7 @@ package com.siriusxi.ms.store.ps; -import com.siriusxi.ms.store.api.core.product.Product; -import com.siriusxi.ms.store.ps.controller.ProductMapper; +import com.siriusxi.ms.store.api.core.product.dto.Product; +import com.siriusxi.ms.store.ps.service.ProductMapper; import com.siriusxi.ms.store.ps.persistence.ProductEntity; import org.junit.jupiter.api.Test; @@ -9,7 +9,7 @@ public class MapperTests { - private final ProductMapper mapper = ProductMapper.INSTANCE; + private final ProductMapper mapper = ProductMapper.INSTANCE; @Test public void mapperTests() { diff --git a/product-service/src/test/java/com/siriusxi/ms/store/ps/ProductServiceApplicationTests.java b/product-service/src/test/java/com/siriusxi/ms/store/ps/ProductServiceApplicationTests.java index ce7f24e1..cfc36a27 100644 --- a/product-service/src/test/java/com/siriusxi/ms/store/ps/ProductServiceApplicationTests.java +++ b/product-service/src/test/java/com/siriusxi/ms/store/ps/ProductServiceApplicationTests.java @@ -1,6 +1,6 @@ package com.siriusxi.ms.store.ps; -import com.siriusxi.ms.store.api.core.product.Product; +import com.siriusxi.ms.store.api.core.product.dto.Product; import com.siriusxi.ms.store.ps.persistence.ProductRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -33,7 +33,6 @@ public void setupDb() { repository.deleteAll(); } - @Test public void getProductById() { @@ -70,10 +69,10 @@ public void deleteProduct() { postAndVerifyProduct(productId, OK); assertTrue(repository.findByProductId(productId).isPresent()); - deleteAndVerifyProduct(productId, OK); + deleteAndVerifyProduct(productId); assertFalse(repository.findByProductId(productId).isPresent()); - deleteAndVerifyProduct(productId, OK); + deleteAndVerifyProduct(productId); } @Test @@ -130,12 +129,12 @@ private WebTestClient.BodyContentSpec postAndVerifyProduct(int productId, HttpSt .expectBody(); } - private WebTestClient.BodyContentSpec deleteAndVerifyProduct(int productId, HttpStatus expectedStatus) { - return client.delete() + private void deleteAndVerifyProduct(int productId) { + client.delete() .uri(BASE_URI + productId) .accept(APPLICATION_JSON) .exchange() - .expectStatus().isEqualTo(expectedStatus) + .expectStatus().isEqualTo(OK) .expectBody(); } diff --git a/review-service/src/test/java/com/siriusxi/ms/store/revs/ReviewServiceApplicationTests.java b/review-service/src/test/java/com/siriusxi/ms/store/revs/ReviewServiceApplicationTests.java index 10df8c72..28cdca84 100644 --- a/review-service/src/test/java/com/siriusxi/ms/store/revs/ReviewServiceApplicationTests.java +++ b/review-service/src/test/java/com/siriusxi/ms/store/revs/ReviewServiceApplicationTests.java @@ -82,10 +82,10 @@ public void deleteReviews() { postAndVerifyReview(productId, recommendationId, OK); assertEquals(1, repository.findByProductId(productId).size()); - deleteAndVerifyReviewsByProductId(productId, OK); + deleteAndVerifyReviewsByProductId(productId); assertEquals(0, repository.findByProductId(productId).size()); - deleteAndVerifyReviewsByProductId(productId, OK); + deleteAndVerifyReviewsByProductId(productId); } @Test @@ -153,12 +153,12 @@ private WebTestClient.BodyContentSpec postAndVerifyReview(int productId, .expectBody(); } - private void deleteAndVerifyReviewsByProductId(int productId, HttpStatus expectedStatus) { + private void deleteAndVerifyReviewsByProductId(int productId) { client.delete() .uri(BASE_URI + "?productId=" + productId) .accept(APPLICATION_JSON) .exchange() - .expectStatus().isEqualTo(expectedStatus) + .expectStatus().isEqualTo(OK) .expectBody(); } } \ No newline at end of file diff --git a/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductService.java b/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductService.java index 41f64db8..13967058 100644 --- a/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductService.java +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductService.java @@ -1,43 +1,44 @@ package com.siriusxi.ms.store.api.core.product; -import org.springframework.web.bind.annotation.*; +import com.siriusxi.ms.store.api.core.product.dto.Product; -import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; - -//@RequestMapping("products") +/** + * Interface that define the general service contract (methods) for th Product + * 1. Service and + * 2. Controller interfaces. + * + * @author mohamed.taman + * @version 3.0. + */ public interface ProductService { /** - * Sample usage: curl $HOST:$PORT/products/1 + * Get the product with Id from repository. * - * @param productId is the product that you are looking for. + * @param id is the product id that you are looking for. * @return the product, if found, else null. + * + * @since version 0.1 */ - @GetMapping(value = "products/{productId}", - produces = APPLICATION_JSON_VALUE) - Product getProduct(@PathVariable int productId); + Product getProduct(int id); /** - * Sample usage: - * - *

curl -X POST $HOST:$PORT/products \ -H "Content-Type: application/json" --data \ - * '{"productId":123,"name":"product 123","weight":123}' + * Add product to the repository. * * @param body product to save. * @return just created product. + * + * @since version 0.1 */ - @PostMapping( value = "products", - produces = APPLICATION_JSON_VALUE, - consumes = APPLICATION_JSON_VALUE) - Product createProduct(@RequestBody Product body); + Product createProduct(Product body); /** - * Sample usage: + * Delete the product from repository. * - *

curl -X DELETE $HOST:$PORT/products/1 + * @implNote This method should be idempotent and always return 200 OK status. + * @param id to be deleted. * - * @param productId to be deleted. + * @since version 0.1 */ - @DeleteMapping("products/{productId}") - void deleteProduct(@PathVariable int productId); + void deleteProduct(int id); } diff --git a/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductServiceApi.java b/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductServiceApi.java new file mode 100644 index 00000000..7e035006 --- /dev/null +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductServiceApi.java @@ -0,0 +1,58 @@ +package com.siriusxi.ms.store.api.core.product; + + +import com.siriusxi.ms.store.api.core.product.dto.Product; +import org.springframework.web.bind.annotation.*; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +/** + * Interface ProductServiceApi is a higher level Interface to define Product + * service endpoint APIs. and to be implemented by service controllers. + * + * @author mohamed.taman + * @since v3.0 codename Storm + * @version 1.0 + */ +@RequestMapping("products") +public interface ProductServiceApi extends ProductService { + + /** + * Sample usage: + * + *

curl $HOST:$PORT/products/1

+ * + * @param id is the product that you are looking for. + * @return Product the product, if found, else null. + */ + @Override + @GetMapping(value = "{productId}", + produces = APPLICATION_JSON_VALUE) + Product getProduct(@PathVariable("productId") int id); + + /** + * Sample usage: + * + *

curl -X POST $HOST:$PORT/products \ -H "Content-Type: application/json" --data \ + * '{"productId":123,"name":"product 123","weight":123}'

+ * + * @param body product to save. + * @return Product just created. + */ + @Override + @PostMapping( + produces = APPLICATION_JSON_VALUE, + consumes = APPLICATION_JSON_VALUE) + Product createProduct(@RequestBody Product body); + + /** + * Sample usage: + * + *

curl -X DELETE $HOST:$PORT/products/1

+ * + * @param id to be deleted. + */ + @Override + @DeleteMapping("{productId}") + void deleteProduct(@PathVariable("productId") int id); +} diff --git a/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/Product.java b/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/dto/Product.java similarity index 83% rename from store-api/src/main/java/com/siriusxi/ms/store/api/core/product/Product.java rename to store-api/src/main/java/com/siriusxi/ms/store/api/core/product/dto/Product.java index 3bc9b4a2..1c5a3193 100644 --- a/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/Product.java +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/dto/Product.java @@ -1,4 +1,4 @@ -package com.siriusxi.ms.store.api.core.product; +package com.siriusxi.ms.store.api.core.product.dto; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/store-service/src/main/java/com/siriusxi/ms/store/pcs/controller/StoreServiceImpl.java b/store-service/src/main/java/com/siriusxi/ms/store/pcs/controller/StoreServiceImpl.java index 877b1f22..59901b00 100644 --- a/store-service/src/main/java/com/siriusxi/ms/store/pcs/controller/StoreServiceImpl.java +++ b/store-service/src/main/java/com/siriusxi/ms/store/pcs/controller/StoreServiceImpl.java @@ -5,7 +5,7 @@ import com.siriusxi.ms.store.api.composite.dto.RecommendationSummary; import com.siriusxi.ms.store.api.composite.dto.ReviewSummary; import com.siriusxi.ms.store.api.composite.dto.ServiceAddresses; -import com.siriusxi.ms.store.api.core.product.Product; +import com.siriusxi.ms.store.api.core.product.dto.Product; import com.siriusxi.ms.store.api.core.recommendation.Recommendation; import com.siriusxi.ms.store.api.core.review.Review; import com.siriusxi.ms.store.pcs.integration.StoreIntegration; diff --git a/store-service/src/main/java/com/siriusxi/ms/store/pcs/integration/StoreIntegration.java b/store-service/src/main/java/com/siriusxi/ms/store/pcs/integration/StoreIntegration.java index 1da5d2b0..4c70bc13 100644 --- a/store-service/src/main/java/com/siriusxi/ms/store/pcs/integration/StoreIntegration.java +++ b/store-service/src/main/java/com/siriusxi/ms/store/pcs/integration/StoreIntegration.java @@ -1,7 +1,7 @@ package com.siriusxi.ms.store.pcs.integration; import com.fasterxml.jackson.databind.ObjectMapper; -import com.siriusxi.ms.store.api.core.product.Product; +import com.siriusxi.ms.store.api.core.product.dto.Product; import com.siriusxi.ms.store.api.core.product.ProductService; import com.siriusxi.ms.store.api.core.recommendation.Recommendation; import com.siriusxi.ms.store.api.core.recommendation.RecommendationService; diff --git a/store-service/src/test/java/com/siriusxi/ms/store/pcs/StoreServiceApplicationTests.java b/store-service/src/test/java/com/siriusxi/ms/store/pcs/StoreServiceApplicationTests.java index 9ce716a2..8cdc6949 100644 --- a/store-service/src/test/java/com/siriusxi/ms/store/pcs/StoreServiceApplicationTests.java +++ b/store-service/src/test/java/com/siriusxi/ms/store/pcs/StoreServiceApplicationTests.java @@ -3,7 +3,7 @@ import com.siriusxi.ms.store.api.composite.dto.ProductAggregate; import com.siriusxi.ms.store.api.composite.dto.RecommendationSummary; import com.siriusxi.ms.store.api.composite.dto.ReviewSummary; -import com.siriusxi.ms.store.api.core.product.Product; +import com.siriusxi.ms.store.api.core.product.dto.Product; import com.siriusxi.ms.store.api.core.recommendation.Recommendation; import com.siriusxi.ms.store.api.core.review.Review; import com.siriusxi.ms.store.pcs.integration.StoreIntegration; From 83aabee74736f09b5dd862579873c2809aab3537 Mon Sep 17 00:00:00 2001 From: Mohamed Taman Date: Tue, 14 Apr 2020 17:29:42 +0200 Subject: [PATCH 4/7] Recommendation service and APIs decomposed. --- .../ms/store/ps/api/ProductController.java | 12 +-- .../rs/api/RecommendationController.java | 61 ++++++++++++++ .../controller/RecommendationServiceImpl.java | 73 ----------------- .../RecommendationMapper.java | 4 +- .../rs/service/RecommendationServiceImpl.java | 79 +++++++++++++++++++ .../com/siriusxi/ms/store/rs/MapperTests.java | 4 +- ...RecommendationServiceApplicationTests.java | 2 +- ...ctServiceApi.java => ProductEndpoint.java} | 26 +++--- .../api/core/product/ProductService.java | 20 ++--- .../RecommendationEndpoint.java | 59 ++++++++++++++ .../recommendation/RecommendationService.java | 72 ++++++++--------- .../{ => dto}/Recommendation.java | 2 +- .../pcs/controller/StoreServiceImpl.java | 2 +- .../pcs/integration/StoreIntegration.java | 6 +- .../pcs/StoreServiceApplicationTests.java | 2 +- 15 files changed, 279 insertions(+), 145 deletions(-) create mode 100644 recommendation-service/src/main/java/com/siriusxi/ms/store/rs/api/RecommendationController.java delete mode 100644 recommendation-service/src/main/java/com/siriusxi/ms/store/rs/controller/RecommendationServiceImpl.java rename recommendation-service/src/main/java/com/siriusxi/ms/store/rs/{controller => service}/RecommendationMapper.java (88%) create mode 100644 recommendation-service/src/main/java/com/siriusxi/ms/store/rs/service/RecommendationServiceImpl.java rename store-api/src/main/java/com/siriusxi/ms/store/api/core/product/{ProductServiceApi.java => ProductEndpoint.java} (55%) create mode 100644 store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationEndpoint.java rename store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/{ => dto}/Recommendation.java (85%) diff --git a/product-service/src/main/java/com/siriusxi/ms/store/ps/api/ProductController.java b/product-service/src/main/java/com/siriusxi/ms/store/ps/api/ProductController.java index f5486fbf..bcb8a3b0 100644 --- a/product-service/src/main/java/com/siriusxi/ms/store/ps/api/ProductController.java +++ b/product-service/src/main/java/com/siriusxi/ms/store/ps/api/ProductController.java @@ -1,7 +1,7 @@ package com.siriusxi.ms.store.ps.api; +import com.siriusxi.ms.store.api.core.product.ProductEndpoint; import com.siriusxi.ms.store.api.core.product.ProductService; -import com.siriusxi.ms.store.api.core.product.ProductServiceApi; import com.siriusxi.ms.store.api.core.product.dto.Product; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; @@ -9,16 +9,18 @@ import org.springframework.web.bind.annotation.RestController; /** - * Class ProductController is the implementation of the main product API definition. - * @see com.siriusxi.ms.store.api.core.product.ProductServiceApi + * Class ProductController is the implementation + * of the main Product Endpoint API definition. + * + * @see ProductEndpoint * * @author mohamed.taman + * @version v1.0 * @since v3.0 codename Storm - * @version 1.0 */ @RestController @Log4j2 -public class ProductController implements ProductServiceApi { +public class ProductController implements ProductEndpoint { /** * Product service business logic interface. diff --git a/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/api/RecommendationController.java b/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/api/RecommendationController.java new file mode 100644 index 00000000..c972423e --- /dev/null +++ b/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/api/RecommendationController.java @@ -0,0 +1,61 @@ +package com.siriusxi.ms.store.rs.api; + +import com.siriusxi.ms.store.api.core.recommendation.RecommendationEndpoint; +import com.siriusxi.ms.store.api.core.recommendation.RecommendationService; +import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; +import lombok.extern.log4j.Log4j2; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * Class RecommendationController is the implementation + * of the main Recommendation Endpoint API definition. + * + * @see RecommendationEndpoint + * + * @author mohamed.taman + * @version v1.0 + * @since v3.0 codename Storm + */ +@RestController +@Log4j2 +public class RecommendationController implements RecommendationEndpoint { + /** + * Recommendation service business logic interface. + */ + private final RecommendationService recommendationService; + + @Autowired + public RecommendationController( + @Qualifier("RecommendationServiceImpl") RecommendationService recommendationService) { + this.recommendationService = recommendationService; + } + + /** + * {@inheritDoc} + */ + @Override + public List getRecommendations(int productId) { + return recommendationService.getRecommendations(productId); + } + + /** + * {@inheritDoc} + */ + @Override + public Recommendation createRecommendation(Recommendation body) { + return recommendationService.createRecommendation(body); + } + + /** + * {@inheritDoc} + */ + @Override + public void deleteRecommendations(int productId) { + recommendationService.deleteRecommendations(productId); + } +} diff --git a/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/controller/RecommendationServiceImpl.java b/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/controller/RecommendationServiceImpl.java deleted file mode 100644 index 611517a0..00000000 --- a/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/controller/RecommendationServiceImpl.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.siriusxi.ms.store.rs.controller; - -import com.mongodb.DuplicateKeyException; -import com.siriusxi.ms.store.api.core.recommendation.Recommendation; -import com.siriusxi.ms.store.api.core.recommendation.RecommendationService; -import com.siriusxi.ms.store.rs.persistence.RecommendationEntity; -import com.siriusxi.ms.store.rs.persistence.RecommendationRepository; -import com.siriusxi.ms.store.util.exceptions.InvalidInputException; -import com.siriusxi.ms.store.util.http.ServiceUtil; -import lombok.extern.log4j.Log4j2; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.web.bind.annotation.RestController; - -import java.util.ArrayList; -import java.util.List; - -// FIXME to extract service from controller -@RestController -@Log4j2 -public class RecommendationServiceImpl implements RecommendationService { - - private final RecommendationRepository repository; - - private final RecommendationMapper mapper; - - private final ServiceUtil serviceUtil; - - @Autowired - public RecommendationServiceImpl(RecommendationRepository repository, - RecommendationMapper mapper, - ServiceUtil serviceUtil) { - this.repository = repository; - this.mapper = mapper; - this.serviceUtil = serviceUtil; - } - - @Override - public Recommendation createRecommendation(Recommendation body) { - try { - RecommendationEntity entity = mapper.apiToEntity(body); - RecommendationEntity newEntity = repository.save(entity); - - log.debug("createRecommendation: created a recommendation entity: {}/{}", - body.getProductId(), body.getRecommendationId()); - return mapper.entityToApi(newEntity); - - } catch (DuplicateKeyException dke) { - throw new InvalidInputException("Duplicate key, Product Id: " + body.getProductId() + ", Recommendation Id:" + body.getRecommendationId()); - } - } - - @Override - public List getRecommendations(int productId) { - - if (productId < 1) throw new InvalidInputException("Invalid productId: " + productId); - - List entityList = repository.findByProductId(productId); - List list = mapper.entityListToApiList(entityList); - list.forEach(e -> e.setServiceAddress(serviceUtil.getServiceAddress())); - - log.debug("getRecommendations: response size: {}", list.size()); - - return list; - } - - @Override - public void deleteRecommendations(int productId) { - log.debug("deleteRecommendations: tries to delete recommendations for the product with " + - "productId: {}", productId); - repository.deleteAll(repository.findByProductId(productId)); - } -} diff --git a/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/controller/RecommendationMapper.java b/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/service/RecommendationMapper.java similarity index 88% rename from recommendation-service/src/main/java/com/siriusxi/ms/store/rs/controller/RecommendationMapper.java rename to recommendation-service/src/main/java/com/siriusxi/ms/store/rs/service/RecommendationMapper.java index f03814f2..7beb083d 100644 --- a/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/controller/RecommendationMapper.java +++ b/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/service/RecommendationMapper.java @@ -1,6 +1,6 @@ -package com.siriusxi.ms.store.rs.controller; +package com.siriusxi.ms.store.rs.service; -import com.siriusxi.ms.store.api.core.recommendation.Recommendation; +import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; import com.siriusxi.ms.store.rs.persistence.RecommendationEntity; import org.mapstruct.Mapper; import org.mapstruct.Mapping; diff --git a/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/service/RecommendationServiceImpl.java b/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/service/RecommendationServiceImpl.java new file mode 100644 index 00000000..27883563 --- /dev/null +++ b/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/service/RecommendationServiceImpl.java @@ -0,0 +1,79 @@ +package com.siriusxi.ms.store.rs.service; + +import com.mongodb.DuplicateKeyException; + +import com.siriusxi.ms.store.api.core.recommendation.RecommendationService; +import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; +import com.siriusxi.ms.store.rs.persistence.RecommendationEntity; +import com.siriusxi.ms.store.rs.persistence.RecommendationRepository; +import com.siriusxi.ms.store.util.exceptions.InvalidInputException; +import com.siriusxi.ms.store.util.http.ServiceUtil; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service("RecommendationServiceImpl") +@Log4j2 +public class RecommendationServiceImpl implements RecommendationService { + + private final RecommendationRepository repository; + + private final RecommendationMapper mapper; + + private final ServiceUtil serviceUtil; + + @Autowired + public RecommendationServiceImpl( + RecommendationRepository repository, RecommendationMapper mapper, ServiceUtil serviceUtil) { + this.repository = repository; + this.mapper = mapper; + this.serviceUtil = serviceUtil; + } + + @Override + public Recommendation createRecommendation(Recommendation body) { + try { + RecommendationEntity entity = mapper.apiToEntity(body); + RecommendationEntity newEntity = repository.save(entity); + + log.debug( + "createRecommendation: created a recommendation entity: {}/{}", + body.getProductId(), + body.getRecommendationId()); + + return mapper.entityToApi(newEntity); + + } catch (DuplicateKeyException dke) { + throw new InvalidInputException( + "Duplicate key, Product Id: " + + body.getProductId() + + ", Recommendation Id:" + + body.getRecommendationId()); + } + } + + @Override + public List getRecommendations(int productId) { + + if (productId < 1) throw new InvalidInputException("Invalid productId: " + productId); + + List entityList = repository.findByProductId(productId); + List list = mapper.entityListToApiList(entityList); + list.forEach(e -> e.setServiceAddress(serviceUtil.getServiceAddress())); + + log.debug("getRecommendations: response size: {}", list.size()); + + return list; + } + + @Override + public void deleteRecommendations(int productId) { + log.debug( + "deleteRecommendations: tries to delete recommendations for the product with " + + "productId: {}", + productId); + repository.deleteAll(repository.findByProductId(productId)); + } +} diff --git a/recommendation-service/src/test/java/com/siriusxi/ms/store/rs/MapperTests.java b/recommendation-service/src/test/java/com/siriusxi/ms/store/rs/MapperTests.java index 74942e60..31e1395a 100644 --- a/recommendation-service/src/test/java/com/siriusxi/ms/store/rs/MapperTests.java +++ b/recommendation-service/src/test/java/com/siriusxi/ms/store/rs/MapperTests.java @@ -1,7 +1,7 @@ package com.siriusxi.ms.store.rs; -import com.siriusxi.ms.store.api.core.recommendation.Recommendation; -import com.siriusxi.ms.store.rs.controller.RecommendationMapper; +import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; +import com.siriusxi.ms.store.rs.service.RecommendationMapper; import com.siriusxi.ms.store.rs.persistence.RecommendationEntity; import org.junit.jupiter.api.Test; diff --git a/recommendation-service/src/test/java/com/siriusxi/ms/store/rs/RecommendationServiceApplicationTests.java b/recommendation-service/src/test/java/com/siriusxi/ms/store/rs/RecommendationServiceApplicationTests.java index 1a2938fc..b22cf9f2 100644 --- a/recommendation-service/src/test/java/com/siriusxi/ms/store/rs/RecommendationServiceApplicationTests.java +++ b/recommendation-service/src/test/java/com/siriusxi/ms/store/rs/RecommendationServiceApplicationTests.java @@ -1,6 +1,6 @@ package com.siriusxi.ms.store.rs; -import com.siriusxi.ms.store.api.core.recommendation.Recommendation; +import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; import com.siriusxi.ms.store.rs.persistence.RecommendationRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; diff --git a/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductServiceApi.java b/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductEndpoint.java similarity index 55% rename from store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductServiceApi.java rename to store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductEndpoint.java index 7e035006..e835d730 100644 --- a/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductServiceApi.java +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductEndpoint.java @@ -7,23 +7,27 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; /** - * Interface ProductServiceApi is a higher level Interface to define Product - * service endpoint APIs. and to be implemented by service controllers. + * Interface ProductEndpoint is a higher level Interface + * to define Product Service endpoint APIs that follow ProductService + * interface. And to be implemented by service controllers. + * + * @see com.siriusxi.ms.store.api.core.product.ProductService * * @author mohamed.taman + * @version v1.0 * @since v3.0 codename Storm - * @version 1.0 */ @RequestMapping("products") -public interface ProductServiceApi extends ProductService { +public interface ProductEndpoint extends ProductService { /** * Sample usage: * - *

curl $HOST:$PORT/products/1

+ *

curl $HOST:$PORT/products/1

* * @param id is the product that you are looking for. - * @return Product the product, if found, else null. + * @return Product the product, if found, else null. + * @since v3.0 codename Storm */ @Override @GetMapping(value = "{productId}", @@ -33,11 +37,12 @@ public interface ProductServiceApi extends ProductService { /** * Sample usage: * - *

curl -X POST $HOST:$PORT/products \ -H "Content-Type: application/json" --data \ - * '{"productId":123,"name":"product 123","weight":123}'

+ *

curl -X POST $HOST:$PORT/products \ -H "Content-Type: application/json" --data \ + * '{"productId":123,"name":"product 123","weight":123}'

* * @param body product to save. - * @return Product just created. + * @return Product just created. + * @since v3.0 codename Storm */ @Override @PostMapping( @@ -48,9 +53,10 @@ public interface ProductServiceApi extends ProductService { /** * Sample usage: * - *

curl -X DELETE $HOST:$PORT/products/1

+ *

curl -X DELETE $HOST:$PORT/products/1

* * @param id to be deleted. + * @since v3.0 codename Storm */ @Override @DeleteMapping("{productId}") diff --git a/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductService.java b/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductService.java index 13967058..8874261f 100644 --- a/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductService.java +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductService.java @@ -3,12 +3,15 @@ import com.siriusxi.ms.store.api.core.product.dto.Product; /** - * Interface that define the general service contract (methods) for th Product - * 1. Service and - * 2. Controller interfaces. + * Interface that define the general service contract (methods) for the Product + *
    + *
  1. Service and,
  2. + *
  3. Controller interfaces.
  4. + *
* * @author mohamed.taman - * @version 3.0. + * @version v0.2 + * @since v0.1 */ public interface ProductService { @@ -17,8 +20,7 @@ public interface ProductService { * * @param id is the product id that you are looking for. * @return the product, if found, else null. - * - * @since version 0.1 + * @since v0.1 */ Product getProduct(int id); @@ -27,8 +29,7 @@ public interface ProductService { * * @param body product to save. * @return just created product. - * - * @since version 0.1 + * @since v0.1 */ Product createProduct(Product body); @@ -37,8 +38,7 @@ public interface ProductService { * * @implNote This method should be idempotent and always return 200 OK status. * @param id to be deleted. - * - * @since version 0.1 + * @since v0.1 */ void deleteProduct(int id); } diff --git a/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationEndpoint.java b/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationEndpoint.java new file mode 100644 index 00000000..d8585f95 --- /dev/null +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationEndpoint.java @@ -0,0 +1,59 @@ +package com.siriusxi.ms.store.api.core.recommendation; + +import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +/** + * Interface RecommendationEndpoint is a higher level Interface to define + * Recommendation Service endpoint APIs, that follow RecommendationService + * interface. And to be implemented by service controllers. + * + * @see com.siriusxi.ms.store.api.core.recommendation.RecommendationService + * @author mohamed.taman + * @version v1.0 + * @since v3.0 codename Storm + */ +@RequestMapping("recommendations") +public interface RecommendationEndpoint extends RecommendationService { + + /** + * Sample usage: + * + *

curl $HOST:$PORT/recommendations?productId=1

+ * + * @param productId that you are looking for its recommendations. + * @return list of product recommendations, or empty list if there are no recommendations. + * @since v3.0 codename Storm + */ + @GetMapping(produces = APPLICATION_JSON_VALUE) + List getRecommendations(@RequestParam("productId") int productId); + + /** + * Sample usage: + * + *

curl -X POST $HOST:$PORT/recommendations \ + * -H "Content-Type: application/json" --data \ + * '{"productId":123,"recommendationId":456,"author":"me","rate":5,"content":"yada, yada, yada" + * }'

+ * + * @param body the recommendation to add. + * @return currently created recommendation. + * @since v3.0 codename Storm + */ + @PostMapping(produces = APPLICATION_JSON_VALUE, consumes = APPLICATION_JSON_VALUE) + Recommendation createRecommendation(@RequestBody Recommendation body); + + /** + * Sample usage: + * + *

curl -X DELETE $HOST:$PORT/recommendations?productId=1

+ * + * @param productId to delete recommendations for. + * @since version 0.1 + */ + @DeleteMapping + void deleteRecommendations(@RequestParam("productId") int productId); +} diff --git a/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationService.java b/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationService.java index 273b85e2..e0de77ff 100644 --- a/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationService.java +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationService.java @@ -1,46 +1,46 @@ package com.siriusxi.ms.store.api.core.recommendation; -import org.springframework.web.bind.annotation.*; +import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; import java.util.List; -import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; -//@RequestMapping("recommendations") +/** + * Interface that define the general service contract (methods) for the Recommendation + *
    + *
  1. Service and,
  2. + *
  3. Controller interfaces.
  4. + *
+ * + * @author mohamed.taman + * @version v0.2 + * @since v0.1 + */ public interface RecommendationService { + /** + * Get all recommendations for specific product by product id. + * + * @param productId that you are looking for its recommendations. + * @return list of product recommendations, + * or empty list if there are no recommendations. + * @since v0.1 + */ + List getRecommendations(int productId); - /** - * Sample usage: curl $HOST:$PORT/recommendations?productId=1 - * - * @param productId that you are looking for its recommendations. - * - * @return list of product recommendations, - * or empty list if there are no recommendations. - */ - @GetMapping(value = "recommendations",produces = APPLICATION_JSON_VALUE) - List getRecommendations(@RequestParam("productId") int productId); + /** + * Create a new recommendation for a product. + * + * @param body the recommendation to add. + * @return currently created recommendation. + * @since v0.1 + */ + Recommendation createRecommendation(Recommendation body); - /** - * Sample usage: - * - *

curl -X POST $HOST:$PORT/recommendations \ -H "Content-Type: application/json" --data \ - * '{"productId":123,"recommendationId":456,"author":"me","rate":5,"content":"yada, yada, yada"}' - * - * @param body the recommendation to add. - * @return currently created recommendation. - */ - @PostMapping(value = "recommendations", - produces = APPLICATION_JSON_VALUE, - consumes = APPLICATION_JSON_VALUE) - Recommendation createRecommendation(@RequestBody Recommendation body); - - /** - * Sample usage: - * - *

curl -X DELETE $HOST:$PORT/recommendations?productId=1 - * - * @param productId to delete recommendations for. - */ - @DeleteMapping(value = "recommendations") - void deleteRecommendations(@RequestParam("productId") int productId); + /** + * Delete all product recommendations, + * + * @param productId to delete recommendations for. + * @since v0.1 + */ + void deleteRecommendations(int productId); } diff --git a/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/Recommendation.java b/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/dto/Recommendation.java similarity index 85% rename from store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/Recommendation.java rename to store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/dto/Recommendation.java index 5757c25c..d96eeff0 100644 --- a/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/Recommendation.java +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/dto/Recommendation.java @@ -1,4 +1,4 @@ -package com.siriusxi.ms.store.api.core.recommendation; +package com.siriusxi.ms.store.api.core.recommendation.dto; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/store-service/src/main/java/com/siriusxi/ms/store/pcs/controller/StoreServiceImpl.java b/store-service/src/main/java/com/siriusxi/ms/store/pcs/controller/StoreServiceImpl.java index 59901b00..68b5f6cd 100644 --- a/store-service/src/main/java/com/siriusxi/ms/store/pcs/controller/StoreServiceImpl.java +++ b/store-service/src/main/java/com/siriusxi/ms/store/pcs/controller/StoreServiceImpl.java @@ -6,7 +6,7 @@ import com.siriusxi.ms.store.api.composite.dto.ReviewSummary; import com.siriusxi.ms.store.api.composite.dto.ServiceAddresses; import com.siriusxi.ms.store.api.core.product.dto.Product; -import com.siriusxi.ms.store.api.core.recommendation.Recommendation; +import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; import com.siriusxi.ms.store.api.core.review.Review; import com.siriusxi.ms.store.pcs.integration.StoreIntegration; import com.siriusxi.ms.store.util.exceptions.NotFoundException; diff --git a/store-service/src/main/java/com/siriusxi/ms/store/pcs/integration/StoreIntegration.java b/store-service/src/main/java/com/siriusxi/ms/store/pcs/integration/StoreIntegration.java index 4c70bc13..1f440bfb 100644 --- a/store-service/src/main/java/com/siriusxi/ms/store/pcs/integration/StoreIntegration.java +++ b/store-service/src/main/java/com/siriusxi/ms/store/pcs/integration/StoreIntegration.java @@ -3,8 +3,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.siriusxi.ms.store.api.core.product.dto.Product; import com.siriusxi.ms.store.api.core.product.ProductService; -import com.siriusxi.ms.store.api.core.recommendation.Recommendation; -import com.siriusxi.ms.store.api.core.recommendation.RecommendationService; +import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; +import com.siriusxi.ms.store.api.core.recommendation.RecommendationEndpoint; import com.siriusxi.ms.store.api.core.review.Review; import com.siriusxi.ms.store.api.core.review.ReviewService; import com.siriusxi.ms.store.util.exceptions.InvalidInputException; @@ -30,7 +30,7 @@ public class StoreIntegration implements ProductService, - RecommendationService, + RecommendationEndpoint, ReviewService { private final RestTemplate restTemplate; diff --git a/store-service/src/test/java/com/siriusxi/ms/store/pcs/StoreServiceApplicationTests.java b/store-service/src/test/java/com/siriusxi/ms/store/pcs/StoreServiceApplicationTests.java index 8cdc6949..33b3b1b7 100644 --- a/store-service/src/test/java/com/siriusxi/ms/store/pcs/StoreServiceApplicationTests.java +++ b/store-service/src/test/java/com/siriusxi/ms/store/pcs/StoreServiceApplicationTests.java @@ -4,7 +4,7 @@ import com.siriusxi.ms.store.api.composite.dto.RecommendationSummary; import com.siriusxi.ms.store.api.composite.dto.ReviewSummary; import com.siriusxi.ms.store.api.core.product.dto.Product; -import com.siriusxi.ms.store.api.core.recommendation.Recommendation; +import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; import com.siriusxi.ms.store.api.core.review.Review; import com.siriusxi.ms.store.pcs.integration.StoreIntegration; import com.siriusxi.ms.store.util.exceptions.InvalidInputException; From 0da05c19587181a958f624291dc457fef373c58e Mon Sep 17 00:00:00 2001 From: Mohamed Taman Date: Tue, 14 Apr 2020 18:05:00 +0200 Subject: [PATCH 5/7] Review service and api decomposed. --- .../ms/store/ps/api/ProductController.java | 51 ++++++-------- .../rs/api/RecommendationController.java | 66 ++++++++----------- .../ms/store/revs/api/ReviewController.java | 52 +++++++++++++++ .../{controller => service}/ReviewMapper.java | 4 +- .../ReviewServiceImpl.java | 7 +- .../siriusxi/ms/store/revs/MapperTests.java | 4 +- .../revs/ReviewServiceApplicationTests.java | 2 +- .../RecommendationEndpoint.java | 2 +- .../recommendation/RecommendationService.java | 2 +- .../store/api/core/review/ReviewEndpoint.java | 65 ++++++++++++++++++ .../store/api/core/review/ReviewService.java | 51 +++++++------- .../api/core/review/{ => dto}/Review.java | 2 +- .../pcs/controller/StoreServiceImpl.java | 2 +- .../pcs/integration/StoreIntegration.java | 2 +- .../pcs/StoreServiceApplicationTests.java | 2 +- 15 files changed, 204 insertions(+), 110 deletions(-) create mode 100644 review-service/src/main/java/com/siriusxi/ms/store/revs/api/ReviewController.java rename review-service/src/main/java/com/siriusxi/ms/store/revs/{controller => service}/ReviewMapper.java (87%) rename review-service/src/main/java/com/siriusxi/ms/store/revs/{controller => service}/ReviewServiceImpl.java (92%) create mode 100644 store-api/src/main/java/com/siriusxi/ms/store/api/core/review/ReviewEndpoint.java rename store-api/src/main/java/com/siriusxi/ms/store/api/core/review/{ => dto}/Review.java (86%) diff --git a/product-service/src/main/java/com/siriusxi/ms/store/ps/api/ProductController.java b/product-service/src/main/java/com/siriusxi/ms/store/ps/api/ProductController.java index bcb8a3b0..1e549f1b 100644 --- a/product-service/src/main/java/com/siriusxi/ms/store/ps/api/ProductController.java +++ b/product-service/src/main/java/com/siriusxi/ms/store/ps/api/ProductController.java @@ -9,11 +9,10 @@ import org.springframework.web.bind.annotation.RestController; /** - * Class ProductController is the implementation - * of the main Product Endpoint API definition. + * Class ProductController is the implementation of the main Product Endpoint API + * definition. * * @see ProductEndpoint - * * @author mohamed.taman * @version v1.0 * @since v3.0 codename Storm @@ -22,37 +21,29 @@ @Log4j2 public class ProductController implements ProductEndpoint { - /** - * Product service business logic interface. - */ - private final ProductService prodService; + /** Product service business logic interface. */ + private final ProductService prodService; - @Autowired - public ProductController(@Qualifier("ProductServiceImpl") ProductService prodService) { - this.prodService = prodService; - } + @Autowired + public ProductController(@Qualifier("ProductServiceImpl") ProductService prodService) { + this.prodService = prodService; + } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public Product getProduct(int id) { - return prodService.getProduct(id); - } + return prodService.getProduct(id); + } - /** - * {@inheritDoc} - */ - @Override - public Product createProduct(Product body) { - return prodService.createProduct(body); - } + /** {@inheritDoc} */ + @Override + public Product createProduct(Product body) { + return prodService.createProduct(body); + } - /** - * {@inheritDoc} - */ - @Override - public void deleteProduct(int id) { - prodService.deleteProduct(id); - } + /** {@inheritDoc} */ + @Override + public void deleteProduct(int id) { + prodService.deleteProduct(id); + } } diff --git a/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/api/RecommendationController.java b/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/api/RecommendationController.java index c972423e..a96f96a1 100644 --- a/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/api/RecommendationController.java +++ b/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/api/RecommendationController.java @@ -4,7 +4,6 @@ import com.siriusxi.ms.store.api.core.recommendation.RecommendationService; import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; import lombok.extern.log4j.Log4j2; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.RestController; @@ -12,11 +11,10 @@ import java.util.List; /** - * Class RecommendationController is the implementation - * of the main Recommendation Endpoint API definition. + * Class RecommendationController is the implementation of the main Recommendation + * Endpoint API definition. * * @see RecommendationEndpoint - * * @author mohamed.taman * @version v1.0 * @since v3.0 codename Storm @@ -24,38 +22,30 @@ @RestController @Log4j2 public class RecommendationController implements RecommendationEndpoint { - /** - * Recommendation service business logic interface. - */ - private final RecommendationService recommendationService; - - @Autowired - public RecommendationController( - @Qualifier("RecommendationServiceImpl") RecommendationService recommendationService) { - this.recommendationService = recommendationService; - } - - /** - * {@inheritDoc} - */ - @Override - public List getRecommendations(int productId) { - return recommendationService.getRecommendations(productId); - } - - /** - * {@inheritDoc} - */ - @Override - public Recommendation createRecommendation(Recommendation body) { - return recommendationService.createRecommendation(body); - } - - /** - * {@inheritDoc} - */ - @Override - public void deleteRecommendations(int productId) { - recommendationService.deleteRecommendations(productId); - } + /** Recommendation service business logic interface. */ + private final RecommendationService recommendationService; + + @Autowired + public RecommendationController( + @Qualifier("RecommendationServiceImpl") RecommendationService recommendationService) { + this.recommendationService = recommendationService; + } + + /** {@inheritDoc} */ + @Override + public List getRecommendations(int productId) { + return recommendationService.getRecommendations(productId); + } + + /** {@inheritDoc} */ + @Override + public Recommendation createRecommendation(Recommendation body) { + return recommendationService.createRecommendation(body); + } + + /** {@inheritDoc} */ + @Override + public void deleteRecommendations(int productId) { + recommendationService.deleteRecommendations(productId); + } } diff --git a/review-service/src/main/java/com/siriusxi/ms/store/revs/api/ReviewController.java b/review-service/src/main/java/com/siriusxi/ms/store/revs/api/ReviewController.java new file mode 100644 index 00000000..0e078c89 --- /dev/null +++ b/review-service/src/main/java/com/siriusxi/ms/store/revs/api/ReviewController.java @@ -0,0 +1,52 @@ +package com.siriusxi.ms.store.revs.api; + +import com.siriusxi.ms.store.api.core.product.ProductEndpoint; +import com.siriusxi.ms.store.api.core.review.ReviewEndpoint; +import com.siriusxi.ms.store.api.core.review.ReviewService; +import com.siriusxi.ms.store.api.core.review.dto.Review; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * Class ReviewController is the implementation of the main Review Endpoint API + * definition. + * + * @see ProductEndpoint + * @author mohamed.taman + * @version v1.0 + * @since v3.0 codename Storm + */ +@RestController +@Log4j2 +public class ReviewController implements ReviewEndpoint { + + /** Review service business logic interface. */ + private final ReviewService reviewService; + + @Autowired + public ReviewController(@Qualifier("ReviewServiceImpl") ReviewService reviewService) { + this.reviewService = reviewService; + } + + /** {@inheritDoc} */ + @Override + public Review createReview(Review body) { + return reviewService.createReview(body); + } + + /** {@inheritDoc} */ + @Override + public List getReviews(int productId) { + return reviewService.getReviews(productId); + } + + /** {@inheritDoc} */ + @Override + public void deleteReviews(int productId) { + reviewService.deleteReviews(productId); + } +} diff --git a/review-service/src/main/java/com/siriusxi/ms/store/revs/controller/ReviewMapper.java b/review-service/src/main/java/com/siriusxi/ms/store/revs/service/ReviewMapper.java similarity index 87% rename from review-service/src/main/java/com/siriusxi/ms/store/revs/controller/ReviewMapper.java rename to review-service/src/main/java/com/siriusxi/ms/store/revs/service/ReviewMapper.java index 9806155b..8e16c2ce 100644 --- a/review-service/src/main/java/com/siriusxi/ms/store/revs/controller/ReviewMapper.java +++ b/review-service/src/main/java/com/siriusxi/ms/store/revs/service/ReviewMapper.java @@ -1,6 +1,6 @@ -package com.siriusxi.ms.store.revs.controller; +package com.siriusxi.ms.store.revs.service; -import com.siriusxi.ms.store.api.core.review.Review; +import com.siriusxi.ms.store.api.core.review.dto.Review; import com.siriusxi.ms.store.revs.persistence.ReviewEntity; import org.mapstruct.Mapper; import org.mapstruct.Mapping; diff --git a/review-service/src/main/java/com/siriusxi/ms/store/revs/controller/ReviewServiceImpl.java b/review-service/src/main/java/com/siriusxi/ms/store/revs/service/ReviewServiceImpl.java similarity index 92% rename from review-service/src/main/java/com/siriusxi/ms/store/revs/controller/ReviewServiceImpl.java rename to review-service/src/main/java/com/siriusxi/ms/store/revs/service/ReviewServiceImpl.java index 2ff8580a..682809ed 100644 --- a/review-service/src/main/java/com/siriusxi/ms/store/revs/controller/ReviewServiceImpl.java +++ b/review-service/src/main/java/com/siriusxi/ms/store/revs/service/ReviewServiceImpl.java @@ -1,6 +1,6 @@ -package com.siriusxi.ms.store.revs.controller; +package com.siriusxi.ms.store.revs.service; -import com.siriusxi.ms.store.api.core.review.Review; +import com.siriusxi.ms.store.api.core.review.dto.Review; import com.siriusxi.ms.store.api.core.review.ReviewService; import com.siriusxi.ms.store.revs.persistence.ReviewEntity; import com.siriusxi.ms.store.revs.persistence.ReviewRepository; @@ -9,11 +9,12 @@ import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.RestController; import java.util.List; -@RestController +@Service("ReviewServiceImpl") @Log4j2 public class ReviewServiceImpl implements ReviewService { diff --git a/review-service/src/test/java/com/siriusxi/ms/store/revs/MapperTests.java b/review-service/src/test/java/com/siriusxi/ms/store/revs/MapperTests.java index b85d65b7..111730e5 100644 --- a/review-service/src/test/java/com/siriusxi/ms/store/revs/MapperTests.java +++ b/review-service/src/test/java/com/siriusxi/ms/store/revs/MapperTests.java @@ -1,7 +1,7 @@ package com.siriusxi.ms.store.revs; -import com.siriusxi.ms.store.api.core.review.Review; -import com.siriusxi.ms.store.revs.controller.ReviewMapper; +import com.siriusxi.ms.store.api.core.review.dto.Review; +import com.siriusxi.ms.store.revs.service.ReviewMapper; import com.siriusxi.ms.store.revs.persistence.ReviewEntity; import org.junit.jupiter.api.Test; diff --git a/review-service/src/test/java/com/siriusxi/ms/store/revs/ReviewServiceApplicationTests.java b/review-service/src/test/java/com/siriusxi/ms/store/revs/ReviewServiceApplicationTests.java index 28cdca84..6d680713 100644 --- a/review-service/src/test/java/com/siriusxi/ms/store/revs/ReviewServiceApplicationTests.java +++ b/review-service/src/test/java/com/siriusxi/ms/store/revs/ReviewServiceApplicationTests.java @@ -1,6 +1,6 @@ package com.siriusxi.ms.store.revs; -import com.siriusxi.ms.store.api.core.review.Review; +import com.siriusxi.ms.store.api.core.review.dto.Review; import com.siriusxi.ms.store.revs.persistence.ReviewRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationEndpoint.java b/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationEndpoint.java index d8585f95..9cac81f1 100644 --- a/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationEndpoint.java +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationEndpoint.java @@ -11,7 +11,7 @@ * Recommendation Service endpoint APIs, that follow RecommendationService * interface. And to be implemented by service controllers. * - * @see com.siriusxi.ms.store.api.core.recommendation.RecommendationService + * @see RecommendationService * @author mohamed.taman * @version v1.0 * @since v3.0 codename Storm diff --git a/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationService.java b/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationService.java index e0de77ff..10ec77fa 100644 --- a/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationService.java +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationService.java @@ -37,7 +37,7 @@ public interface RecommendationService { Recommendation createRecommendation(Recommendation body); /** - * Delete all product recommendations, + * Delete all product recommendations. * * @param productId to delete recommendations for. * @since v0.1 diff --git a/store-api/src/main/java/com/siriusxi/ms/store/api/core/review/ReviewEndpoint.java b/store-api/src/main/java/com/siriusxi/ms/store/api/core/review/ReviewEndpoint.java new file mode 100644 index 00000000..b544f838 --- /dev/null +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/core/review/ReviewEndpoint.java @@ -0,0 +1,65 @@ +package com.siriusxi.ms.store.api.core.review; + +import com.siriusxi.ms.store.api.core.review.dto.Review; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +/** + * Interface ReviewEndpoint is a higher level Interface to define + * Review Service endpoint APIs, that follow ReviewService interface. + * And to be implemented by service controllers. + * + * @see ReviewService + * @author mohamed.taman + * @version v1.0 + * @since v3.0 codename Storm + */ +@RequestMapping("reviews") +public interface ReviewEndpoint extends ReviewService{ + + /** + * Sample usage: + * + *

curl -X POST $HOST:$PORT/reviews \ + * -H "Content-Type: application/json" --data \ + * '{"productId":123,"reviewId":456,"author":"me","subject":"yada, yada, yada", + * "content":"yada, yada, yada"}'

+ * + * @param body review to be created. + * @return just created review. + * @since v3.0 codename Storm + */ + @PostMapping( + produces = APPLICATION_JSON_VALUE, + consumes = APPLICATION_JSON_VALUE) + Review createReview(@RequestBody Review body); + + /** + * Sample usage: + * + *

curl $HOST:$PORT/reviews?productId=1

+ * + * @param productId that you are looking for its reviews. + * @return list of reviews for this product, + * or empty list if there are no reviews. + * @since v3.0 codename Storm + */ + @GetMapping(produces = APPLICATION_JSON_VALUE) + List getReviews(@RequestParam("productId") int productId); + + + /** + * Sample usage: + * + *

curl -X DELETE $HOST:$PORT/review?productId=1

+ * + * @param productId to delete its reviews. + * @since v3.0 codename Storm + */ + @DeleteMapping + void deleteReviews(@RequestParam("productId") int productId); + +} diff --git a/store-api/src/main/java/com/siriusxi/ms/store/api/core/review/ReviewService.java b/store-api/src/main/java/com/siriusxi/ms/store/api/core/review/ReviewService.java index 8c149ab3..e3251ff0 100644 --- a/store-api/src/main/java/com/siriusxi/ms/store/api/core/review/ReviewService.java +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/core/review/ReviewService.java @@ -1,49 +1,44 @@ package com.siriusxi.ms.store.api.core.review; -import org.springframework.web.bind.annotation.*; +import com.siriusxi.ms.store.api.core.review.dto.Review; import java.util.List; -import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; - -//@RequestMapping("reviews") +/** + * Interface that define the general service contract (methods) for the Review + *
    + *
  1. Service and,
  2. + *
  3. Controller interfaces.
  4. + *
+ * + * @author mohamed.taman + * @version v0.2 + * @since v0.1 + */ public interface ReviewService { - /** - * Sample usage: - * - * curl -X POST $HOST:$PORT/reviews \ - * -H "Content-Type: application/json" --data \ - * '{"productId":123,"reviewId":456,"author":"me","subject":"yada, yada, yada", - * "content":"yada, yada, yada"}' - * - * @param body review to be created. - * @return just created review. - */ - @PostMapping(value = "reviews", - produces = APPLICATION_JSON_VALUE, - consumes = APPLICATION_JSON_VALUE) - Review createReview(@RequestBody Review body); /** - * Sample usage: curl $HOST:$PORT/reviews?productId=1 + * Get all reviews for specific product by product id. * * @param productId that you are looking for its reviews. * @return list of reviews for this product, * or empty list if there are no reviews. */ - @GetMapping(value = "reviews", - produces = APPLICATION_JSON_VALUE) - List getReviews(@RequestParam("productId") int productId); - + List getReviews(int productId); /** - * Sample usage: + * Create a new review for a product. * - * curl -X DELETE $HOST:$PORT/review?productId=1 + * @param body review to be created. + * @return just created review. + */ + Review createReview(Review body); + + /** + * Delete all product reviews. * * @param productId to delete its reviews. */ - @DeleteMapping(value = "reviews") - void deleteReviews(@RequestParam("productId") int productId); + void deleteReviews(int productId); } diff --git a/store-api/src/main/java/com/siriusxi/ms/store/api/core/review/Review.java b/store-api/src/main/java/com/siriusxi/ms/store/api/core/review/dto/Review.java similarity index 86% rename from store-api/src/main/java/com/siriusxi/ms/store/api/core/review/Review.java rename to store-api/src/main/java/com/siriusxi/ms/store/api/core/review/dto/Review.java index f25dd9a6..6827af73 100644 --- a/store-api/src/main/java/com/siriusxi/ms/store/api/core/review/Review.java +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/core/review/dto/Review.java @@ -1,4 +1,4 @@ -package com.siriusxi.ms.store.api.core.review; +package com.siriusxi.ms.store.api.core.review.dto; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/store-service/src/main/java/com/siriusxi/ms/store/pcs/controller/StoreServiceImpl.java b/store-service/src/main/java/com/siriusxi/ms/store/pcs/controller/StoreServiceImpl.java index 68b5f6cd..7f5ac4df 100644 --- a/store-service/src/main/java/com/siriusxi/ms/store/pcs/controller/StoreServiceImpl.java +++ b/store-service/src/main/java/com/siriusxi/ms/store/pcs/controller/StoreServiceImpl.java @@ -7,7 +7,7 @@ import com.siriusxi.ms.store.api.composite.dto.ServiceAddresses; import com.siriusxi.ms.store.api.core.product.dto.Product; import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; -import com.siriusxi.ms.store.api.core.review.Review; +import com.siriusxi.ms.store.api.core.review.dto.Review; import com.siriusxi.ms.store.pcs.integration.StoreIntegration; import com.siriusxi.ms.store.util.exceptions.NotFoundException; import com.siriusxi.ms.store.util.http.ServiceUtil; diff --git a/store-service/src/main/java/com/siriusxi/ms/store/pcs/integration/StoreIntegration.java b/store-service/src/main/java/com/siriusxi/ms/store/pcs/integration/StoreIntegration.java index 1f440bfb..4b2f3003 100644 --- a/store-service/src/main/java/com/siriusxi/ms/store/pcs/integration/StoreIntegration.java +++ b/store-service/src/main/java/com/siriusxi/ms/store/pcs/integration/StoreIntegration.java @@ -5,7 +5,7 @@ import com.siriusxi.ms.store.api.core.product.ProductService; import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; import com.siriusxi.ms.store.api.core.recommendation.RecommendationEndpoint; -import com.siriusxi.ms.store.api.core.review.Review; +import com.siriusxi.ms.store.api.core.review.dto.Review; import com.siriusxi.ms.store.api.core.review.ReviewService; import com.siriusxi.ms.store.util.exceptions.InvalidInputException; import com.siriusxi.ms.store.util.exceptions.NotFoundException; diff --git a/store-service/src/test/java/com/siriusxi/ms/store/pcs/StoreServiceApplicationTests.java b/store-service/src/test/java/com/siriusxi/ms/store/pcs/StoreServiceApplicationTests.java index 33b3b1b7..ba463ca6 100644 --- a/store-service/src/test/java/com/siriusxi/ms/store/pcs/StoreServiceApplicationTests.java +++ b/store-service/src/test/java/com/siriusxi/ms/store/pcs/StoreServiceApplicationTests.java @@ -5,7 +5,7 @@ import com.siriusxi.ms.store.api.composite.dto.ReviewSummary; import com.siriusxi.ms.store.api.core.product.dto.Product; import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; -import com.siriusxi.ms.store.api.core.review.Review; +import com.siriusxi.ms.store.api.core.review.dto.Review; import com.siriusxi.ms.store.pcs.integration.StoreIntegration; import com.siriusxi.ms.store.util.exceptions.InvalidInputException; import com.siriusxi.ms.store.util.exceptions.NotFoundException; From 6aeef129807a72f791d54986f3cbda15daefc561 Mon Sep 17 00:00:00 2001 From: Mohamed Taman Date: Tue, 14 Apr 2020 19:39:36 +0200 Subject: [PATCH 6/7] Store service and api decomposed. --- .../ms/store/api/composite/StoreEndpoint.java | 119 +++++++++++++ .../ms/store/api/composite/StoreService.java | 133 ++++---------- .../api/core/product/ProductEndpoint.java | 2 +- .../ms/store/pcs/api/StoreController.java | 45 +++++ .../pcs/controller/StoreServiceImpl.java | 127 -------------- .../store/pcs/service/StoreServiceImpl.java | 165 ++++++++++++++++++ 6 files changed, 368 insertions(+), 223 deletions(-) create mode 100644 store-api/src/main/java/com/siriusxi/ms/store/api/composite/StoreEndpoint.java create mode 100644 store-service/src/main/java/com/siriusxi/ms/store/pcs/api/StoreController.java delete mode 100644 store-service/src/main/java/com/siriusxi/ms/store/pcs/controller/StoreServiceImpl.java create mode 100644 store-service/src/main/java/com/siriusxi/ms/store/pcs/service/StoreServiceImpl.java diff --git a/store-api/src/main/java/com/siriusxi/ms/store/api/composite/StoreEndpoint.java b/store-api/src/main/java/com/siriusxi/ms/store/api/composite/StoreEndpoint.java new file mode 100644 index 00000000..2bea8e78 --- /dev/null +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/composite/StoreEndpoint.java @@ -0,0 +1,119 @@ +package com.siriusxi.ms.store.api.composite; + +import com.siriusxi.ms.store.api.composite.dto.ProductAggregate; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import org.springframework.web.bind.annotation.*; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +/** + * Interface StoreEndpoint is a higher level Interface + * to define Store Service endpoint APIs that follow StoreService + * interface. And to be implemented by service controllers. + * + * + * @author mohamed.taman + * @version v1.0 + * @since v3.0 codename Storm + */ +@Api("REST API for Springy Store products information.") +@RequestMapping("store/api/v1") +public interface StoreEndpoint extends StoreService { + + /** + * Sample usage: + * + *

curl $HOST:$PORT/store/api/v1/products/1

+ * + * @param id is the product that you are looking for. + * @return the composite product info, if found, else null. + * @since v3.0 codename Storm. + */ + @ApiOperation( + value = "${api.product-composite.get-composite-product.description}", + notes = "${api.product-composite.get-composite-product.notes}") + @ApiResponses( + value = { + @ApiResponse( + code = 400, + message = """ + Bad Request, invalid format of the request. + See response message for more information. + """), + @ApiResponse(code = 404, message = "Not found, the specified id does not exist."), + @ApiResponse( + code = 422, + message = """ + Unprocessable entity, input parameters caused the processing to fails. + See response message for more information. + """) + }) + @GetMapping(value = "products/{id}", + produces = APPLICATION_JSON_VALUE) + ProductAggregate getProduct(@PathVariable int id); + + /** + * Sample usage: + * + *

curl -X POST $HOST:$PORT/store/api/v1/products \ + * -H "Content-Type: application/json" --data \ + * '{"productId":123,"name":"product 123", "weight":123}'

+ * + * @param body of composite product elements definition. + * @since v3.0 codename Storm. + */ + @ApiOperation( + value = "${api.product-composite.create-composite-product.description}", + notes = "${api.product-composite.create-composite-product.notes}") + @ApiResponses( + value = { + @ApiResponse( + code = 400, + message = """ + Bad Request, invalid format of the request. + See response message for more information. + """), + @ApiResponse( + code = 422, + message = """ + Unprocessable entity, input parameters caused the processing to fail. + See response message for more information. + """) + }) + @PostMapping( + value = "products", + consumes = APPLICATION_JSON_VALUE) + void createProduct(@RequestBody ProductAggregate body); + + /** + * Sample usage: + * + *

curl -X DELETE $HOST:$PORT/store/api/v1/products/1

+ * + * @param id is the product id to delete it. + * @since v3.0 codename Storm. + */ + @ApiOperation( + value = "${api.product-composite.delete-composite-product.description}", + notes = "${api.product-composite.delete-composite-product.notes}") + @ApiResponses( + value = { + @ApiResponse( + code = 400, + message =""" + Bad Request, invalid format of the request. + See response message for more information. + """), + @ApiResponse( + code = 422, + message =""" + Unprocessable entity, input parameters caused the processing to fail. + See response message for more information. + """) + }) + @DeleteMapping("products/{id}") + void deleteProduct(@PathVariable int id); +} diff --git a/store-api/src/main/java/com/siriusxi/ms/store/api/composite/StoreService.java b/store-api/src/main/java/com/siriusxi/ms/store/api/composite/StoreService.java index 989db656..990c3a57 100644 --- a/store-api/src/main/java/com/siriusxi/ms/store/api/composite/StoreService.java +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/composite/StoreService.java @@ -1,104 +1,47 @@ package com.siriusxi.ms.store.api.composite; import com.siriusxi.ms.store.api.composite.dto.ProductAggregate; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import org.springframework.web.bind.annotation.*; -import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; - -@Api("REST API for Springy Store products information.") -@RequestMapping("store/api/v1") +/** + * Interface that define the general service contract (methods) for the Store + *
    + *
  1. Service and,
  2. + *
  3. Controller interfaces.
  4. + *
+ * + * @author mohamed.taman + * @version v0.2 + * @since v0.1 + */ public interface StoreService { - /** - * Sample usage: curl $HOST:$PORT/store/api/v1/products/1 - * - * @param productId is the product that you are looking for. - * @return the product info, if found, else null. - */ - @ApiOperation( - value = "${api.product-composite.get-composite-product.description}", - notes = "${api.product-composite.get-composite-product.notes}") - @ApiResponses( - value = { - @ApiResponse( - code = 400, - message = """ - Bad Request, invalid format of the request. - See response message for more information. - """), - @ApiResponse(code = 404, message = "Not found, the specified id does not exist."), - @ApiResponse( - code = 422, - message = """ - Unprocessable entity, input parameters caused the processing to fails. - See response message for more information. - """) - }) - @GetMapping(value = "products/{productId}", - produces = APPLICATION_JSON_VALUE) - ProductAggregate getProduct(@PathVariable int productId); + /** + * Add composite product to the product, review, and recommendation repositories. + * + * @see ProductAggregate + * @param body product to save. + * @since v0.1 + */ + void createProduct(ProductAggregate body); - /** - * Sample usage: - * - *

curl -X POST $HOST:$PORT/store/api/v1/products \ - * -H "Content-Type: application/json" --data \ - * '{"productId":123,"name":"product 123", "weight":123}' - * - * @param body of product elements definition. - */ - @ApiOperation( - value = "${api.product-composite.create-composite-product.description}", - notes = "${api.product-composite.create-composite-product.notes}") - @ApiResponses( - value = { - @ApiResponse( - code = 400, - message = """ - Bad Request, invalid format of the request. - See response message for more information. - """), - @ApiResponse( - code = 422, - message = """ - Unprocessable entity, input parameters caused the processing to fail. - See response message for more information. - """) - }) - @PostMapping( - value = "products", - consumes = APPLICATION_JSON_VALUE) - void createProduct(@RequestBody ProductAggregate body); + /** + * Get the aggregate product with its reviews and recommendations and services involved in + * the call. + * + * @see ProductAggregate + * @param id is the product id that you are looking for. + * @return the product, if found, else null. + * @since v0.1 + */ + ProductAggregate getProduct(int id); - /** - * Sample usage: - * - *

curl -X DELETE $HOST:$PORT/store/api/v1/products/1 - * - * @param productId to delete. - */ - @ApiOperation( - value = "${api.product-composite.delete-composite-product.description}", - notes = "${api.product-composite.delete-composite-product.notes}") - @ApiResponses( - value = { - @ApiResponse( - code = 400, - message =""" - Bad Request, invalid format of the request. - See response message for more information. - """), - @ApiResponse( - code = 422, - message =""" - Unprocessable entity, input parameters caused the processing to fail. - See response message for more information. - """) - }) - @DeleteMapping("products/{productId}") - void deleteProduct(@PathVariable int productId); + /** + * Delete the product and all its relate reviews and recommendations from their repositories. + * + * @see ProductAggregate + * @implNote This method should be idempotent and always return 200 OK status. + * @param id to be deleted. + * @since v0.1 + */ + void deleteProduct(int id); } diff --git a/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductEndpoint.java b/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductEndpoint.java index e835d730..13912d65 100644 --- a/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductEndpoint.java +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductEndpoint.java @@ -11,7 +11,7 @@ * to define Product Service endpoint APIs that follow ProductService * interface. And to be implemented by service controllers. * - * @see com.siriusxi.ms.store.api.core.product.ProductService + * @see ProductService * * @author mohamed.taman * @version v1.0 diff --git a/store-service/src/main/java/com/siriusxi/ms/store/pcs/api/StoreController.java b/store-service/src/main/java/com/siriusxi/ms/store/pcs/api/StoreController.java new file mode 100644 index 00000000..55d7bb14 --- /dev/null +++ b/store-service/src/main/java/com/siriusxi/ms/store/pcs/api/StoreController.java @@ -0,0 +1,45 @@ +package com.siriusxi.ms.store.pcs.api; + +import com.siriusxi.ms.store.api.composite.StoreEndpoint; +import com.siriusxi.ms.store.api.composite.StoreService; +import com.siriusxi.ms.store.api.composite.dto.ProductAggregate; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@Log4j2 +public class StoreController implements StoreEndpoint { + /** Store service business logic interface. */ + private final StoreService storeService; + + @Autowired + public StoreController(@Qualifier("StoreServiceImpl") StoreService storeService) { + this.storeService = storeService; + } + + /** + * {@inheritDoc} + */ + @Override + public ProductAggregate getProduct(int id) { + return storeService.getProduct(id); + } + + /** + * {@inheritDoc} + */ + @Override + public void createProduct(ProductAggregate body) { + storeService.createProduct(body); + } + + /** + * {@inheritDoc} + */ + @Override + public void deleteProduct(int id) { + storeService.deleteProduct(id); + } +} diff --git a/store-service/src/main/java/com/siriusxi/ms/store/pcs/controller/StoreServiceImpl.java b/store-service/src/main/java/com/siriusxi/ms/store/pcs/controller/StoreServiceImpl.java deleted file mode 100644 index 7f5ac4df..00000000 --- a/store-service/src/main/java/com/siriusxi/ms/store/pcs/controller/StoreServiceImpl.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.siriusxi.ms.store.pcs.controller; - -import com.siriusxi.ms.store.api.composite.StoreService; -import com.siriusxi.ms.store.api.composite.dto.ProductAggregate; -import com.siriusxi.ms.store.api.composite.dto.RecommendationSummary; -import com.siriusxi.ms.store.api.composite.dto.ReviewSummary; -import com.siriusxi.ms.store.api.composite.dto.ServiceAddresses; -import com.siriusxi.ms.store.api.core.product.dto.Product; -import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; -import com.siriusxi.ms.store.api.core.review.dto.Review; -import com.siriusxi.ms.store.pcs.integration.StoreIntegration; -import com.siriusxi.ms.store.util.exceptions.NotFoundException; -import com.siriusxi.ms.store.util.http.ServiceUtil; -import lombok.extern.log4j.Log4j2; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; -import java.util.stream.Collectors; - -@RestController -@Log4j2 -public class StoreServiceImpl implements StoreService { - - private final ServiceUtil serviceUtil; - private final StoreIntegration integration; - - @Autowired - public StoreServiceImpl(ServiceUtil serviceUtil, - StoreIntegration integration) { - this.serviceUtil = serviceUtil; - this.integration = integration; - } - - @Override - public void createProduct(ProductAggregate body) { - - try { - - log.debug("createCompositeProduct: creates a new composite entity for productId: {}", - body.getProductId()); - - Product product = new Product(body.getProductId(), body.getName(), body.getWeight(), null); - integration.createProduct(product); - - if (body.getRecommendations() != null) { - body.getRecommendations().forEach(r -> { - Recommendation recommendation = new Recommendation(body.getProductId(), r.getRecommendationId(), r.getAuthor(), r.getRate(), r.getContent(), null); - integration.createRecommendation(recommendation); - }); - } - - if (body.getReviews() != null) { - body.getReviews().forEach(r -> { - Review review = new Review(body.getProductId(), r.getReviewId(), r.getAuthor(), r.getSubject(), r.getContent(), null); - integration.createReview(review); - }); - } - - log.debug("createCompositeProduct: composite entites created for productId: {}", - body.getProductId()); - - } catch (RuntimeException re) { - log.warn("createCompositeProduct failed", re); - throw re; - } - } - - @Override - public ProductAggregate getProduct(int productId) { - log.debug("getCompositeProduct: lookup a product aggregate for productId: {}", productId); - - Product product = integration.getProduct(productId); - if (product == null) throw new NotFoundException("No product found for productId: " + productId); - - List recommendations = integration.getRecommendations(productId); - - List reviews = integration.getReviews(productId); - - log.debug("getCompositeProduct: aggregate entity found for productId: {}", productId); - - return createProductAggregate(product, recommendations, reviews, serviceUtil.getServiceAddress()); - } - - @Override - public void deleteProduct(int productId) { - - log.debug("deleteCompositeProduct: Deletes a product aggregate for productId: {}", - productId); - - integration.deleteProduct(productId); - - integration.deleteRecommendations(productId); - - integration.deleteReviews(productId); - - log.debug("getCompositeProduct: aggregate entities deleted for productId: {}", productId); - } - - private ProductAggregate createProductAggregate(Product product, List recommendations, List reviews, String serviceAddress) { - - // 1. Setup product info - int productId = product.getProductId(); - String name = product.getName(); - int weight = product.getWeight(); - - // 2. Copy summary recommendation info, if available - List recommendationSummaries = (recommendations == null) ? null : - recommendations.stream() - .map(r -> new RecommendationSummary(r.getRecommendationId(), r.getAuthor(), r.getRate(), r.getContent())) - .collect(Collectors.toList()); - - // 3. Copy summary review info, if available - List reviewSummaries = (reviews == null) ? null : - reviews.stream() - .map(r -> new ReviewSummary(r.getReviewId(), r.getAuthor(), r.getSubject(), r.getContent())) - .collect(Collectors.toList()); - - // 4. Create info regarding the involved microservices addresses - String productAddress = product.getServiceAddress(); - String reviewAddress = (reviews != null && reviews.size() > 0) ? reviews.get(0).getServiceAddress() : ""; - String recommendationAddress = (recommendations != null && recommendations.size() > 0) ? recommendations.get(0).getServiceAddress() : ""; - ServiceAddresses serviceAddresses = new ServiceAddresses(serviceAddress, productAddress, reviewAddress, recommendationAddress); - - return new ProductAggregate(productId, name, weight, recommendationSummaries, reviewSummaries, serviceAddresses); - } -} diff --git a/store-service/src/main/java/com/siriusxi/ms/store/pcs/service/StoreServiceImpl.java b/store-service/src/main/java/com/siriusxi/ms/store/pcs/service/StoreServiceImpl.java new file mode 100644 index 00000000..0882c066 --- /dev/null +++ b/store-service/src/main/java/com/siriusxi/ms/store/pcs/service/StoreServiceImpl.java @@ -0,0 +1,165 @@ +package com.siriusxi.ms.store.pcs.service; + +import com.siriusxi.ms.store.api.composite.StoreService; +import com.siriusxi.ms.store.api.composite.dto.ProductAggregate; +import com.siriusxi.ms.store.api.composite.dto.RecommendationSummary; +import com.siriusxi.ms.store.api.composite.dto.ReviewSummary; +import com.siriusxi.ms.store.api.composite.dto.ServiceAddresses; +import com.siriusxi.ms.store.api.core.product.dto.Product; +import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; +import com.siriusxi.ms.store.api.core.review.dto.Review; +import com.siriusxi.ms.store.pcs.integration.StoreIntegration; +import com.siriusxi.ms.store.util.exceptions.NotFoundException; +import com.siriusxi.ms.store.util.http.ServiceUtil; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +// FIXME to add all the checks for empty collections +@Service("StoreServiceImpl") +@Log4j2 +public class StoreServiceImpl implements StoreService { + + private final ServiceUtil serviceUtil; + private final StoreIntegration integration; + + @Autowired + public StoreServiceImpl(ServiceUtil serviceUtil, StoreIntegration integration) { + this.serviceUtil = serviceUtil; + this.integration = integration; + } + + @Override + public void createProduct(ProductAggregate body) { + + try { + + log.debug( + "createCompositeProduct: creates a new composite entity for id: {}", body.getProductId()); + + Product product = new Product(body.getProductId(), body.getName(), body.getWeight(), null); + integration.createProduct(product); + + if (body.getRecommendations() != null) { + body.getRecommendations() + .forEach( + r -> { + Recommendation recommendation = + new Recommendation( + body.getProductId(), + r.getRecommendationId(), + r.getAuthor(), + r.getRate(), + r.getContent(), + null); + integration.createRecommendation(recommendation); + }); + } + + if (body.getReviews() != null) { + body.getReviews() + .forEach( + r -> { + Review review = + new Review( + body.getProductId(), + r.getReviewId(), + r.getAuthor(), + r.getSubject(), + r.getContent(), + null); + integration.createReview(review); + }); + } + + log.debug( + "createCompositeProduct: composite entites created for id: {}", body.getProductId()); + + } catch (RuntimeException re) { + log.warn("createCompositeProduct failed", re); + throw re; + } + } + + @Override + public ProductAggregate getProduct(int id) { + log.debug("getCompositeProduct: lookup a product aggregate for id: {}", id); + + Product product = integration.getProduct(id); + if (product == null) throw new NotFoundException("No product found for id: " + id); + + List recommendations = integration.getRecommendations(id); + + List reviews = integration.getReviews(id); + + log.debug("getCompositeProduct: aggregate entity found for id: {}", id); + + return createProductAggregate( + product, recommendations, reviews, serviceUtil.getServiceAddress()); + } + + @Override + public void deleteProduct(int id) { + + log.debug("deleteCompositeProduct: Deletes a product aggregate for id: {}", id); + + integration.deleteProduct(id); + + integration.deleteRecommendations(id); + + integration.deleteReviews(id); + + log.debug("getCompositeProduct: aggregate entities deleted for id: {}", id); + } + + private ProductAggregate createProductAggregate( + Product product, + List recommendations, + List reviews, + String serviceAddress) { + + // 1. Setup product info + int id = product.getProductId(); + String name = product.getName(); + int weight = product.getWeight(); + + // 2. Copy summary recommendation info, if available + List recommendationSummaries = + (recommendations == null) + ? null + : recommendations.stream() + .map( + r -> + new RecommendationSummary( + r.getRecommendationId(), r.getAuthor(), r.getRate(), r.getContent())) + .collect(Collectors.toList()); + + // 3. Copy summary review info, if available + List reviewSummaries = + (reviews == null) + ? null + : reviews.stream() + .map( + r -> + new ReviewSummary( + r.getReviewId(), r.getAuthor(), r.getSubject(), r.getContent())) + .collect(Collectors.toList()); + + // 4. Create info regarding the involved microservices addresses + String productAddress = product.getServiceAddress(); + String reviewAddress = + (reviews != null && reviews.size() > 0) ? reviews.get(0).getServiceAddress() : ""; + String recommendationAddress = + (recommendations != null && recommendations.size() > 0) + ? recommendations.get(0).getServiceAddress() + : ""; + ServiceAddresses serviceAddresses = + new ServiceAddresses(serviceAddress, productAddress, reviewAddress, recommendationAddress); + + return new ProductAggregate( + id, name, weight, recommendationSummaries, reviewSummaries, serviceAddresses); + } +} From e89c43fc9820c583845658938cab15ce73490f7e Mon Sep 17 00:00:00 2001 From: Mohamed Taman Date: Tue, 14 Apr 2020 20:06:50 +0200 Subject: [PATCH 7/7] Fix duplicated error test cases. --- .gitignore | 3 + .../store/ps/service/ProductServiceImpl.java | 86 +++--- .../ps/ProductServiceApplicationTests.java | 240 ++++++++------- .../rs/service/RecommendationServiceImpl.java | 3 +- ...RecommendationServiceApplicationTests.java | 291 ++++++++++-------- 5 files changed, 332 insertions(+), 291 deletions(-) diff --git a/.gitignore b/.gitignore index 139c43af..dfce618d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +## Mac files +.DS_Store + ## Drawio source files ## *.drawio diff --git a/product-service/src/main/java/com/siriusxi/ms/store/ps/service/ProductServiceImpl.java b/product-service/src/main/java/com/siriusxi/ms/store/ps/service/ProductServiceImpl.java index 9bcd57ad..03dce467 100644 --- a/product-service/src/main/java/com/siriusxi/ms/store/ps/service/ProductServiceImpl.java +++ b/product-service/src/main/java/com/siriusxi/ms/store/ps/service/ProductServiceImpl.java @@ -1,8 +1,7 @@ package com.siriusxi.ms.store.ps.service; -import com.mongodb.DuplicateKeyException; -import com.siriusxi.ms.store.api.core.product.dto.Product; import com.siriusxi.ms.store.api.core.product.ProductService; +import com.siriusxi.ms.store.api.core.product.dto.Product; import com.siriusxi.ms.store.ps.persistence.ProductEntity; import com.siriusxi.ms.store.ps.persistence.ProductRepository; import com.siriusxi.ms.store.util.exceptions.InvalidInputException; @@ -10,63 +9,66 @@ import com.siriusxi.ms.store.util.http.ServiceUtil; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; @Service("ProductServiceImpl") @Log4j2 public class ProductServiceImpl implements ProductService { - private final ServiceUtil serviceUtil; + private final ServiceUtil serviceUtil; - private final ProductRepository repository; + private final ProductRepository repository; - private final ProductMapper mapper; + private final ProductMapper mapper; - @Autowired - public ProductServiceImpl(ProductRepository repository, - ProductMapper mapper, - ServiceUtil serviceUtil) { - this.repository = repository; - this.mapper = mapper; - this.serviceUtil = serviceUtil; - } + @Autowired + public ProductServiceImpl( + ProductRepository repository, ProductMapper mapper, ServiceUtil serviceUtil) { + this.repository = repository; + this.mapper = mapper; + this.serviceUtil = serviceUtil; + } - @Override - public Product createProduct(Product body) { - try { - ProductEntity entity = mapper.apiToEntity(body); - ProductEntity newEntity = repository.save(entity); + @Override + public Product createProduct(Product body) { + try { + ProductEntity entity = mapper.apiToEntity(body); + ProductEntity newEntity = repository.save(entity); - log.debug("createProduct: entity created for productId: {}", body.getProductId()); - return mapper.entityToApi(newEntity); + log.debug("createProduct: entity created for productId: {}", body.getProductId()); + return mapper.entityToApi(newEntity); - } catch (DuplicateKeyException dke) { - throw new InvalidInputException("Duplicate key, Product Id: " + body.getProductId()); - } + } catch (DuplicateKeyException dke) { + throw new InvalidInputException("Duplicate key, Product Id: " + body.getProductId()); } + } - @Override - public Product getProduct(int productId) { - if (productId < 1) throw new InvalidInputException("Invalid productId: " + productId); + @Override + public Product getProduct(int productId) { + if (productId < 1) throw new InvalidInputException("Invalid productId: " + productId); - ProductEntity entity = repository.findByProductId(productId) - .orElseThrow(() -> new NotFoundException("No product found for productId: " + productId)); + ProductEntity entity = + repository + .findByProductId(productId) + .orElseThrow( + () -> new NotFoundException("No product found for productId: " + productId)); - Product response = mapper.entityToApi(entity); - response.setServiceAddress(serviceUtil.getServiceAddress()); + Product response = mapper.entityToApi(entity); + response.setServiceAddress(serviceUtil.getServiceAddress()); - log.debug("getProduct: found productId: {}", response.getProductId()); + log.debug("getProduct: found productId: {}", response.getProductId()); - return response; - } + return response; + } - /* - Implementation is idempotent, that is, - it will not report any failure if the entity is not found Always 200 - */ - @Override - public void deleteProduct(int productId) { - log.debug("deleteProduct: tries to delete an entity with productId: {}", productId); - repository.findByProductId(productId).ifPresent(repository::delete); - } + /* + Implementation is idempotent, that is, + it will not report any failure if the entity is not found Always 200 + */ + @Override + public void deleteProduct(int productId) { + log.debug("deleteProduct: tries to delete an entity with productId: {}", productId); + repository.findByProductId(productId).ifPresent(repository::delete); + } } diff --git a/product-service/src/test/java/com/siriusxi/ms/store/ps/ProductServiceApplicationTests.java b/product-service/src/test/java/com/siriusxi/ms/store/ps/ProductServiceApplicationTests.java index cfc36a27..e8c7af7e 100644 --- a/product-service/src/test/java/com/siriusxi/ms/store/ps/ProductServiceApplicationTests.java +++ b/product-service/src/test/java/com/siriusxi/ms/store/ps/ProductServiceApplicationTests.java @@ -3,7 +3,6 @@ import com.siriusxi.ms.store.api.core.product.dto.Product; import com.siriusxi.ms.store.ps.persistence.ProductRepository; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -17,125 +16,140 @@ import static org.springframework.http.HttpStatus.*; import static org.springframework.http.MediaType.APPLICATION_JSON; -@SpringBootTest(webEnvironment= RANDOM_PORT, properties = {"spring.data.mongodb.port: 0"}) +@SpringBootTest( + webEnvironment = RANDOM_PORT, + properties = {"spring.data.mongodb.port: 0"}) class ProductServiceApplicationTests { - private final String BASE_URI = "/products/"; + private final String BASE_URI = "/products/"; - @Autowired - private WebTestClient client; + @Autowired private WebTestClient client; - @Autowired - private ProductRepository repository; + @Autowired private ProductRepository repository; - @BeforeEach - public void setupDb() { - repository.deleteAll(); - } + @BeforeEach + public void setupDb() { + repository.deleteAll(); + } - @Test - public void getProductById() { + @Test + public void getProductById() { - int productId = 1; + int productId = 1; - postAndVerifyProduct(productId, OK); - - assertTrue(repository.findByProductId(productId).isPresent()); - - getAndVerifyProduct(productId, OK) - .jsonPath("$.productId").isEqualTo(productId); - } - - @Test - @Disabled - public void duplicateError() { - - int productId = 1; - - postAndVerifyProduct(productId, OK); - - assertTrue(repository.findByProductId(productId).isPresent()); - - postAndVerifyProduct(productId, UNPROCESSABLE_ENTITY) - .jsonPath("$.path").isEqualTo("BASE_RESOURCE_URI") - .jsonPath("$.message").isEqualTo("Duplicate key, Product Id: " + productId); - } - - @Test - public void deleteProduct() { - - int productId = 1; - - postAndVerifyProduct(productId, OK); - assertTrue(repository.findByProductId(productId).isPresent()); - - deleteAndVerifyProduct(productId); - assertFalse(repository.findByProductId(productId).isPresent()); - - deleteAndVerifyProduct(productId); - } - - @Test - public void getProductInvalidParameterString() { - - getAndVerifyProduct(BASE_URI + "/no-integer", BAD_REQUEST) - .jsonPath("$.path").isEqualTo(BASE_URI + "no-integer") - .jsonPath("$.message").isEqualTo("Type mismatch."); - } - - @Test - public void getProductNotFound() { - - int productIdNotFound = 13; - getAndVerifyProduct(productIdNotFound, NOT_FOUND) - .jsonPath("$.path").isEqualTo(BASE_URI + productIdNotFound) - .jsonPath("$.message").isEqualTo("No product found for productId: " + productIdNotFound); - } - - @Test - public void getProductInvalidParameterNegativeValue() { - - int productIdInvalid = -1; - - getAndVerifyProduct(productIdInvalid, UNPROCESSABLE_ENTITY) - .jsonPath("$.path").isEqualTo(BASE_URI + productIdInvalid) - .jsonPath("$.message").isEqualTo("Invalid productId: " + productIdInvalid); - } - - - private WebTestClient.BodyContentSpec getAndVerifyProduct(int productId, HttpStatus expectedStatus) { - return getAndVerifyProduct(BASE_URI + productId, expectedStatus); - } - - private WebTestClient.BodyContentSpec getAndVerifyProduct(String productIdPath, HttpStatus expectedStatus) { - return client.get() - .uri(productIdPath) - .accept(APPLICATION_JSON) - .exchange() - .expectStatus().isEqualTo(expectedStatus) - .expectHeader().contentType(APPLICATION_JSON) - .expectBody(); - } - - private WebTestClient.BodyContentSpec postAndVerifyProduct(int productId, HttpStatus expectedStatus) { - Product product = new Product(productId, "Name " + productId, productId, "SA"); - return client.post() - .uri(BASE_URI) - .body(Mono.just(product), Product.class) - .accept(APPLICATION_JSON) - .exchange() - .expectStatus().isEqualTo(expectedStatus) - .expectHeader().contentType(APPLICATION_JSON) - .expectBody(); - } - - private void deleteAndVerifyProduct(int productId) { - client.delete() - .uri(BASE_URI + productId) - .accept(APPLICATION_JSON) - .exchange() - .expectStatus().isEqualTo(OK) - .expectBody(); - } + postAndVerifyProduct(productId, OK); + assertTrue(repository.findByProductId(productId).isPresent()); + + getAndVerifyProduct(productId, OK).jsonPath("$.productId").isEqualTo(productId); + } + + @Test + public void duplicateError() { + + int productId = 1; + + postAndVerifyProduct(productId, OK); + + assertTrue(repository.findByProductId(productId).isPresent()); + + postAndVerifyProduct(productId, UNPROCESSABLE_ENTITY) + .jsonPath("$.path") + .isEqualTo(BASE_URI) + .jsonPath("$.message") + .isEqualTo("Duplicate key, Product Id: " + productId); + } + + @Test + public void deleteProduct() { + + int productId = 1; + + postAndVerifyProduct(productId, OK); + assertTrue(repository.findByProductId(productId).isPresent()); + + deleteAndVerifyProduct(productId); + assertFalse(repository.findByProductId(productId).isPresent()); + + deleteAndVerifyProduct(productId); + } + + @Test + public void getProductInvalidParameterString() { + + getAndVerifyProduct(BASE_URI + "/no-integer", BAD_REQUEST) + .jsonPath("$.path") + .isEqualTo(BASE_URI + "no-integer") + .jsonPath("$.message") + .isEqualTo("Type mismatch."); + } + + @Test + public void getProductNotFound() { + + int productIdNotFound = 13; + getAndVerifyProduct(productIdNotFound, NOT_FOUND) + .jsonPath("$.path") + .isEqualTo(BASE_URI + productIdNotFound) + .jsonPath("$.message") + .isEqualTo("No product found for productId: " + productIdNotFound); + } + + @Test + public void getProductInvalidParameterNegativeValue() { + + int productIdInvalid = -1; + + getAndVerifyProduct(productIdInvalid, UNPROCESSABLE_ENTITY) + .jsonPath("$.path") + .isEqualTo(BASE_URI + productIdInvalid) + .jsonPath("$.message") + .isEqualTo("Invalid productId: " + productIdInvalid); + } + + private WebTestClient.BodyContentSpec getAndVerifyProduct( + int productId, HttpStatus expectedStatus) { + return getAndVerifyProduct(BASE_URI + productId, expectedStatus); + } + + private WebTestClient.BodyContentSpec getAndVerifyProduct( + String productIdPath, HttpStatus expectedStatus) { + return client + .get() + .uri(productIdPath) + .accept(APPLICATION_JSON) + .exchange() + .expectStatus() + .isEqualTo(expectedStatus) + .expectHeader() + .contentType(APPLICATION_JSON) + .expectBody(); + } + + private WebTestClient.BodyContentSpec postAndVerifyProduct( + int productId, HttpStatus expectedStatus) { + Product product = new Product(productId, "Name " + productId, productId, "SA"); + return client + .post() + .uri(BASE_URI) + .body(Mono.just(product), Product.class) + .accept(APPLICATION_JSON) + .exchange() + .expectStatus() + .isEqualTo(expectedStatus) + .expectHeader() + .contentType(APPLICATION_JSON) + .expectBody(); + } + + private void deleteAndVerifyProduct(int productId) { + client + .delete() + .uri(BASE_URI + productId) + .accept(APPLICATION_JSON) + .exchange() + .expectStatus() + .isEqualTo(OK) + .expectBody(); + } } diff --git a/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/service/RecommendationServiceImpl.java b/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/service/RecommendationServiceImpl.java index 27883563..3cabadde 100644 --- a/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/service/RecommendationServiceImpl.java +++ b/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/service/RecommendationServiceImpl.java @@ -1,7 +1,5 @@ package com.siriusxi.ms.store.rs.service; -import com.mongodb.DuplicateKeyException; - import com.siriusxi.ms.store.api.core.recommendation.RecommendationService; import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; import com.siriusxi.ms.store.rs.persistence.RecommendationEntity; @@ -10,6 +8,7 @@ import com.siriusxi.ms.store.util.http.ServiceUtil; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import java.util.List; diff --git a/recommendation-service/src/test/java/com/siriusxi/ms/store/rs/RecommendationServiceApplicationTests.java b/recommendation-service/src/test/java/com/siriusxi/ms/store/rs/RecommendationServiceApplicationTests.java index b22cf9f2..53059e8b 100644 --- a/recommendation-service/src/test/java/com/siriusxi/ms/store/rs/RecommendationServiceApplicationTests.java +++ b/recommendation-service/src/test/java/com/siriusxi/ms/store/rs/RecommendationServiceApplicationTests.java @@ -3,7 +3,6 @@ import com.siriusxi.ms.store.api.core.recommendation.dto.Recommendation; import com.siriusxi.ms.store.rs.persistence.RecommendationRepository; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -17,147 +16,171 @@ import static org.springframework.http.HttpStatus.*; import static org.springframework.http.MediaType.APPLICATION_JSON; -@SpringBootTest(webEnvironment = RANDOM_PORT, properties = {"spring.data.mongodb.port: 0"}) +@SpringBootTest( + webEnvironment = RANDOM_PORT, + properties = {"spring.data.mongodb.port: 0"}) class RecommendationServiceApplicationTests { - private final String BASE_URI = "/recommendations"; - @Autowired - private WebTestClient client; + private final String BASE_URI = "/recommendations"; + @Autowired private WebTestClient client; - @Autowired - private RecommendationRepository repository; + @Autowired private RecommendationRepository repository; + @BeforeEach + public void setupDb() { + repository.deleteAll(); + } - @BeforeEach - public void setupDb() { - repository.deleteAll(); - } + @Test + public void getRecommendationsByProductId() { - @Test - public void getRecommendationsByProductId() { + int productId = 1; - int productId = 1; + postAndVerifyRecommendation(productId, 1, OK); + postAndVerifyRecommendation(productId, 2, OK); + postAndVerifyRecommendation(productId, 3, OK); - postAndVerifyRecommendation(productId, 1, OK); - postAndVerifyRecommendation(productId, 2, OK); - postAndVerifyRecommendation(productId, 3, OK); + assertEquals(3, repository.findByProductId(productId).size()); - assertEquals(3, repository.findByProductId(productId).size()); - - getAndVerifyRecommendationsByProductId(productId, OK) - .jsonPath("$.length()").isEqualTo(3) - .jsonPath("$[2].productId").isEqualTo(productId) - .jsonPath("$[2].recommendationId").isEqualTo(3); - } - - @Test - @Disabled - public void duplicateError() { - - int productId = 1; - int recommendationId = 1; - - postAndVerifyRecommendation(productId, recommendationId, OK) - .jsonPath("$.productId").isEqualTo(productId) - .jsonPath("$.recommendationId").isEqualTo(recommendationId); - - assertEquals(1, repository.count()); - - postAndVerifyRecommendation(productId, recommendationId, UNPROCESSABLE_ENTITY) - .jsonPath("$.path").isEqualTo(BASE_URI) - .jsonPath("$.message").isEqualTo("Duplicate key, Product Id: 1, Recommendation Id:1"); - - assertEquals(1, repository.count()); - } - - @Test - public void deleteRecommendations() { - - int productId = 1; - int recommendationId = 1; - - postAndVerifyRecommendation(productId, recommendationId, OK); - assertEquals(1, repository.findByProductId(productId).size()); - - deleteAndVerifyRecommendationsByProductIdIsOk(productId); - assertEquals(0, repository.findByProductId(productId).size()); - - deleteAndVerifyRecommendationsByProductIdIsOk(productId); - } - - @Test - public void getRecommendationsMissingParameter() { - - getAndVerifyRecommendationsByProductId("", BAD_REQUEST) - .jsonPath("$.path").isEqualTo(BASE_URI) - .jsonPath("$.message").isEqualTo("Required int parameter 'productId' is not present"); - } - - @Test - public void getRecommendationsInvalidParameter() { - - getAndVerifyRecommendationsByProductId("?productId=no-integer", BAD_REQUEST) - .jsonPath("$.path").isEqualTo(BASE_URI) - .jsonPath("$.message").isEqualTo("Type mismatch."); - } - - @Test - public void getRecommendationsNotFound() { - - getAndVerifyRecommendationsByProductId("?productId=113", OK) - .jsonPath("$.length()").isEqualTo(0); - } - - @Test - public void getRecommendationsInvalidParameterNegativeValue() { - - int productIdInvalid = -1; - - getAndVerifyRecommendationsByProductId("?productId=" + productIdInvalid, UNPROCESSABLE_ENTITY) - .jsonPath("$.path").isEqualTo(BASE_URI) - .jsonPath("$.message").isEqualTo("Invalid productId: " + productIdInvalid); - } - - private BodyContentSpec getAndVerifyRecommendationsByProductId(int productId, HttpStatus expectedStatus) { - return getAndVerifyRecommendationsByProductId("?productId=" + productId, expectedStatus); - } - - private BodyContentSpec getAndVerifyRecommendationsByProductId(String productIdQuery, - HttpStatus expectedStatus) { - return client.get() - .uri(BASE_URI + productIdQuery) - .accept(APPLICATION_JSON) - .exchange() - .expectStatus().isEqualTo(expectedStatus) - .expectHeader().contentType(APPLICATION_JSON) - .expectBody(); - } - - private BodyContentSpec postAndVerifyRecommendation(int productId, - int recommendationId, - HttpStatus expectedStatus) { - - Recommendation recommendation = new Recommendation(productId, - recommendationId, "Author " + recommendationId, - recommendationId, "Content " + recommendationId, "SA"); - - return client.post() - .uri(BASE_URI) - .body(Mono.just(recommendation), Recommendation.class) - .accept(APPLICATION_JSON) - .exchange() - .expectStatus().isEqualTo(expectedStatus) - .expectHeader().contentType(APPLICATION_JSON) - .expectBody(); - } - - private void deleteAndVerifyRecommendationsByProductIdIsOk(int productId) { - client.delete() - .uri(BASE_URI + "?productId=" + productId) - .accept(APPLICATION_JSON) - .exchange() - .expectStatus().isEqualTo(OK) - .expectBody(); - } + getAndVerifyRecommendationsByProductId(productId, OK) + .jsonPath("$.length()") + .isEqualTo(3) + .jsonPath("$[2].productId") + .isEqualTo(productId) + .jsonPath("$[2].recommendationId") + .isEqualTo(3); + } + @Test + public void duplicateError() { + + int productId = 1; + int recommendationId = 1; + + postAndVerifyRecommendation(productId, recommendationId, OK) + .jsonPath("$.productId") + .isEqualTo(productId) + .jsonPath("$.recommendationId") + .isEqualTo(recommendationId); + + assertEquals(1, repository.count()); + + postAndVerifyRecommendation(productId, recommendationId, UNPROCESSABLE_ENTITY) + .jsonPath("$.path") + .isEqualTo(BASE_URI) + .jsonPath("$.message") + .isEqualTo("Duplicate key, Product Id: 1, Recommendation Id:1"); + + assertEquals(1, repository.count()); + } + + @Test + public void deleteRecommendations() { + + int productId = 1; + int recommendationId = 1; + + postAndVerifyRecommendation(productId, recommendationId, OK); + assertEquals(1, repository.findByProductId(productId).size()); + + deleteAndVerifyRecommendationsByProductIdIsOk(productId); + assertEquals(0, repository.findByProductId(productId).size()); + + deleteAndVerifyRecommendationsByProductIdIsOk(productId); + } + + @Test + public void getRecommendationsMissingParameter() { + + getAndVerifyRecommendationsByProductId("", BAD_REQUEST) + .jsonPath("$.path") + .isEqualTo(BASE_URI) + .jsonPath("$.message") + .isEqualTo("Required int parameter 'productId' is not present"); + } + + @Test + public void getRecommendationsInvalidParameter() { + + getAndVerifyRecommendationsByProductId("?productId=no-integer", BAD_REQUEST) + .jsonPath("$.path") + .isEqualTo(BASE_URI) + .jsonPath("$.message") + .isEqualTo("Type mismatch."); + } + + @Test + public void getRecommendationsNotFound() { + + getAndVerifyRecommendationsByProductId("?productId=113", OK) + .jsonPath("$.length()") + .isEqualTo(0); + } + + @Test + public void getRecommendationsInvalidParameterNegativeValue() { + + int productIdInvalid = -1; + + getAndVerifyRecommendationsByProductId("?productId=" + productIdInvalid, UNPROCESSABLE_ENTITY) + .jsonPath("$.path") + .isEqualTo(BASE_URI) + .jsonPath("$.message") + .isEqualTo("Invalid productId: " + productIdInvalid); + } + + private BodyContentSpec getAndVerifyRecommendationsByProductId( + int productId, HttpStatus expectedStatus) { + return getAndVerifyRecommendationsByProductId("?productId=" + productId, expectedStatus); + } + + private BodyContentSpec getAndVerifyRecommendationsByProductId( + String productIdQuery, HttpStatus expectedStatus) { + return client + .get() + .uri(BASE_URI + productIdQuery) + .accept(APPLICATION_JSON) + .exchange() + .expectStatus() + .isEqualTo(expectedStatus) + .expectHeader() + .contentType(APPLICATION_JSON) + .expectBody(); + } + + private BodyContentSpec postAndVerifyRecommendation( + int productId, int recommendationId, HttpStatus expectedStatus) { + + Recommendation recommendation = + new Recommendation( + productId, + recommendationId, + "Author " + recommendationId, + recommendationId, + "Content " + recommendationId, + "SA"); + + return client + .post() + .uri(BASE_URI) + .body(Mono.just(recommendation), Recommendation.class) + .accept(APPLICATION_JSON) + .exchange() + .expectStatus() + .isEqualTo(expectedStatus) + .expectHeader() + .contentType(APPLICATION_JSON) + .expectBody(); + } + + private void deleteAndVerifyRecommendationsByProductIdIsOk(int productId) { + client + .delete() + .uri(BASE_URI + "?productId=" + productId) + .accept(APPLICATION_JSON) + .exchange() + .expectStatus() + .isEqualTo(OK) + .expectBody(); + } }