Skip to content

Commit

Permalink
Merge pull request #22 from leung018/order-service-request-id
Browse files Browse the repository at this point in the history
Order-service-request-id
  • Loading branch information
leung018 authored Dec 11, 2024
2 parents ba68dda + c31a213 commit 0793333
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.leungcheng.spring_simple_backend.domain.order;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
Expand All @@ -11,11 +12,15 @@ public class Order {
private String buyerUserId;
private PurchaseItems purchaseItems;

@Column(unique = true)
private String requestId;

private Order() {}

Order(String buyerUserId, PurchaseItems purchaseItems) {
Order(String buyerUserId, PurchaseItems purchaseItems, String requestId) {
this.buyerUserId = buyerUserId;
this.purchaseItems = purchaseItems;
this.requestId = requestId;
}

public String getId() {
Expand All @@ -29,4 +34,8 @@ public String getBuyerUserId() {
public PurchaseItems getPurchaseItems() {
return purchaseItems;
}

public String getRequestId() {
return requestId;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.leungcheng.spring_simple_backend.domain.order;

import java.util.Optional;
import org.springframework.data.repository.CrudRepository;

public interface OrderRepository extends CrudRepository<Order, String> {}
public interface OrderRepository extends CrudRepository<Order, String> {
Optional<Order> findByRequestId(String requestId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ public CreateOrderException(String message) {

@Retryable(noRetryFor = CreateOrderException.class)
@Transactional(isolation = Isolation.SERIALIZABLE)
public Order createOrder(String buyerUserId, PurchaseItems purchaseItems) {
public Order createOrder(String buyerUserId, PurchaseItems purchaseItems, String requestId) {
Optional<Order> order = orderRepository.findByRequestId(requestId);
if (order.isPresent()) {
return order.get();
}

User buyer =
getUser(buyerUserId).orElseThrow(() -> new CreateOrderException("Buyer does not exist"));

Expand All @@ -39,7 +44,7 @@ public Order createOrder(String buyerUserId, PurchaseItems purchaseItems) {
}
saveNewBalance(buyer, buyer.getBalance().subtract(totalCost));

return addNewOrder(buyerUserId, purchaseItems);
return addNewOrder(buyerUserId, purchaseItems, requestId);
}

private Optional<User> getUser(String userId) {
Expand All @@ -51,8 +56,8 @@ private void saveNewBalance(User buyer, BigDecimal newBalance) {
userRepository.save(updatedBuyer);
}

private Order addNewOrder(String buyerUserId, PurchaseItems purchaseItems) {
Order order = new Order(buyerUserId, purchaseItems);
private Order addNewOrder(String buyerUserId, PurchaseItems purchaseItems, String requestId) {
Order order = new Order(buyerUserId, purchaseItems, requestId);
return orderRepository.save(order);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ void shouldRejectCreateOrderWithNonExistingBuyer() {

CreateOrderException exception =
assertThrows(
CreateOrderException.class,
() -> orderService.createOrder("non_existing_buyer_id", purchaseItems));
CreateOrderException.class, () -> createOrder("non_existing_buyer_id", purchaseItems));
assertEquals("Buyer does not exist", exception.getMessage());
}

Expand All @@ -70,9 +69,7 @@ void shouldRejectCreateOrderWithEmptyPurchaseItems() {
PurchaseItems purchaseItems = new PurchaseItems();

CreateOrderException exception =
assertThrows(
CreateOrderException.class,
() -> orderService.createOrder(buyer.getId(), purchaseItems));
assertThrows(CreateOrderException.class, () -> createOrder(buyer.getId(), purchaseItems));
assertEquals("Purchase items cannot be empty", exception.getMessage());
}

Expand All @@ -85,9 +82,7 @@ void shouldRejectCreateOrderWithNonExistingProduct() {
purchaseItems.setPurchaseItem("non_existing_product_id", 1);

CreateOrderException exception =
assertThrows(
CreateOrderException.class,
() -> orderService.createOrder(buyer.getId(), purchaseItems));
assertThrows(CreateOrderException.class, () -> createOrder(buyer.getId(), purchaseItems));
assertEquals("Product: non_existing_product_id does not exist", exception.getMessage());
}

Expand All @@ -103,9 +98,7 @@ void shouldRejectCreateOrderWithInsufficientBalance() {
purchaseItems.setPurchaseItem(product.getId(), 2);

CreateOrderException exception =
assertThrows(
CreateOrderException.class,
() -> orderService.createOrder(buyer.getId(), purchaseItems));
assertThrows(CreateOrderException.class, () -> createOrder(buyer.getId(), purchaseItems));
assertEquals("Insufficient balance", exception.getMessage());

// product quantity should not be reduced
Expand All @@ -124,9 +117,7 @@ void shouldRejectOrderWithInsufficientProductQuantity() {
purchaseItems.setPurchaseItem(product.getId(), 2);

CreateOrderException exception =
assertThrows(
CreateOrderException.class,
() -> orderService.createOrder(buyer.getId(), purchaseItems));
assertThrows(CreateOrderException.class, () -> createOrder(buyer.getId(), purchaseItems));
assertEquals("Insufficient stock for product: " + product.getId(), exception.getMessage());

// buyer balance should not be reduced
Expand All @@ -145,7 +136,7 @@ void shouldNotThrowExceptionIfStockAndBuyerBalanceIsJustEnough() {
PurchaseItems purchaseItems = new PurchaseItems();
purchaseItems.setPurchaseItem(product.getId(), 1);

orderService.createOrder(buyer.getId(), purchaseItems); // should not throw exception
createOrder(buyer.getId(), purchaseItems); // should not throw exception
}

@Test
Expand All @@ -162,7 +153,7 @@ void shouldReduceProductQuantityAndBuyerBalanceWhenOrderIsSuccessful() {
purchaseItems.setPurchaseItem(product1.getId(), 2);
purchaseItems.setPurchaseItem(product2.getId(), 3);

orderService.createOrder(buyer.getId(), purchaseItems);
createOrder(buyer.getId(), purchaseItems);

assertEquals(8, productRepository.findById(product1.getId()).orElseThrow().getQuantity());
assertEquals(7, productRepository.findById(product2.getId()).orElseThrow().getQuantity());
Expand Down Expand Up @@ -192,7 +183,7 @@ void shouldIncreaseSellersBalance() {
purchaseItems.setPurchaseItem(product1.getId(), 2);
purchaseItems.setPurchaseItem(product2.getId(), 3);

orderService.createOrder(buyer.getId(), purchaseItems);
createOrder(buyer.getId(), purchaseItems);

assertBigDecimalEquals(
new BigDecimal(15), userRepository.findById(seller1.getId()).orElseThrow().getBalance());
Expand All @@ -213,7 +204,7 @@ void shouldCreateOrder() {
purchaseItems.setPurchaseItem(product1.getId(), 2);
purchaseItems.setPurchaseItem(product2.getId(), 5);

Order order = orderService.createOrder(buyer.getId(), purchaseItems);
Order order = createOrder(buyer.getId(), purchaseItems);

assertEquals(buyer.getId(), order.getBuyerUserId());
assertEquals(
Expand All @@ -234,12 +225,32 @@ void shouldEachCreatedOrderHasDifferentId() {
PurchaseItems purchaseItems = new PurchaseItems();
purchaseItems.setPurchaseItem(product.getId(), 1);

Order order1 = orderService.createOrder(buyer.getId(), purchaseItems);
Order order2 = orderService.createOrder(buyer.getId(), purchaseItems);
Order order1 = createOrder(buyer.getId(), purchaseItems, "request-01");
Order order2 = createOrder(buyer.getId(), purchaseItems, "request-02");

assertNotEquals(order1.getId(), order2.getId());
}

@Test
void shouldOneOrderBeingCreatedOnly_IfCreateOrderWithSameRequestIdTwice() {
User buyer = randomUsernameUserBuilder().balance(new BigDecimal(10)).build();
userRepository.save(buyer);

Product product = productBuilder().quantity(5).price(new BigDecimal(1)).build();
productRepository.save(product);

PurchaseItems purchaseItems = new PurchaseItems();
purchaseItems.setPurchaseItem(product.getId(), 1);

Order order1 = createOrder(buyer.getId(), purchaseItems, "request_id");
Order order2 = createOrder(buyer.getId(), purchaseItems, "request_id");

assertOrderEquals(order2, order1);
assertEquals(4, productRepository.findById(product.getId()).orElseThrow().getQuantity());
assertBigDecimalEquals(
new BigDecimal(9), userRepository.findById(buyer.getId()).orElseThrow().getBalance());
}

@Test
void shouldAutoRetry_WhenOneThreadMayFailJustDueToRacing() {
User seller = randomUsernameUserBuilder().balance(new BigDecimal(999)).build();
Expand All @@ -254,8 +265,8 @@ void shouldAutoRetry_WhenOneThreadMayFailJustDueToRacing() {
purchaseItems.setPurchaseItem(product.getId(), 1);

// 2 threads try to buy the same product at the same time
Thread thread1 = new Thread(() -> orderService.createOrder(buyer.getId(), purchaseItems));
Thread thread2 = new Thread(() -> orderService.createOrder(buyer.getId(), purchaseItems));
Thread thread1 = new Thread(() -> createOrder(buyer.getId(), purchaseItems, "request-01"));
Thread thread2 = new Thread(() -> createOrder(buyer.getId(), purchaseItems, "request-02"));

thread1.start();
thread2.start();
Expand All @@ -270,11 +281,20 @@ void shouldAutoRetry_WhenOneThreadMayFailJustDueToRacing() {
assertEquals(0, productRepository.findById(product.getId()).orElseThrow().getQuantity());
}

private Order createOrder(String buyerUserId, PurchaseItems purchaseItems) {
return orderService.createOrder(buyerUserId, purchaseItems, "dummy_request_id");
}

private Order createOrder(String buyerUserId, PurchaseItems purchaseItems, String requestId) {
return orderService.createOrder(buyerUserId, purchaseItems, requestId);
}

private void assertOrderEquals(Order expected, Order actual) {
assertEquals(expected.getId(), actual.getId());
assertEquals(expected.getBuyerUserId(), actual.getBuyerUserId());
assertEquals(
expected.getPurchaseItems().getProductIdToQuantity(),
actual.getPurchaseItems().getProductIdToQuantity());
assertEquals(expected.getRequestId(), actual.getRequestId());
}
}

0 comments on commit 0793333

Please sign in to comment.