Module 5 · Architecture and maintainable code

Lesson 24 — SOLID and dependency injection

Layer separation and dependencies pointing in the right direction.

Published
In this lesson
  1. Exercise 1 — The fat view, operated on
  2. Exercise 2 — Injected clock
  3. Exercise 3 — L of Liskov with gateways
  4. Exercise 4 — O with a method registry
  5. Exercise 5 — I in data access
  6. Submit

Refactors on your TicketFlow. Each one backed by tests (28 formalizes the method). Don't look at solutions.md before submitting.

Exercise 1 — The fat view, operated on

  1. Take your reservation view (or the longest one you have) and list every line with its layer: HTTP (parse/status), domain (rules/transactions), persistence (raw ORM).
  2. Extract services/reservations.py::reservar() with the signature reservar(user, event_uuid, seat_refs, *, clock, gateway) -> Reservation. The view becomes: serializer + call + response. Measure: how many lines does the view have now?
  3. Verify the cotton test: call it from manage.py shell without a request. Did it work?

Exercise 2 — Injected clock

  1. Create the Clock Protocol + SystemClock + FakeClock(fixed=...) and use it in reservar() (the production default untouched).
  2. Write the test "a reservation with FakeClock at 23:59 expires at 00:09" without sleeps or freezegun: only the fake.
  3. Bonus: the expiration job (when 29 arrives) will consume the same Clock — note how it helps.

Exercise 3 — L of Liskov with gateways

  1. Define the Protocol PaymentGateway.charge(amount, intent) -> ChargeResult. Implement FakeGateway (approves if the amount < 1000) and SimulatedGateway (for production-staging).
  2. Break it on purpose: an implementation that returns None on error instead of raising. Write the test that catches it (which assertion failed?).
  3. Document the exact contract: what it returns, what it raises, what idempotency guarantee it gives (14). Is your docstring enough or is strict typing needed?

Exercise 4 — O with a method registry

  1. Implement PAYMENT_METHODS: dict[str, type[PaymentMethod]] with the @register("gift_card") registry. Add "cash_on_delivery" (simulated) WITHOUT touching the payments service: only a new module + registration.
  2. What happens in your old code if tomorrow they add "crypto"? How many lines change? (expected answer: 1 new file).

Exercise 5 — I in data access

  1. Split AvailabilityReader (what the public endpoint uses) from ReservationWriter (what the checkout uses). Which methods were superfluous in the single interface you imagined first?
  2. Your utils.py (if you have one): reorganize it by domain (utils/time.py, utils/money.py...) or confess you don't have it yet and write how you would avoid it.

Submit

Paste the view's before/after, the FakeClock tests and the method registry. Next: Lesson 25 — Service patterns.