A service layer
A use case such as placing an order touches several things: it loads objects from a repository, asks the domain to change them, saves the result and reports back. A service function holds that orchestration, so web handlers, command-line tools and tests all call the same code.
from dataclasses import dataclass
@dataclass
class Product:
sku: str
stock: int
def restock(self, qty):
if qty <= 0:
raise ValueError(qty)
self.stock += qty
def restock(repo, sku, qty):
product = repo[sku]
product.restock(qty)
repo[sku] = product
return product.stock
repo = {"MUG": Product("MUG", 1)}
print(restock(repo, "MUG", 4))The service takes its repository as an argument and returns plain values. It does not parse requests, print or open connections; those belong to its callers. The rules live on the domain objects, so the service reads like a short list of steps.
Test a service with a fake repository built in the test. If the service works with that, the seam is real.
Your task
Write Product.reserve(qty) raising OutOfStock when qty is more than the stock. Write place_order(repo, sku, qty) that raises ValueError for a quantity below 1, UnknownProduct when repo.get(sku) is None, and otherwise reserves, saves with repo.add(product) and returns the remaining stock.
The explanation opens here once every check passes.