From 1ba560d5ff2d65e172e1961732a920afc08b1dbf Mon Sep 17 00:00:00 2001 From: Laurent Broudoux Date: Mon, 7 Aug 2023 17:09:16 +0200 Subject: [PATCH] Adding comments for Spring Boot + Testcontainers demo --- .../java/org/acme/order/api/OrderController.java | 7 +++++++ .../org/acme/order/client/PastryAPIClient.java | 5 +++++ .../org/acme/order/service/OrderService.java | 16 ++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/api/OrderController.java b/shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/api/OrderController.java index 0da1094..32f10b8 100644 --- a/shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/api/OrderController.java +++ b/shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/api/OrderController.java @@ -13,6 +13,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +/** + * OrderController is responsible for exposing the REST API for the Order Service. It should take + * care of serialization, business rules mapping to model types and Http status codes. + * @author laurent + */ @RestController @RequestMapping("/api/orders") public class OrderController { @@ -26,6 +31,7 @@ public ResponseEntity order(@RequestBody OrderInfo info) { try { createdOrder = service.placeOrder(info); } catch (UnavailablePastryException upe) { + // We have to return a 422 (unprocessable) with correct expected type. //return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY); return new ResponseEntity<>( new UnavailableProduct(upe.getProduct(), upe.getMessage()), @@ -33,6 +39,7 @@ public ResponseEntity order(@RequestBody OrderInfo info) { } catch (Exception e) { return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } + // We can return a 201 with created entity. return new ResponseEntity<>(createdOrder, HttpStatus.CREATED); } } diff --git a/shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/client/PastryAPIClient.java b/shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/client/PastryAPIClient.java index e906e56..0d40fab 100644 --- a/shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/client/PastryAPIClient.java +++ b/shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/client/PastryAPIClient.java @@ -10,6 +10,11 @@ import java.util.List; +/** + * PastryAPIClient is responsible for requesting the product/stock management system (aka the Pastry registry) + * using its REST API. It should take care of serializing entities and Http params as required by the 3rd party API. + * @author laurent + */ @Component public class PastryAPIClient { diff --git a/shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/service/OrderService.java b/shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/service/OrderService.java index 720609a..b522c7e 100644 --- a/shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/service/OrderService.java +++ b/shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/service/OrderService.java @@ -13,13 +13,27 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +/** + * OrderService is responsible for checking business rules/constraints on Orders. + * @author laurent + */ @Service public class OrderService { @Autowired PastryAPIClient pastryRepository; + /** + * This method will check that an Order can be actually placed and persisted. A full implementation + * will probably check stocks, customer loyalty, payment methods, shipping details, etc... For sake + * of simplicity, we'll just check that products (here pastries) are all available. + * @param info The order informations. + * @return + * @throws UnavailablePastryException + * @throws Exception + */ public Order placeOrder(OrderInfo info) throws UnavailablePastryException, Exception { + // For all products in order, check the avaibility calling the Pastry API. Map, String> availabilityFutures = new HashMap<>(); for (ProductQuantity productQuantity : info.productQuantities()) { availabilityFutures.put(checkPastryAvailability(productQuantity.productName()), productQuantity.productName()); @@ -29,6 +43,7 @@ public Order placeOrder(OrderInfo info) throws UnavailablePastryException, Excep CompletableFuture.allOf(availabilityFutures.keySet().toArray(new CompletableFuture[0])).join(); try { + // If one pastry is marked as unavailable, throw a business exception. for (CompletableFuture availabilityFuture : availabilityFutures.keySet()) { if (!availabilityFuture.get()) { String pastryName = availabilityFutures.get(availabilityFuture); @@ -39,6 +54,7 @@ public Order placeOrder(OrderInfo info) throws UnavailablePastryException, Excep throw new Exception("Unexpected exception: " + e.getMessage()); } + // Everything is available! Create (and probably persist ;-) a new order. Order result = new Order(); result.setCustomerId(info.customerId()); result.setProductQuantities(info.productQuantities());