๐ WAYPOINT LESSON
Ports, adapters, repositories
โญโญโญ advancedโณ 22 min read๐ Lesson 140 of 169
Own the interface in the core; translate at the edge.
Ports & adapters with real seams
A port is an interface owned by the application core. An adapter is a
piece of infrastructure that fulfills it. The core defines NotificationSender
and OrderRepository; the adapters are SendGridSender, SmtpSender,
PostgresOrderRepository, InMemoryOrderRepository (for tests).
from typing import Protocol
class OrderRepository(Protocol):
def get(self, order_id: str) -> "Order | None": ...
def save(self, order: "Order") -> None: ...
class PlaceOrder: # application service โ the core
def __init__(self, repo: OrderRepository, notify: NotificationSender):
self._repo = repo
self._notify = notify
def __call__(self, order: "Order") -> None:
self._repo.save(order)
self._notify.order_placed(order)
What this buys, concretely:
- Business logic tests run in-memory โ no Postgres, no HTTP, no mocks of the domain, only fakes at the port.
- Swapping infrastructure is a wiring change, not a rewrite: Postgres โ DynamoDB touches one adapter.
- The core compiles without knowing HTTP exists.
The failure mode to avoid: a repository per table leaking SQL-shaped shapes
(QuerySet, Row) into the domain. The port speaks the domain's language
(Order, OrderNotFound), and the adapter translates.
โก Now practice
Ready to CodeRepository PracticeA repository that speaks the domain's language โ and defends its own state.
1 challenge ยท ยท ~20 min