diff --git a/target-service/files/welcome.txt b/target-service/files/welcome.txt new file mode 100644 index 0000000..13d6eec --- /dev/null +++ b/target-service/files/welcome.txt @@ -0,0 +1 @@ +hello from storage \ No newline at end of file diff --git a/target-service/src/main/java/com/example/targetservice/repository/InventoryRepository.java b/target-service/src/main/java/com/example/targetservice/repository/InventoryRepository.java index adc83df..7c00b00 100644 --- a/target-service/src/main/java/com/example/targetservice/repository/InventoryRepository.java +++ b/target-service/src/main/java/com/example/targetservice/repository/InventoryRepository.java @@ -32,4 +32,26 @@ public InventoryItem save(InventoryItem item) { public int stock(String sku) { return findBySku(sku).stock(); } + + /** + * Atomically deducts stock for a given SKU. + * Uses ConcurrentHashMap.compute to ensure read-check-write atomicity. + * @throws IllegalArgumentException if SKU not found or insufficient stock + */ + public InventoryItem deductStock(String sku, int quantity) { + if (quantity <= 0) { + throw new IllegalArgumentException("quantity must be positive, but got: " + quantity); + } + return store.compute(sku, (key, item) -> { + if (item == null) { + throw new IllegalArgumentException("SKU not found: " + sku); + } + if (item.stock() < quantity) { + throw new IllegalArgumentException( + "insufficient stock for " + sku + ": have " + item.stock() + ", need " + quantity); + } + return new InventoryItem(item.sku(), item.name(), + item.stock() - quantity, item.reserved() + quantity); + }); + } } diff --git a/target-service/src/main/java/com/example/targetservice/service/InventoryService.java b/target-service/src/main/java/com/example/targetservice/service/InventoryService.java index d20a46c..bbe6caf 100644 --- a/target-service/src/main/java/com/example/targetservice/service/InventoryService.java +++ b/target-service/src/main/java/com/example/targetservice/service/InventoryService.java @@ -14,21 +14,10 @@ public InventoryService(InventoryRepository repository) { } /** - * Deducts stock for a given SKU. - * BUG: No synchronization — concurrent calls can over-sell inventory. + * Deducts stock for a given SKU using atomic operation. */ public InventoryItem deduct(String sku, int quantity) { - if (quantity <= 0) { - throw new IllegalArgumentException("quantity must be positive, but got: " + quantity); - } - InventoryItem item = repository.findBySku(sku); - if (item.stock() < quantity) { - throw new IllegalArgumentException( - "insufficient stock for " + sku + ": have " + item.stock() + ", need " + quantity); - } - InventoryItem updated = new InventoryItem(item.sku(), item.name(), - item.stock() - quantity, item.reserved() + quantity); - return repository.save(updated); + return repository.deductStock(sku, quantity); } public InventoryItem getStock(String sku) {