How to Apply the Clean Architecture Dependency Rule in Python
Demonstrates the dependency rule with a Protocol repository, a use case, and a presenter wired together at a composition root.
Python code
38 linesfrom dataclasses import dataclass
from typing import List, Protocol
class Repository(Protocol):
def get_items(self) -> List[str]:
...
@dataclass
class InMemoryRepository:
items: List[str]
def get_items(self) -> List[str]:
return self.items
class UseCase:
"""Application layer depends on interfaces, not implementations."""
def __init__(self, repository: Repository):
self.repository = repository
def execute(self) -> List[str]:
return [item.upper() for item in self.repository.get_items()]
class ConsolePresenter:
"""Interface layer depends on application and domain layers."""
def present(self, items: List[str]) -> None:
print(f"Items: {', '.join(items)}")
def main():
# Composition root: wires dependencies
repository = InMemoryRepository(items=["apple", "banana", "cherry"])
usecase = UseCase(repository)
presenter = ConsolePresenter()
# Presenter calls usecase, usecase calls repository
presenter.present(usecase.execute())
if __name__ == "__main__":
main()
Output
Items: APPLE, BANANA, CHERRY
How it works
The dependency rule says source code dependencies point inward: the application layer (UseCase) depends only on abstractions, not concrete implementations. Here Repository is a Protocol, so UseCase never imports InMemoryRepository — the concrete type is injected at runtime. ConsolePresenter lives in the interface layer and depends on the application layer by calling usecase.execute(). The main() function acts as the composition root, wiring all concrete objects together. This keeps the use case testable and lets you swap repositories or presenters without changing application logic.
Common mistakes
- Making the use case import the concrete repository instead of a Protocol or abstract base class
- Placing the composition root inside the application layer instead of at the app's entry point
- Letting the domain layer depend on the framework or interface layer
- Using concrete types in constructor hints instead of duck-typed Protocols
Variations
- Use `abc.ABC` and `@abstractmethod` instead of `Protocol` for explicit abstract classes
- Inject a presenter into the use case for read-model style responses rather than returning data
Real-world use cases
- Structuring a Flask or FastAPI service so controllers call use cases that depend on database repositories through interfaces.
- Swapping a production SQL repository for an in-memory fake in unit tests without touching the use case code.
- Building a CLI tool where the presenter writes to stdout today and to a file or API later.
Sponsored
More from System design patterns
- Build a BFF (Backend for Frontend) Mock Aggregator in Python medium
- Builder pattern for mocking complex objects in Python easy
- Circuit Breaker Pattern in Python: Closed, Open, and Half-Open States medium
- Create a Data Helper Class in Python easy
- Domain Driven Design Aggregate Root Example in Python medium
- Facade Pattern in Python with Mock Simplification medium
Keep learning
Related tutorials and quizzes for this topic.