Skip to content

ADR-0003. adjustStock liền mạch với điều kiện chặn forceNonNegative

TrườngGiá trị
Trạng tháiAccepted
Ngày2026-02-10
Người quyết địnhPhat Nguyen
Thay thế-

Bối cảnh

  • Sự kiện thanh toán Sale thành công có thể gửi xuống inventory theo cơ chế at-least-once.
  • Hai client (ví dụ POS + admin) có thể đồng thời cố trừ tồn kho cho cùng một item.
  • Cách làm ngây thơ SELECT current; UPDATE current - delta; là một race condition kinh điển khi có truy cập đồng thời.
  • Một số item phải chặn bán âm hoàn toàn (nguyên liệu thô với allowOversell=false), số khác phải cho phép (sản phẩm cho đặt trước).

Quyết định

InventoryStockRepository.adjustStock({ stockId, adjustOnHand, adjustAvailable, adjustReserved, forceNonNegative }) thực hiện đọc và ghi trong một câu lệnh UPDATE SQL duy nhất với điều kiện chặn đặt ngay trong câu lệnh:

sql
UPDATE "InventoryStock"
SET quantity_on_hand = quantity_on_hand + $adjust_on_hand,
    quantity_available = quantity_available + $adjust_available,
    quantity_reserved = quantity_reserved + $adjust_reserved
WHERE id = $stock_id
  AND ($forceNonNegative = false
       OR (quantity_on_hand + $adjust_on_hand >= 0
           AND quantity_available + $adjust_available >= 0
           AND quantity_reserved + $adjust_reserved >= 0))
RETURNING quantity_on_hand, quantity_available, quantity_reserved;

Khi forceNonNegative=true và bất kỳ số lượng nào sau khi điều chỉnh sẽ âm, câu UPDATE không khớp dòng nào; repository trả về null. Bên gọi ghi tracking note OVERSELL_BLOCKED và bỏ qua việc trừ kho một cách lặng lẽ.

Hệ quả

Ưu điểmNhược điểm
An toàn trước mọi mức truy cập đồng thờiBên gọi phải kiểm tra giá trị trả về null và xử lý
Một lần đi-về duy nhất tới DBKhông có lỗi chi tiết: chỉ "guard failed"
Hoạt động dưới serializable, read-committed, v.v.SQL khó đọc hơn so với cấp ORM
Tương thích với PostgreSQL + DrizzleTính khả chuyển giữa các loại CSDL có chỗ tế nhị

Phương án thay thế đã cân nhắc

Phương ánƯu điểmNhược điểmLý do từ chối
Khóa lạc quan (cột version + thử lại)Khả chuyển giữa các DBBão thử lại khi tranh chấp caoHiệu năng tệ với SKU nóng
Khóa bi quan SELECT FOR UPDATE rồi UPDATEPattern quen thuộcGấp đôi lần đi-về; giữ khóa lâu hơnChậm hơn; cùng độ đúng
Mutex ở cấp ứng dụng (khóa Redis)Đa CSDLRủi ro khóa cũ kẹt lại, có điểm hỏng đơn lẻMong manh hơn primitive của DB

Tham chiếu

  • core/src/repositories/inventory/inventory-stock.repository.ts:36-76 (adjustStock)
  • inventory/src/common/constants.ts:8 (InventoryTrackingNotes.OVERSELL_BLOCKED)
  • inventory/src/services/inventory-worker.service.ts (pattern bên gọi)

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.