PythiloLesson 7 of 15 · progress kept in this browser

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.

Next: Unit of work