Stack: Django/DRF · Project: TicketFlow Status: Published — opening the architecture module Prerequisite: Lesson 23 — Secrets and GDPR
Objectives
- Explain each SOLID principle with an example from YOUR project (not with tutorial Shape/Circle).
- Separate layers: view (HTTP) → service (domain) → models (persistence), and know what belongs in each.
- Apply dependency injection in Python without ceremony (duck typing + protocols).
1. SOLID on TicketFlow (each letter, an example of yours)
S — Single Responsibility: reservar() (10) reserves; it doesn't send emails or compute taxes. The view translates HTTP; the service executes domain; the model persists and maintains invariants. When a function needs "and also", that is the signal.
O — Open/Closed: adding a new payment method (lesson 08's gift card, gateway B) without touching existing code: a PaymentMethod with a common interface (charge(amount, intent)) and a registry of implementations, instead of an if method == "card" elif... that grows forever. Extending = adding a class; modifying = no.
L — Liskov: every implementation of PaymentGateway respects the contract: charge returns or raises, never silently returns None "sometimes". If a simulated gateway returns {"ok": True} and the real one {"status": "ok"}, the code using them breaks — substitutability is the exact contract, exceptions included.
I — Interface Segregation: the availability view doesn't need a "repository that also sends emails". Small interfaces per consumer: AvailabilityReader (reads), ReservationWriter (writes) — clients don't depend on methods they don't use.
D — Dependency Inversion: the reservation service doesn't import psycopg or Redis directly: it receives clock, payment_gateway, cache by parameter/constructor. The domain defines what it needs; the infrastructure fulfills it.
2. TicketFlow's layers
views/serializers → HTTP: parse, auth, status codes (13/26)
services/ → DOMAIN: rules, transactions (10), invariants
models/ → PERSISTENCE: tables, constraints, state machines (00b)Boundary rules: the view does NOT query the ORM in loops (that is domain logic); the service does NOT build a Response nor read request (that is HTTP); the model does NOT import Redis or call APIs (that is infrastructure). The cotton test: can you call reservar(user, seats) from a management command, a worker and a test without a Django request? If yes, the layers are right.
3. Injection in Python (without gory containers)
Python doesn't need Spring: dependencies enter by parameter with defaults, and duck typing + typing.Protocol document the contract:
class Clock(Protocol):
def now(self) -> datetime: ...
class SystemClock:
def now(self) -> datetime:
return timezone.now()
def reservar(user, seats, *, clock: Clock = SystemClock(), expires_in: timedelta = timedelta(minutes=10)):
expires_at = clock.now() + expires_in # the test injects FakeClock and controls timeLesson 10's test with a fixed expires_at stops depending on timezone.now(); the job's expiration test (29) advances the virtual clock. DI here is: constructor + production default + fake in tests. A container only when the graph truly grows (not this project).
4. Architecture debt signals (the smells you read in review)
- A 900-line
utils.py(the junk drawer): split by domain. - Importing
requests/redisinsideservices/reservations.py: a hard infrastructure dependency → inject it. if user.role == "STAFF"repeated in 12 places: the living rule in one place (permissions.py/services).- A 150-line view: mixing HTTP+domain: extract a service.
- Behavior-less (anemic) models with all logic in services: valid, but the schema invariants (00b) must stay in the model — the project's balance: state machines in the model, flows in services.
Self-assessment
- Your reservation view has 150 lines with ORM in loops: what do you extract and which layer does each piece go to?
- Which concrete problem does the L in
PaymentGatewaysolve, and how does its violation show up in tests? - Why is
Clockthe first dependency worth injecting in a reservation system? - When is an "anemic" model a problem signal and when is it a legitimate choice in Django?
- Which cotton test do you apply to know your layers are properly separated?
Continue with the exercises. The solutions only after trying it yourself.