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/api/ProductController.java b/product-service/src/main/java/com/siriusxi/ms/store/ps/api/ProductController.java new file mode 100644 index 00000000..1e549f1b --- /dev/null +++ b/product-service/src/main/java/com/siriusxi/ms/store/ps/api/ProductController.java @@ -0,0 +1,49 @@ +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.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 Endpoint API + * definition. + * + * @see ProductEndpoint + * @author mohamed.taman + * @version v1.0 + * @since v3.0 codename Storm + */ +@RestController +@Log4j2 +public class ProductController implements ProductEndpoint { + + /** 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/ProductServiceImpl.java b/product-service/src/main/java/com/siriusxi/ms/store/ps/controller/ProductServiceImpl.java deleted file mode 100644 index c2152311..00000000 --- a/product-service/src/main/java/com/siriusxi/ms/store/ps/controller/ProductServiceImpl.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.siriusxi.ms.store.ps.controller; - -import com.mongodb.DuplicateKeyException; -import com.siriusxi.ms.store.api.core.product.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; -import com.siriusxi.ms.store.util.exceptions.InvalidInputException; -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; - -@RestController -@Log4j2 -public class ProductServiceImpl implements ProductService { - - private final ServiceUtil serviceUtil; - - private final ProductRepository repository; - - private final ProductMapper mapper; - - @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); - - 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()); - } - } - - @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)); - - Product response = mapper.entityToApi(entity); - response.setServiceAddress(serviceUtil.getServiceAddress()); - - log.debug("getProduct: found productId: {}", response.getProductId()); - - return response; - } - - @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/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/service/ProductServiceImpl.java b/product-service/src/main/java/com/siriusxi/ms/store/ps/service/ProductServiceImpl.java new file mode 100644 index 00000000..03dce467 --- /dev/null +++ b/product-service/src/main/java/com/siriusxi/ms/store/ps/service/ProductServiceImpl.java @@ -0,0 +1,74 @@ +package com.siriusxi.ms.store.ps.service; + +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; +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.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; + +@Service("ProductServiceImpl") +@Log4j2 +public class ProductServiceImpl implements ProductService { + + private final ServiceUtil serviceUtil; + + private final ProductRepository repository; + + private final ProductMapper mapper; + + @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); + + 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()); + } + } + + @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)); + + Product response = mapper.entityToApi(entity); + response.setServiceAddress(serviceUtil.getServiceAddress()); + + log.debug("getProduct: found productId: {}", response.getProductId()); + + 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); + } +} diff --git a/product-service/src/main/resources/application.yaml b/product-service/src/main/resources/application.yaml index 0c7cdc9d..11a6c7ba 100644 --- a/product-service/src/main/resources/application.yaml +++ b/product-service/src/main/resources/application.yaml @@ -6,7 +6,8 @@ spring: host: localhost port: 27017 database: product-db - + auto-index-creation: true + server: port: 9081 @@ -39,4 +40,4 @@ spring: host: mongodb server: - port: 8080 \ No newline at end of file + port: 8080 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/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/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..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 @@ -1,9 +1,8 @@ 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; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -17,126 +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, OK); - assertFalse(repository.findByProductId(productId).isPresent()); - - deleteAndVerifyProduct(productId, OK); - } - - @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 WebTestClient.BodyContentSpec deleteAndVerifyProduct(int productId, HttpStatus expectedStatus) { - return client.delete() - .uri(BASE_URI + productId) - .accept(APPLICATION_JSON) - .exchange() - .expectStatus().isEqualTo(expectedStatus) - .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/api/RecommendationController.java b/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/api/RecommendationController.java new file mode 100644 index 00000000..a96f96a1 --- /dev/null +++ b/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/api/RecommendationController.java @@ -0,0 +1,51 @@ +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..3cabadde --- /dev/null +++ b/recommendation-service/src/main/java/com/siriusxi/ms/store/rs/service/RecommendationServiceImpl.java @@ -0,0 +1,78 @@ +package com.siriusxi.ms.store.rs.service; + +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.dao.DuplicateKeyException; +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/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/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/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, 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..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 @@ -1,9 +1,8 @@ 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; 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(); + } } 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 10df8c72..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; @@ -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/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 new file mode 100644 index 00000000..13912d65 --- /dev/null +++ b/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductEndpoint.java @@ -0,0 +1,64 @@ +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 ProductEndpoint is a higher level Interface + * to define Product Service endpoint APIs that follow ProductService + * interface. And to be implemented by service controllers. + * + * @see ProductService + * + * @author mohamed.taman + * @version v1.0 + * @since v3.0 codename Storm + */ +@RequestMapping("products") +public interface ProductEndpoint 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. + * @since v3.0 codename Storm + */ + @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. + * @since v3.0 codename Storm + */ + @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. + * @since v3.0 codename Storm + */ + @Override + @DeleteMapping("{productId}") + void deleteProduct(@PathVariable("productId") int id); +} 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..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 @@ -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 the Product + *
    + *
  1. Service and,
  2. + *
  3. Controller interfaces.
  4. + *
+ * + * @author mohamed.taman + * @version v0.2 + * @since v0.1 + */ 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 v0.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 v0.1 */ - @PostMapping( value = "products", - produces = APPLICATION_JSON_VALUE, - consumes = APPLICATION_JSON_VALUE) - Product createProduct(@RequestBody Product body); + Product createProduct(Product body); /** - * Sample usage: - * - *

curl -X DELETE $HOST:$PORT/products/1 + * Delete the product from repository. * - * @param productId to be deleted. + * @implNote This method should be idempotent and always return 200 OK status. + * @param id to be deleted. + * @since v0.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/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-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..9cac81f1 --- /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 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..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 @@ -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-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/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 877b1f22..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.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; -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/integration/StoreIntegration.java b/store-service/src/main/java/com/siriusxi/ms/store/pcs/integration/StoreIntegration.java index 1da5d2b0..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 @@ -1,11 +1,11 @@ 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; -import com.siriusxi.ms.store.api.core.review.Review; +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.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; @@ -30,7 +30,7 @@ public class StoreIntegration implements ProductService, - RecommendationService, + RecommendationEndpoint, ReviewService { private final RestTemplate restTemplate; 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); + } +} 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..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 @@ -3,9 +3,9 @@ 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.recommendation.Recommendation; -import com.siriusxi.ms.store.api.core.review.Review; +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.InvalidInputException; import com.siriusxi.ms.store.util.exceptions.NotFoundException;