diff --git a/order-service/README.md b/order-service/README.md index b21155b6..919a8a35 100644 --- a/order-service/README.md +++ b/order-service/README.md @@ -26,5 +26,29 @@ You can also run the application using Maven as follows: * Actuator Endpoint: http://localhost:18282/order-service/actuator * Catalog Service : http://localhost:18080/catalog-service/swagger-ui.html +## Running only this Service Locally - Tips + +To run only the Order Service locally with clean logs, you can follow these steps: + + + +1.start the Kafka and Postgres servers by using below command(You should be inside appropriate directory and docker setup should be done :- ) : +```shell +docker compose up kafka postgres +``` +2.In IntelIj Open Modify Run Configuration from Main class: + `com.example.orderservice.OrderServiceApplication` +Set the Environment variable value to +```text +SPRING_PROFILES_ACTIVE=local +``` + +3.In case local profile doesn't due to any issues possible not able to connect to postgresDB +Modify the Environment Variable Value as below this brings application up by connecting to H2 database. +```text +SPRING_PROFILES_ACTIVE=h2 +``` + + ### Notes * KafkaStream DeadLetter is configured in `KafkaStreamsConfig.java` diff --git a/order-service/src/main/java/com/example/orderservice/entities/OrderItem.java b/order-service/src/main/java/com/example/orderservice/entities/OrderItem.java index 4be5736e..61994331 100644 --- a/order-service/src/main/java/com/example/orderservice/entities/OrderItem.java +++ b/order-service/src/main/java/com/example/orderservice/entities/OrderItem.java @@ -6,15 +6,7 @@ Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli. package com.example.orderservice.entities; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; +import jakarta.persistence.*; import java.math.BigDecimal; import java.util.Objects; import lombok.AllArgsConstructor; @@ -51,6 +43,12 @@ public class OrderItem { @Column(columnDefinition = "NUMERIC(19,2)") private BigDecimal productPrice; + @Transient private BigDecimal price; + + public BigDecimal getPrice() { + return productPrice.multiply(new BigDecimal(quantity)); + } + @ManyToOne(fetch = FetchType.LAZY) @ToString.Exclude private Order order; diff --git a/order-service/src/main/java/com/example/orderservice/mapper/OrderMapper.java b/order-service/src/main/java/com/example/orderservice/mapper/OrderMapper.java index 5e365ac6..24912daa 100644 --- a/order-service/src/main/java/com/example/orderservice/mapper/OrderMapper.java +++ b/order-service/src/main/java/com/example/orderservice/mapper/OrderMapper.java @@ -12,6 +12,8 @@ Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli. import com.example.orderservice.entities.OrderItem; import com.example.orderservice.model.request.OrderItemRequest; import com.example.orderservice.model.request.OrderRequest; +import com.example.orderservice.model.response.OrderItemResponse; +import com.example.orderservice.model.response.OrderResponse; import org.mapstruct.AfterMapping; import org.mapstruct.DecoratedWith; import org.mapstruct.Mapper; @@ -43,6 +45,7 @@ public interface OrderMapper { @Mapping(target = "id", ignore = true) @Mapping(target = "order", ignore = true) + @Mapping(target = "price", ignore = true) OrderItem orderItemRequestToOrderItem(OrderItemRequest orderItemRequest); @AfterMapping @@ -65,4 +68,11 @@ default void addOrderItemRequestToOrderEntity( @Mapping(target = "lastModifiedBy", ignore = true) @Mapping(target = "lastModifiedDate", ignore = true) void updateOrderFromOrderRequest(OrderRequest orderRequest, @MappingTarget Order order); + + @Mapping(source = "id", target = "orderId") + OrderResponse toResponse(Order order); + + @Mapping(target = "itemId", source = "id") + @Mapping(target = "productId", source = "productCode") + OrderItemResponse orderItemToOrderItemResponse(OrderItem orderItem); } diff --git a/order-service/src/main/java/com/example/orderservice/model/response/OrderItemResponse.java b/order-service/src/main/java/com/example/orderservice/model/response/OrderItemResponse.java new file mode 100644 index 00000000..e90f5553 --- /dev/null +++ b/order-service/src/main/java/com/example/orderservice/model/response/OrderItemResponse.java @@ -0,0 +1,12 @@ +/*** +

