Problem statement
Design the object model and core APIs for the software that drives an automatic beverage machine. The machine has several dispensing outlets and a shared ingredient store; the object model owns the recipes, the inventory, and the logic that turns a drink request into a served cup or a clear refusal. Whatever sits on top (a keypad, a touch panel, a test harness) merely selects a beverage and reads the result back.
Operating context. One physical machine with a fixed number of outlets (say 2–4) that can each be dispensing a different drink at the same moment. Every beverage is defined by a recipe: a set of ingredient-to-quantity requirements (water, milk, coffee syrup, tea leaves, sugar, and so on) drawn from one shared, refillable inventory. A request names a beverage and an outlet; the machine may serve it only if every required ingredient is currently in stock in sufficient quantity, and serving atomically deducts those amounts. When an ingredient runs low or dries up, requests that need it are refused with the specific missing items named, while drinks that do not need it keep flowing. An operator can top up any ingredient at any time. There is no network and no persistence — a single in-process engine driven by concurrent callers on multiple threads.
Out of scope. The physical hardware and actuator/valve control, the keypad or touchscreen UI, cash or cashless payment, remote telemetry or cloud reporting, and any persistence or save/restore across a power cycle. Design only the in-process engine and its object model, not the electronics or transport around it.
What to produce. The class hierarchy (entities such as Machine, Outlet, Beverage/Recipe, Ingredient, Inventory, and any value objects like a quantity or an ingredient key), the public API each class exposes, and the state transitions for an outlet (idle → dispensing → idle) and for a request (accepted → served, or rejected). Be explicit about: how a recipe checks and consumes ingredients atomically, how two outlets dispensing at once never over-draw the shared inventory, how a low or empty ingredient is detected and reported with the exact missing items, how refills interleave safely with dispensing, and how a brand-new beverage is added without editing existing beverages or the dispensing core.
Functional requirements
- Serve a requested beverage on a named outlet: check every ingredient in its recipe, and if all are sufficient, deduct them atomically and dispense.
- Reject a request when any required ingredient is short, naming the specific missing ingredient(s) rather than a generic failure.
- Let an operator refill (top up) the quantity of any ingredient in the shared inventory at any time.
- Expose the current inventory levels and the set of beverages currently servable given present stock.
- Register a new beverage from its recipe at runtime without changing existing beverage definitions or the dispensing logic.
- Report low-ingredient status (at or below a configurable threshold) so it can be surfaced or used to refuse requests early.
Non-functional requirements
- The check-and-deduct of a recipe's ingredients is atomic: an accepted serve consumes all-or-nothing, never a partial deduction that leaves inventory inconsistent.
- Concurrent serves across outlets are thread-safe: two outlets drawing the same ingredient at once must never over-draw below zero.
- Adding a new beverage is a localised change (new Recipe/registration only) — no edits to existing beverages, the inventory, or the serve path.
- Ingredient lookup and a per-drink stock check run in O(k) for a recipe of k ingredients, independent of the total ingredient catalogue size.
- The engine is unit-testable with no hardware, no wall clock, and no I/O — requests in, results and inventory state out.
- A refill interleaving with in-flight serves never corrupts a quantity (no lost updates) and never blocks unrelated outlets indefinitely.
Topics
- System Design LLD
- Oop Solid
- Concurrency Locks
- Patterns Factory
- Extensibility