Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/cart/cpc 1033/retrieve cart for client #623

Merged
merged 7 commits into from
Sep 16, 2024
17 changes: 11 additions & 6 deletions cart-service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ java {
}

ext{
mapstructVersion = "1.3.1.Final"
mapstructVersion = "1.5.3.Final"
lombokVersion = "1.18.26"
lombokMapstructBindingVersion = "0.2.0"
}
Expand All @@ -27,26 +27,32 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation "org.mapstruct:mapstruct:${mapstructVersion}"
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'

developmentOnly 'org.springframework.boot:spring-boot-devtools'

runtimeOnly 'com.h2database:h2'
runtimeOnly 'io.r2dbc:r2dbc-h2'
runtimeOnly 'org.postgresql:postgresql'
runtimeOnly 'org.postgresql:r2dbc-postgresql'

compileOnly "org.mapstruct:mapstruct-processor:${mapstructVersion}"
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}", "org.projectlombok:lombok:${lombokVersion}", "org.projectlombok:lombok-mapstruct-binding:${lombokMapstructBindingVersion}"
testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.r2dbc:r2dbc-h2:1.0.0.RELEASE'
testImplementation 'io.projectreactor:reactor-test'
testImplementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo.spring30x:4.9.2'
testImplementation("org.mock-server:mockserver-netty:5.13.0")
testImplementation("org.mock-server:mockserver-client-java:5.13.0")
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

}

jacoco {
toolVersion = "0.8.8"
toolVersion = "0.8.11"
}

