Inventory Management
Design the stockroom behind an online store: items sit in warehouses, quantities go up on
delivery and down on sale, and someone needs to be told when a shelf is running low. The
trap in this prompt is treating "reorder" as a side effect buried in sell() - it's a
policy that changes per item, and it deserves its own seam.
Requirements
Functional
- A
WarehouseholdsStockLevels: one entry perItem, tracking on-hand quantity. - Selling an item decrements its stock level; receiving a delivery increments it.
- A sale that would take stock below zero is rejected, not clamped to zero.
- Each item defines its own reorder threshold; when a stock level crosses at or below it, the warehouse should flag that item for restocking.
Non-functional
- The reorder rule (fixed threshold today, demand-based forecasting tomorrow) must be
swappable per item without changing
WarehouseorStockLevel. - Checking "does anything need restocking right now" must not re-scan every item in the warehouse on every sale; it should be answered from a small, kept-current set.
Design
Each Item carries a ReorderStrategy instead of a plain numeric threshold field -
exactly the same move Parking Lot makes for
FeeStrategy: the one part of the domain that legitimately varies (today it's a fixed
number, tomorrow it might read a demand forecast) gets isolated behind one method,
shouldReorder(currentQuantity), so Warehouse never branches on which kind of item it's
holding.
- 1The order system never touches a StockLevel directly - every mutation is routed through the warehouse.
- 2The warehouse finds the right stock level and asks it to change quantity by a signed amount.
- 3A negative result throws here, before anything else happens - no partial update to clean up.
- 4After a successful adjustment, the stock level asks the item’s own strategy whether this crossed the line.
- 5If the strategy says yes, the warehouse updates its restock set right here - no separate scan ever needed.
StockLevel is the only place quantity is mutated; Warehouse maintains a
needsRestock set that gets updated at that single mutation point, so it never has to
recompute the whole warehouse's status from scratch.
Class diagram
Code
Design decisions
- Reorder logic is a
ReorderStrategyon the item, not a threshold field read byWarehouse. A number would handle "restock at 10 units," but demand-based forecasting needs history and a formula, not a comparison - giving every item a strategy object meansFixedThresholdReorderand a futureDemandForecastReorderimplement the same one-method interface, andWarehousecalls it identically either way. StockLevel.adjust()is the single mutation point for quantity, and it's also where the restock set gets updated. Every increment or decrement - sale, delivery, return - funnels through one method, so "did this stock level just cross into reorder territory" is checked exactly once per change instead of being re-derived by every caller.- A sale that would go negative throws, it doesn't clamp to zero. Clamping would silently lie about how many units actually left the warehouse (useful for accounting and for whoever is reconciling the delivery that's now short); making it a rejected operation forces the caller to handle "we don't have enough" explicitly rather than discovering it in a report later.
- What's missing for a real system: concurrent sales against the same
StockLevelneed the decrement-and-check to be atomic (a lock per stock level, or a compare-and-swap on quantity) to avoid overselling under load, and multi-warehouse fulfillment (pick the nearest warehouse with stock) needs a routing layer this design doesn't include - both skipped to keep the reorder-strategy seam the whole focus.