Domain events and handlers
When stock runs out, someone should be told. Sending an email from inside Product.reserve would drag email into the domain. Instead the domain records what happened, as an event, and code outside decides what to do about it.
from dataclasses import dataclass
@dataclass(frozen=True)
class OutOfStock:
sku: str
def email_buyer(event):
print("email buyer:", event.sku)
def hide_listing(event):
print("hide listing:", event.sku)
HANDLERS = {OutOfStock: [email_buyer,
hide_listing]}
def publish(event):
for handle in HANDLERS[type(event)]:
handle(event)
publish(OutOfStock("MUG"))An event is a small immutable record of a fact. An entity appends events to a list as it changes. When the use case has finished, the service layer hands them to a message bus, which calls every handler subscribed to that type of event.
A new reaction, such as reordering stock, is a new handler. The entity does not change, and it never imports the code that reacts.
Your task
Write Product.reserve(qty) that raises ValueError when there is too little stock and appends OutOfStock(sku) to self.events when the stock reaches 0. Write MessageBus with subscribe(event_type, handler) and publish(events), calling handlers in subscription order and ignoring events nobody subscribed to, and reserve_and_publish(product, qty, bus) that reserves, then publishes and clears the product's events.
The explanation opens here once every check passes.