+ Licensed under MIT License Copyright (c) 2023 Raja Kolli. +

+***/ + +package com.example.orderservice.model.response; + +import java.math.BigDecimal; + +public record OrderItemResponse( + Long itemId, String productId, int quantity, BigDecimal productPrice, BigDecimal price) {} diff --git a/order-service/src/main/java/com/example/orderservice/model/response/OrderResponse.java b/order-service/src/main/java/com/example/orderservice/model/response/OrderResponse.java new file mode 100644 index 00000000..fb2fc30c --- /dev/null +++ b/order-service/src/main/java/com/example/orderservice/model/response/OrderResponse.java @@ -0,0 +1,18 @@ +/*** +

+ Licensed under MIT License Copyright (c) 2023 Raja Kolli. +

+***/ + +package com.example.orderservice.model.response; + +import java.time.LocalDateTime; +import java.util.List; + +public record OrderResponse( + Long orderId, + Long customerId, + String status, + String source, + LocalDateTime createdDate, + List items) {} diff --git a/order-service/src/main/java/com/example/orderservice/repositories/OrderRepository.java b/order-service/src/main/java/com/example/orderservice/repositories/OrderRepository.java index 8a5f38f6..170f65b6 100644 --- a/order-service/src/main/java/com/example/orderservice/repositories/OrderRepository.java +++ b/order-service/src/main/java/com/example/orderservice/repositories/OrderRepository.java @@ -27,6 +27,14 @@ public interface OrderRepository extends JpaRepository { @Query("select o from Order o join fetch o.items oi where o.id = :id") Optional findOrderById(@Param("id") Long id); + @Query( + value = "select o from Order o join fetch o.items oi where o.customerId = :customerId", + countQuery = "select count(o) from Order o where o.customerId=:customerId") + Page findByCustomerId(@Param("customerId") Long customerId, Pageable pageable); + + @Query("select o.id from Order o where o.customerId = :customerId") + Page findAllOrdersByCustomerId(@Param("customerId") Long customerId, Pageable pageable); + @Modifying @Transactional @Query("update Order o set o.status =:status, o.source =:source where o.id = :id") diff --git a/order-service/src/main/java/com/example/orderservice/services/OrderService.java b/order-service/src/main/java/com/example/orderservice/services/OrderService.java index 70d2c4c7..66f42989 100644 --- a/order-service/src/main/java/com/example/orderservice/services/OrderService.java +++ b/order-service/src/main/java/com/example/orderservice/services/OrderService.java @@ -13,6 +13,7 @@ Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli. import com.example.orderservice.mapper.OrderMapper; import com.example.orderservice.model.request.OrderItemRequest; import com.example.orderservice.model.request.OrderRequest; +import com.example.orderservice.model.response.OrderResponse; import com.example.orderservice.model.response.PagedResult; import com.example.orderservice.repositories.OrderRepository; import java.util.List; @@ -128,4 +129,34 @@ public Optional findById(Long id) { public Optional findOrderByIdAsDto(Long id) { return orderRepository.findOrderById(id).map(this.orderMapper::toDto); } + + public PagedResult getOrdersByCustomerId(Long customerId, Pageable pageable) { + // Error:: JpaSystem firstResult/maxResults specified with collection fetch. In memory + // pagination was about to be applied. Failing because 'Fail on pagination over collection + // fetch' is enabled. + // To fix above error Fetches only ParentEntities ids and then using keys fetch Data. + Page page = orderRepository.findAllOrdersByCustomerId(customerId, pageable); + // fetching parentAlongWithChildEntries + List ordersWithOrderItems = orderRepository.findByIdIn(page.getContent()); + // Mapping Order to OrderResponse CompletableFuture + List> completableFutureList = + ordersWithOrderItems.stream() + .map( + order -> + CompletableFuture.supplyAsync( + () -> this.orderMapper.toResponse(order))) + .toList(); + // Joining all completeable future to get OrderResponses + List orderResponse = + completableFutureList.stream().map(CompletableFuture::join).toList(); + return new PagedResult<>( + orderResponse, + page.getTotalElements(), + page.getNumber() + 1, + page.getTotalPages(), + page.isFirst(), + page.isLast(), + page.hasNext(), + page.hasPrevious()); + } } diff --git a/order-service/src/main/java/com/example/orderservice/web/controllers/OrderController.java b/order-service/src/main/java/com/example/orderservice/web/controllers/OrderController.java index 3ca90831..ad30f6ab 100644 --- a/order-service/src/main/java/com/example/orderservice/web/controllers/OrderController.java +++ b/order-service/src/main/java/com/example/orderservice/web/controllers/OrderController.java @@ -8,6 +8,7 @@ Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli. import com.example.common.dtos.OrderDto; import com.example.orderservice.model.request.OrderRequest; +import com.example.orderservice.model.response.OrderResponse; import com.example.orderservice.model.response.PagedResult; import com.example.orderservice.services.OrderGeneratorService; import com.example.orderservice.services.OrderKafkaStreamService; @@ -18,6 +19,7 @@ Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli. import java.net.URI; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; @@ -131,4 +133,10 @@ public List all( int pageSize) { return orderKafkaStreamService.getAllOrders(pageNo, pageSize); } + + @GetMapping("/customer/{id}") + public ResponseEntity> ordersByCustomerId( + @PathVariable Long id, Pageable pageable) { + return ResponseEntity.ok(orderService.getOrdersByCustomerId(id, pageable)); + } } diff --git a/order-service/src/main/resources/application-h2.yml b/order-service/src/main/resources/application-h2.yml new file mode 100644 index 00000000..d41d5b4c --- /dev/null +++ b/order-service/src/main/resources/application-h2.yml @@ -0,0 +1,17 @@ +spring: + cloud: + config: + enabled: false + discovery: + enabled: false + datasource: + url: jdbc:h2:file:/data/demo + username: sa + password: password + driverClassName: org.h2.Driver + jpa: + spring.jpa.database-platform: org.hibernate.dialect.H2Dialect + config: + import: optional:configserver:${CONFIG_SERVER:http://localhost:8888}/ +application: + catalog-service-url: http://localhost:18080/catalog-service diff --git a/order-service/src/main/resources/application-local.properties b/order-service/src/main/resources/application-local.properties index 37938618..7e35d74b 100644 --- a/order-service/src/main/resources/application-local.properties +++ b/order-service/src/main/resources/application-local.properties @@ -3,6 +3,27 @@ spring.datasource.url=jdbc:postgresql://localhost:5432/appdb spring.datasource.username=appuser spring.datasource.password=secret +################ Database ##################### +spring.jpa.show-sql=true +spring.jpa.open-in-view=false +spring.datasource.hikari.auto-commit=false +spring.jpa.hibernate.ddl-auto=none +#spring.jpa.properties.hibernate.format_sql=true +spring.jpa.properties.hibernate.jdbc.time_zone=UTC +spring.jpa.properties.hibernate.generate_statistics=false +spring.jpa.properties.hibernate.jdbc.batch_size=25 +spring.jpa.properties.hibernate.order_inserts=true +spring.jpa.properties.hibernate.order_updates=true +spring.jpa.properties.hibernate.query.fail_on_pagination_over_collection_fetch=true +spring.jpa.properties.hibernate.query.in_clause_parameter_padding=true +spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true +spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true + +spring.mvc.problemdetails.enabled=true + +spring.cloud.config.enabled=false +spring.cloud.discovery.enabled=false + application.catalog-service-url=http://localhost:18080/catalog-service spring.config.import=optional:configserver:${CONFIG_SERVER:http://localhost:8888}/ diff --git a/order-service/src/main/resources/application.yml b/order-service/src/main/resources/application.yml index 60725110..eb8c0c74 100644 --- a/order-service/src/main/resources/application.yml +++ b/order-service/src/main/resources/application.yml @@ -3,7 +3,6 @@ server: servlet: contextPath: /${spring.application.name} forward-headers-strategy: framework - spring: application: name: order-service diff --git a/order-service/src/test/java/com/example/orderservice/repositories/OrderRepositoryTest.java b/order-service/src/test/java/com/example/orderservice/repositories/OrderRepositoryTest.java index 337c077c..3aa42708 100644 --- a/order-service/src/test/java/com/example/orderservice/repositories/OrderRepositoryTest.java +++ b/order-service/src/test/java/com/example/orderservice/repositories/OrderRepositoryTest.java @@ -14,8 +14,7 @@ Licensed under MIT License Copyright (c) 2023 Raja Kolli. import com.example.orderservice.util.TestData; import java.util.ArrayList; import java.util.List; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; @@ -35,8 +34,8 @@ class OrderRepositoryTest { @Autowired private OrderRepository orderRepository; @Autowired private OrderItemRepository orderItemRepository; - @AfterEach - void tearDown() { + @BeforeEach + void setUp() { this.orderItemRepository.deleteAllInBatch(); this.orderRepository.deleteAllInBatch(); } diff --git a/order-service/src/test/java/com/example/orderservice/util/TestData.java b/order-service/src/test/java/com/example/orderservice/util/TestData.java index fc132441..d55bf593 100644 --- a/order-service/src/test/java/com/example/orderservice/util/TestData.java +++ b/order-service/src/test/java/com/example/orderservice/util/TestData.java @@ -22,7 +22,7 @@ public static Order getOrder() { OrderItem orderItem = new OrderItem(); orderItem.setProductCode("Product1"); orderItem.setQuantity(10); - orderItem.setProductPrice(BigDecimal.TEN); + orderItem.setProductPrice(new BigDecimal("10.1")); OrderItem orderItem1 = new OrderItem(); orderItem1.setProductCode("Product2"); orderItem1.setQuantity(100); diff --git a/order-service/src/test/java/com/example/orderservice/web/controllers/OrderControllerIT.java b/order-service/src/test/java/com/example/orderservice/web/controllers/OrderControllerIT.java index 579618d1..4f09cf6a 100644 --- a/order-service/src/test/java/com/example/orderservice/web/controllers/OrderControllerIT.java +++ b/order-service/src/test/java/com/example/orderservice/web/controllers/OrderControllerIT.java @@ -30,6 +30,8 @@ Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli. import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.http.MediaType; class OrderControllerIT extends AbstractIntegrationTest { @@ -37,6 +39,7 @@ class OrderControllerIT extends AbstractIntegrationTest { @Autowired private OrderRepository orderRepository; private List orderList = null; + private OrderItem orderItem; @BeforeEach void setUp() { @@ -178,7 +181,7 @@ void shouldUpdateOrder() throws Exception { .andExpect(jsonPath("$.status", is("NEW"))) .andExpect(jsonPath("$.items.size()", is(2))) .andExpect(jsonPath("$.items[0].quantity", is(110))) - .andExpect(jsonPath("$.items[0].price", is(1100))) + .andExpect(jsonPath("$.items[0].price", is(1111.0))) .andExpect(jsonPath("$.items[1].quantity", is(100))) .andExpect(jsonPath("$.items[1].price", is(100))); } @@ -191,4 +194,30 @@ void shouldDeleteOrder() throws Exception { .perform(delete("/api/orders/{id}", order.getId())) .andExpect(status().isAccepted()); } + + @Test + void shouldFindOrdersByCustomersId() throws Exception { + OrderItem orderItem = orderList.get(0).getItems().get(0); + + Pageable pageable = PageRequest.of(0, 1); + mockMvc.perform( + get("/api/orders/customer/{id}", orderList.get(0).getCustomerId()) + .queryParam("page", "0") + .queryParam("size", "1")) + .andExpect(status().isOk()) + .andExpect( + jsonPath( + "$.data[0].customerId", + is(orderList.get(0).getCustomerId()), + Long.class)) + .andExpect(jsonPath("$.totalElements", is(orderList.size()))) + .andExpect( + jsonPath( + "$.data[0].items[0].price", + is( + orderItem + .getProductPrice() + .multiply(new BigDecimal(orderItem.getQuantity()))), + BigDecimal.class)); + } }