An Inventory Management System (IMS) is a software solution that helps businesses efficiently track, organize, and control their inventory across the supply chain, from procurement to storage to sales and fulfillment.
Track and manage your stock
6
$6666.72
2
0
| Product | SKU | Category | Price | Stock | Actions |
|---|---|---|---|---|---|
Wireless Mouse | ELEC-001 | Electronics | $29.99 | 45 | |
USB-C Cable | ELEC-002 | Electronics | $12.99 | 8 | |
Cotton T-Shirt | CLTH-001 | Clothing | $24.99 | 120 | |
Organic Coffee | FOOD-001 | Food | $18.99 | 5 | |
LED Desk Lamp | HOME-001 | Home | $45.99 | 32 | |
Yoga Mat | SPRT-001 | Sports | $35.99 | 18 |
A well-designed IMS allows companies to:
In this chapter, we will explore the low-level design of an inventory management system in detail.
Lets start by clarifying the requirements:
We are tasked with designing a system to manage inventory across multiple warehouses. The system should track products, stock levels, and handle orders.
Let's begin by clearly defining the system's capabilities.
Candidate: Should the system support multiple types of inventory operations such as adding, removing, and updating stock?
Interviewer: Yes, it should support all standard inventory operations like adding new items, updating existing quantities, and removing stock.
Candidate: Do we need to track inventory at a single location or across multiple warehouses?
Interviewer: Let’s support multiple warehouses. Each warehouse should maintain its own stock levels independently.
Candidate: Should the system track individual items (like using serial numbers), or manage inventory at the product level (by quantity)?
Interviewer: To keep it simple, assume quantity-based tracking per product per warehouse. No need for serial-number level tracking for now.
Candidate: Should the system support setting threshold alerts for low stock levels?
Interviewer: Yes, users should be able to set minimum quantity thresholds, and the system should alert when stock falls below that level.
Candidate: Do we need to support transaction history for auditing purposes?
Interviewer: Yes, every inventory operation should be recorded with a timestamp and operation type for audit and reporting.
Candidate: Should the system support bulk operations, such as importing inventory in batches?
Interviewer: That’s a nice-to-have. For this version, assume individual item operations are sufficient.
Candidate: Do we need to handle concurrency in case multiple updates happen on the same product simultaneously?
Interviewer: Yes, inventory updates must be thread-safe to prevent race conditions or inconsistent stock counts.
With these clarifications, we can now summarize the key system requirements.
With the requirements clarified, the next step is to identify the core entities and responsibilities in the system.
Core entities are the fundamental building blocks of our system. We identify them by analyzing the functional requirements and extracting the key nouns and responsibilities that naturally map to object-oriented abstractions such as classes, enums, or interfaces.
Let’s walk through the functional requirements and extract the relevant entities:
This suggests the need for an InventoryService or InventoryManager component that provides APIs to perform operations on stock. Each operation should be tied to a specific product and warehouse.
This indicates the need for a Warehouse entity that holds an independent stock record, and a Product entity that represents the items being tracked. A mapping of product-to-quantity should be maintained within each warehouse.
This validates the need for a well-defined Product entity to encapsulate product-specific information.
The Warehouse entity will be responsible for managing a collection of product stock entries and exposing methods to query and update them.
This introduces a InventoryLog entity that records each inventory operation (e.g., ADD, REMOVE, UPDATE), including metadata such as timestamp, quantity, product ID, warehouse, and operation type.
This suggests a StockThreshold entity or a threshold field associated with each product-warehouse pair. The InventoryManager will periodically or reactively check stock levels against the threshold and trigger an alert when necessary.
While not a separate entity, this requirement influences the design of InventoryManager, which must ensure thread-safe updates, possibly through synchronization or locking mechanisms.
Product: Represents an item being tracked. Contains fields such as product ID, name, and description.Warehouse: Represents a physical or virtual storage location. Maintains stock levels for multiple products and supports operations like add, remove, and update.InventoryManager: Core service that coordinates all inventory operations across products and warehouses. Enforces thread safety and handles threshold checks.StockRecord: Represents the current quantity of a product in a warehouse, possibly with an associated minimum threshold for alerts.StockTransaction / InventoryLog: Represents an audit record for any inventory operation. Contains timestamp, product, warehouse, quantity, and operation type.OperationType (Enum): Enum representing types of inventory operations — ADD, REMOVE, UPDATE.StockThreshold (optional abstraction): Represents the configured minimum quantity for a specific product in a warehouse. Triggers alerts when stock falls below this threshold.These core entities form the foundational abstractions of an inventory management system. They will guide the class structure, data flow, and system responsibilities, ensuring the design is robust, extensible, and easy to maintain.
This section details the classes that form the core of the inventory management system, their relationships, and the key design patterns employed to ensure a robust, scalable, and maintainable architecture.
The system is designed around a set of well-defined classes, each with a single responsibility. They can be categorized as Enums, Data Classes, and Core Classes.
TransactionTypeA simple enumeration to categorize an inventory transaction.
This improves type safety and code readability compared to using raw strings or integers.
ADD: Represents an increase in stock.REMOVE: Represents a decrease in stock.INITIAL_STOCK: Represents the first stock entry when a product is introduced.ProductAn immutable class representing a stockable item.
It contains essential details like productId, name, and description. Its immutability is enforced by a private constructor and the absence of setters, making it inherently thread-safe. It is constructed using the Builder pattern.
TransactionAn immutable record representing a single, auditable event in the inventory log.
It captures the "what, when, where, and how much" of a stock change, including a unique transactionId, timestamp, productId, warehouseId, quantityChange, and TransactionType.
InventoryManagerThe central Singleton and Facade for the system.
It provides a simplified, high-level API for all client interactions, such as adding stock or viewing inventory. It orchestrates all operations between warehouses, products, and the audit service.
WarehouseRepresents a physical location that stores inventory.
It manages a collection of StockItem objects, mapping each product to its specific stock details within that warehouse.
StockItemA crucial class that encapsulates the inventory details for a single product within a single warehouse.
It holds the quantity, tracks the low-stock threshold, and acts as the Subject in the Observer pattern, notifying observers of any stock updates.
AuditServiceA Singleton service responsible for maintaining a complete, thread-safe log of all Transactions. It provides a central point for recording and retrieving audit trails.
The relationships between classes define the system's structure and data flow.
InventoryManager ◆— Warehouse: The InventoryManager holds and manages the lifecycle of all Warehouse instances.Warehouse ◆— StockItem: A Warehouse is composed of multiple StockItems. A StockItem cannot exist without a parent Warehouse.StockItem ◆— Product: A StockItem represents the stock of one specific Product.StockItem — StockObserver: A StockItem (the subject) holds a list of StockObservers to notify upon state change. This is a one-to-many association.InventoryManager — AuditService: The manager uses the single instance of the AuditService to log transactions.LowStockAlertObserver --▷ StockObserver: The LowStockAlertObserver is a concrete implementation of the StockObserver interface.InventoryManager ---> Transaction: The manager creates Transaction objects and passes them to the AuditService.ProductFactory ---> Product.ProductBuilder: The factory depends on the builder to construct Product objects.Several design patterns are strategically used to achieve flexibility, safety, and separation of concerns.
Implemented with StockItem as the Subject and StockObserver as the Observer. When the stock level in a StockItem changes, it automatically notifies all its registered observers (LowStockAlertObserver), which then react to the change. This decouples the stock-taking logic from the alerting mechanism.
The Product class uses a static inner ProductBuilder to create its instances. This pattern is ideal for constructing complex, immutable objects. It improves readability by allowing a step-by-step construction process and ensures object validity before the final object is created.
The ProductFactory class serves as a simple factory. It encapsulates the instantiation logic of Product objects, hiding the complexity of the ProductBuilder from the client and providing a straightforward creation method.
Used for InventoryManager and AuditService. This ensures that there is only one instance of these central components throughout the application, providing a global point of access and control over the system's state and audit log.
The InventoryManager acts as a facade. It provides a simple, unified interface (addStock, removeStock) that hides the complex interactions between Warehouse, StockItem, AuditService, and StockObservers. Clients interact with this simple API without needing to know the internal details.
TransactionType EnumDefines the type of inventory transaction
1class TransactionType(Enum):
2 ADD = "ADD"
3 REMOVE = "REMOVE"
4 INITIAL_STOCK = "INITIAL_STOCK"ADD: Increase in stockREMOVE: Decrease in stockINITIAL_STOCK: Setup stock added when a product is first introduced to a warehouseProduct and BuilderRepresents an item that can be stocked in warehouses. Built using the Builder pattern to enforce validation and flexibility during creation.
1class Product:
2 def __init__(self, builder: 'ProductBuilder'):
3 self._product_id = builder._product_id
4 self._name = builder._name
5 self._description = builder._description
6
7 def get_product_id(self) -> str:
8 return self._product_id
9
10 def get_name(self) -> str:
11 return self._name
12
13 def get_description(self) -> str:
14 return self._description
15
16 def __str__(self) -> str:
17 return f"Product{{id='{self._product_id}', name='{self._name}'}}"
18
19 class ProductBuilder:
20 def __init__(self, product_id: str):
21 self._product_id = product_id
22 self._name: Optional[str] = None
23 self._description: Optional[str] = None
24
25 def with_name(self, name: str) -> 'Product.ProductBuilder':
26 self._name = name
27 return self
28
29 def with_description(self, description: str) -> 'Product.ProductBuilder':
30 self._description = description
31 return self
32
33 def build(self) -> 'Product':
34 if self._name is None or self._name.strip() == "":
35 raise ValueError("Product name cannot be null or empty.")
36 return Product(self)ProductFactoryProvides a convenient static method to construct validated Product objects using the builder internally.
1class ProductFactory:
2 @staticmethod
3 def create_product(product_id: str, name: str, description: str) -> Product:
4 return Product.ProductBuilder(product_id) \
5 .with_name(name) \
6 .with_description(description) \
7 .build()StockItemEncapsulates product-level inventory for a specific warehouse. A StockItem is responsible for one product in one warehouse. This includes its current quantity and the low-stock threshold.
1class StockItem:
2 def __init__(self, product: Product, quantity: int, threshold: int, warehouse_id: int):
3 self._product = product
4 self._quantity = quantity
5 self._threshold = threshold
6 self._warehouse_id = warehouse_id
7 self._observers: List[StockObserver] = []
8 self._lock = threading.Lock()
9
10 def get_product(self) -> Product:
11 return self._product
12
13 def get_quantity(self) -> int:
14 return self._quantity
15
16 def get_threshold(self) -> int:
17 return self._threshold
18
19 def get_warehouse_id(self) -> int:
20 return self._warehouse_id
21
22 def add_observer(self, observer: StockObserver):
23 self._observers.append(observer)
24
25 def remove_observer(self, observer: StockObserver):
26 if observer in self._observers:
27 self._observers.remove(observer)
28
29 def update_stock(self, quantity_change: int) -> bool:
30 with self._lock:
31 if self._quantity + quantity_change < 0:
32 print(f"Cannot remove more stock than available. "
33 f"Available: {self._quantity}, Attempted to remove: {-quantity_change}")
34 return False
35
36 self._quantity += quantity_change
37 print(f"Stock updated for {self._product.get_name()} in Warehouse {self._warehouse_id}. "
38 f"New quantity: {self._quantity}")
39 self._notify_observers()
40 return True
41
42 def _notify_observers(self):
43 for observer in self._observers:
44 observer.on_stock_update(self)Implements the Observer pattern to alert when stock falls below threshold. The updateStock() method ensures thread-safe inventory changes and notifies all observers.
WarehouseA warehouse represents a physical location that holds stock.
1class Warehouse:
2 def __init__(self, warehouse_id: int, location: str):
3 self._warehouse_id = warehouse_id
4 self._location = location
5 self._stock_items: Dict[str, StockItem] = {}
6
7 def get_warehouse_id(self) -> int:
8 return self._warehouse_id
9
10 def get_location(self) -> str:
11 return self._location
12
13 def add_product_stock(self, stock_item: StockItem):
14 self._stock_items[stock_item.get_product().get_product_id()] = stock_item
15
16 def update_stock(self, product_id: str, quantity_change: int) -> bool:
17 stock_item = self._stock_items.get(product_id)
18 if stock_item is not None:
19 return stock_item.update_stock(quantity_change)
20 else:
21 print(f"Error: Product {product_id} not found in warehouse {self._warehouse_id}")
22 return False
23
24 def get_stock_level(self, product_id: str) -> int:
25 stock_item = self._stock_items.get(product_id)
26 return stock_item.get_quantity() if stock_item is not None else 0
27
28 def print_inventory(self):
29 print(f"--- Inventory for Warehouse {self._warehouse_id} ({self._location}) ---")
30 if not self._stock_items:
31 print("Warehouse is empty.")
32 return
33
34 for item in self._stock_items.values():
35 print(f"Product: {item.get_product().get_name()} ({item.get_product().get_product_id()}), "
36 f"Quantity: {item.get_quantity()}")
37 print("-------------------------------------------------")TransactionModels an audit log entry for stock change operations.
1class Transaction:
2 def __init__(self, product_id: str, warehouse_id: int, quantity_change: int, transaction_type: TransactionType):
3 self._transaction_id = str(uuid.uuid4())
4 self._timestamp = datetime.now()
5 self._product_id = product_id
6 self._warehouse_id = warehouse_id
7 self._quantity_change = quantity_change
8 self._type = transaction_type
9
10 def __str__(self) -> str:
11 return (f"Transaction [ID={self._transaction_id}, Time={self._timestamp}, "
12 f"Warehouse={self._warehouse_id}, Product={self._product_id}, "
13 f"Type={self._type.value}, QtyChange={self._quantity_change}]")AuditServiceTracks all inventory transactions in a thread-safe manner.
1class AuditService:
2 _instance = None
3 _lock = threading.Lock()
4
5 def __new__(cls):
6 if cls._instance is None:
7 with cls._lock:
8 if cls._instance is None:
9 cls._instance = super().__new__(cls)
10 cls._instance._initialized = False
11 return cls._instance
12
13 def __init__(self):
14 if not self._initialized:
15 self._transaction_log: List[Transaction] = []
16 self._initialized = True
17
18 @classmethod
19 def get_instance(cls):
20 return cls()
21
22 def log(self, transaction: Transaction):
23 self._transaction_log.append(transaction)
24
25 def print_audit_log(self):
26 print("\n--- Audit Log ---")
27 for transaction in self._transaction_log:
28 print(transaction)
29 print("-----------------")StockObserverDefines observers that react to stock changes.
1class StockObserver(ABC):
2 @abstractmethod
3 def on_stock_update(self, stock_item: 'StockItem'):
4 pass
5
6class LowStockAlertObserver(StockObserver):
7 def on_stock_update(self, stock_item: 'StockItem'):
8 if stock_item.get_quantity() < stock_item.get_threshold():
9 print(f"ALERT: Low stock for {stock_item.get_product().get_name()} in warehouse "
10 f"{stock_item.get_warehouse_id()}. Current quantity: {stock_item.get_quantity()}, "
11 f"Threshold: {stock_item.get_threshold()}")LowStockAlertObserver emits a warning when the available stock falls below a predefined threshold.
InventoryManager (Singleton)This class is the central controller for the entire system, providing a simplified API for clients.
1class InventoryManager:
2 _instance = None
3 _lock = threading.Lock()
4
5 def __new__(cls):
6 if cls._instance is None:
7 with cls._lock:
8 if cls._instance is None:
9 cls._instance = super().__new__(cls)
10 cls._instance._initialized = False
11 return cls._instance
12
13 def __init__(self):
14 if not self._initialized:
15 self._products: Dict[str, Product] = {}
16 self._warehouses: Dict[int, Warehouse] = {}
17 self._audit_service = AuditService.get_instance()
18 self._initialized = True
19
20 @classmethod
21 def get_instance(cls):
22 return cls()
23
24 def add_warehouse(self, warehouse_id: int, location: str) -> Warehouse:
25 warehouse = Warehouse(warehouse_id, location)
26 self._warehouses[warehouse_id] = warehouse
27 return warehouse
28
29 def add_product(self, product: Product):
30 self._products[product.get_product_id()] = product
31
32 def add_product_to_warehouse(self, product_id: str, warehouse_id: int, initial_quantity: int, threshold: int):
33 warehouse = self._warehouses.get(warehouse_id)
34 product = self._products.get(product_id)
35
36 if warehouse is None or product is None:
37 print("Warehouse or product not found")
38 return
39
40 stock_item = StockItem(product, initial_quantity, threshold, warehouse_id)
41 stock_item.add_observer(LowStockAlertObserver()) # Register the observer
42 warehouse.add_product_stock(stock_item)
43
44 # Log the initial stock
45 self._audit_service.log(Transaction(product.get_product_id(), warehouse_id,
46 initial_quantity, TransactionType.INITIAL_STOCK))
47
48 def _update_stock(self, warehouse_id: int, product_id: str, quantity_change: int):
49 warehouse = self._warehouses.get(warehouse_id)
50
51 if warehouse is None:
52 print(f"Error: Warehouse {warehouse_id} not found.")
53 return
54
55 success = warehouse.update_stock(product_id, quantity_change)
56
57 if success:
58 transaction_type = TransactionType.ADD if quantity_change >= 0 else TransactionType.REMOVE
59 self._audit_service.log(Transaction(product_id, warehouse_id, quantity_change, transaction_type))
60
61 def add_stock(self, warehouse_id: int, product_id: str, quantity: int):
62 self._update_stock(warehouse_id, product_id, quantity)
63
64 def remove_stock(self, warehouse_id: int, product_id: str, quantity: int):
65 self._update_stock(warehouse_id, product_id, -quantity)
66
67 def view_inventory(self, warehouse_id: int):
68 warehouse = self._warehouses.get(warehouse_id)
69 if warehouse is not None:
70 warehouse.print_inventory()
71 else:
72 print(f"Warehouse with ID {warehouse_id} not found.")
73
74 def view_audit_log(self):
75 self._audit_service.print_audit_log()InventoryManagementDemoThis driver class demonstrates the end-to-end functionality of the system from a client's perspective.
1class InventoryManagementDemo:
2 @staticmethod
3 def main():
4 # Get the singleton instance of the InventoryManager
5 inventory_manager = InventoryManager.get_instance()
6
7 # 1. Setup: Add warehouses and products
8 warehouse1 = inventory_manager.add_warehouse(1, "New York")
9 warehouse2 = inventory_manager.add_warehouse(2, "San Francisco")
10
11 laptop = ProductFactory.create_product("P001", "Dell XPS 15", "A high-performance laptop")
12 mouse = ProductFactory.create_product("P002", "Logitech MX Master 3", "An ergonomic wireless mouse")
13
14 inventory_manager.add_product(laptop)
15 inventory_manager.add_product(mouse)
16
17 # 2. Add initial stock to warehouses
18 print("--- Initializing Stock ---")
19 inventory_manager.add_product_to_warehouse(laptop.get_product_id(), warehouse1.get_warehouse_id(), 10, 5) # 10 laptops in NY, threshold 5
20 inventory_manager.add_product_to_warehouse(mouse.get_product_id(), warehouse1.get_warehouse_id(), 50, 20) # 50 mice in NY, threshold 20
21 inventory_manager.add_product_to_warehouse(laptop.get_product_id(), warehouse2.get_warehouse_id(), 8, 3) # 8 laptops in SF, threshold 3
22 print()
23
24 # 3. View initial inventory
25 inventory_manager.view_inventory(warehouse1.get_warehouse_id())
26 inventory_manager.view_inventory(warehouse2.get_warehouse_id())
27
28 # 4. Perform stock operations
29 print("\n--- Performing Stock Operations ---")
30 inventory_manager.add_stock(warehouse1.get_warehouse_id(), laptop.get_product_id(), 5) # Add 5 laptops to NY
31 inventory_manager.remove_stock(warehouse1.get_warehouse_id(), mouse.get_product_id(), 35) # Remove 35 mice from NY -> should trigger alert
32 inventory_manager.remove_stock(warehouse2.get_warehouse_id(), laptop.get_product_id(), 6) # Remove 6 laptops from SF -> should trigger alert
33
34 # 5. Demonstrate error case: removing too much stock
35 print("\n--- Demonstrating Insufficient Stock Error ---")
36 inventory_manager.remove_stock(warehouse2.get_warehouse_id(), laptop.get_product_id(), 100) # Fails, only 2 left
37 print()
38
39 # 6. View final inventory
40 print("\n--- Final Inventory Status ---")
41 inventory_manager.view_inventory(warehouse1.get_warehouse_id())
42 inventory_manager.view_inventory(warehouse2.get_warehouse_id())
43
44 # 7. View the full audit log
45 inventory_manager.view_audit_log()
46
47if __name__ == "__main__":
48 InventoryManagementDemo.main()Which entity is primarily responsible for maintaining stock levels for each product at a specific location in an Inventory Management System?
No comments yet. Be the first to comment!