jacocoTestReport {
Expand All @@ -55,7 +61,6 @@ jacocoTestReport {
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: [
//"com/petclinic/**/BillingServiceApplication.class"
])
}))
}
Expand All @@ -65,7 +70,7 @@ jacocoTestCoverageVerification {
violationRules {
rule {
limit {
minimum = 0.00 //has to be 90% probably
minimum = 0.00
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.example.cartservice;
package com.petclinic.cartsservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class CartServiceApplication {
Expand All @@ -10,4 +12,9 @@ public static void main(String[] args) {
SpringApplication.run(CartServiceApplication.class, args);
}

@Bean
RestTemplate restTemplate()
{
return new RestTemplate();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
package com.petclinic.cartsservice.businesslayer;


import com.petclinic.cartsservice.presentationlayer.CartResponseModel;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public interface CartService {

public Mono<CartResponseModel> getCartByCartId(String cartId);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,40 @@
package com.petclinic.cartsservice.businesslayer;

public class CartServiceImpl {
import com.petclinic.cartsservice.dataaccesslayer.CartRepository;
import com.petclinic.cartsservice.domainclientlayer.ProductClient;
import com.petclinic.cartsservice.presentationlayer.CartResponseModel;
import com.petclinic.cartsservice.utils.EntityModelUtil;
import com.petclinic.cartsservice.utils.exceptions.NotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;

@Service
@Slf4j
public class CartServiceImpl implements CartService {
private final CartRepository cartRepository;
private final ProductClient productClient;

public CartServiceImpl(CartRepository cartRepository, ProductClient productClient) {
this.cartRepository = cartRepository;
this.productClient = productClient;
}

@Override
public Mono<CartResponseModel> getCartByCartId(String cartId) {
return cartRepository.findCartByCartId(cartId)
.switchIfEmpty(Mono.defer(() -> Mono.error(new NotFoundException("Cart id was not found: " + cartId))))
.doOnNext(e -> log.debug("The cart response entity is: " + e.toString()))
.flatMap(cart -> {
List<String> productIds = cart.getProductIds();
return productIds
.stream().map(productId -> productClient.getProductByProductId(productId).flux())
.reduce(Flux.empty(), Flux::merge)
.collectList()
.map(products -> EntityModelUtil.toCartResponseModel(cart, products));
});
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
package com.petclinic.cartsservice.dataaccesslayer;

import org.springframework.data.mongodb.core.mapping.Document;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import reactor.core.publisher.Flux;

import java.util.List;


@Document(collection = "cart")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Cart {

@Id
private String id;

private String cartId;
private List<String> productIds;
private String customerId;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
package com.petclinic.cartsservice.dataaccesslayer;

public interface CartRepository {

import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import reactor.core.publisher.Mono;

public interface CartRepository extends ReactiveMongoRepository<Cart, String> {
public Mono<Cart> findCartByCartId(String cartId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.petclinic.cartsservice.domainclientlayer;

import com.petclinic.cartsservice.utils.exceptions.InvalidInputException;
import com.petclinic.cartsservice.utils.exceptions.NotFoundException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatusCode;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@Service
public class ProductClient {

private final WebClient webClient;

private final String productsBaseURL;

public ProductClient(@Value("products-service") String productsServiceHost,
@Value("8080") String productsServicePort) {
this.productsBaseURL = "http://" + productsServiceHost + ":" + productsServicePort + "/api/v1/products";

this.webClient = WebClient.builder()
.baseUrl(productsBaseURL)
.build();
}

public Mono<ProductResponseModel> getProductByProductId(String productId){
return webClient.get()
.uri("/{productId}", productId)
.retrieve()
.onStatus(HttpStatusCode::isError,
error -> switch (error.statusCode().value()) {
case 404 -> Mono.error(new NotFoundException("ProductId not found: " + productId));
case 422 -> Mono.error(new InvalidInputException("ProductId invalid: " + productId));
default -> Mono.error(new IllegalArgumentException("Something went wrong"));
})
.bodyToMono(ProductResponseModel.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.petclinic.cartsservice.domainclientlayer;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductResponseModel {

private String productId;
private String productName;
private String productDescription;
private Double productSalePrice;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,32 @@
package com.petclinic.cartsservice.presentationlayer;


import com.petclinic.cartsservice.businesslayer.CartService;
import com.petclinic.cartsservice.utils.exceptions.InvalidInputException;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/api/v1/carts")
public class CartController {

private final CartService cartService;

public CartController(CartService cartService) {
this.cartService = cartService;
}

@GetMapping(value = "/{cartId}", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<CartResponseModel>> getCartByCartId(@PathVariable String cartId) {
return Mono.just(cartId)
.filter(id -> id.length() == 36) // validate the cart id
.switchIfEmpty(Mono.error(new InvalidInputException("Provided cart id is invalid: " + cartId)))
.flatMap(cartService::getCartByCartId)
.map(ResponseEntity::ok);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
package com.petclinic.cartsservice.presentationlayer;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
//since there will be only one cart by customer when the client
// will first create his account, the cart will be empty
public class CartRequestModel {
private String customerId;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
package com.petclinic.cartsservice.presentationlayer;

import com.petclinic.cartsservice.domainclientlayer.ProductResponseModel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import reactor.core.publisher.Flux;

import java.util.List;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CartResponseModel {

private String cartId;
private String customerId;
private List<ProductResponseModel> products;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.petclinic.cartsservice.utils;


import com.petclinic.cartsservice.dataaccesslayer.Cart;
import com.petclinic.cartsservice.dataaccesslayer.CartRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;

@Service
public class DataLoaderService implements CommandLineRunner {

@Autowired
CartRepository cartRepository;

@Override
public void run(String... args) throws Exception {

List<String> productIds = List.of("9a29fff7-564a-4cc9-8fe1-36f6ca9bc223", "d819e4f4-25af-4d33-91e9-2c45f0071606");

Cart cart1 = Cart.builder()
.cartId("98f7b33a-d62a-420a-a84a-05a27c85fc91")
.productIds(productIds)
.customerId("1")
.build();


Flux.just(cart1)
.flatMap(s -> cartRepository.insert(Mono.just(s))
.log(s.toString()))
.subscribe();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.petclinic.cartsservice.utils;


import com.petclinic.cartsservice.dataaccesslayer.Cart;
import com.petclinic.cartsservice.domainclientlayer.ProductResponseModel;
import com.petclinic.cartsservice.presentationlayer.CartRequestModel;
import com.petclinic.cartsservice.presentationlayer.CartResponseModel;
import org.springframework.beans.BeanUtils;

import java.util.List;
import java.util.UUID;

public class EntityModelUtil {

public static CartResponseModel toCartResponseModel(Cart cart, List<ProductResponseModel> products) {
CartResponseModel cartResponseModel = new CartResponseModel();
BeanUtils.copyProperties(cart, cartResponseModel);
cartResponseModel.setProducts(products);
return cartResponseModel;
}

public static Cart toCartEntity(CartRequestModel cartRequestModel) {
return Cart.builder()
.cartId(generateUUIDString())
.customerId(cartRequestModel.getCustomerId())
.build();
}

public static String generateUUIDString() {
return UUID.randomUUID().toString();
}
}
Loading
Loading