Layered & hexagonal architecture
P38.architecture.01 · Audience: guest, it-ml, language-pro
As a system grows, the thing that decides whether it stays changeable is where the boundaries are. This pillar is about shaping code into services — and it starts with the oldest lever there is: depend on interfaces, not concretes. This module has you refactor a tangled module into ports & adapters.
ⓘ Concept: Constructing your own dependency traps you
EmailNotifier and calls it, the core logic is welded to that concrete class. You can't test it without sending real mail, and you can't swap the notifier without editing the core. The dependency points the wrong way — from the core out to a concrete detail.Why it matters — Code that builds its own database or notifier can't be tested without the real thing and can't be pointed elsewhere without editing the core — the root of untestable systems.
The move, in shape: the core stops constructing its dependency and takes it by injection.
from typing import Protocol
class Notifier(Protocol): # the port: what, not how
def send(self, message: str) -> None: ...
class OrderService: # the core depends on the port
def __init__(self, notifier: Notifier):
self._notifier = notifier # injected adapter
Ask the mentor about this module
Ask a question about this content. The mentor explains and grounds its answer in what you are studying; asking is recorded as a learning signal, not a grade.
🎓 Practice ladder
2 graded rungs · ~24 minRefactor a tangled module to ports and adapters, then reason about architecture and boundaries in an interview-style prompt.
Rung 1 — refactor to ports & adapters
Loading exercise…
Rung 2 — architecture & boundaries (interview)
Loading exercise…
With clean boundaries in place, the next module puts a boundary on the outside of the system — an API other code calls.