How to Build Large, Maintainable Python Systems with Clean Architecture: Concepts & Real-World Examples
Why Clean Architecture?
As software grows, so does its complexity—leading to framework lock-in, untestable logic, and tangled code.
Clean Architecture keeps business rules independent of frameworks, databases, and UI.
“Your business logic should be testable and changeable without touching your framework or database.”
The Key Principle
Your business logic should be testable without binding to any framework.
If you can run your core logic—entities and use cases—in pure Python, you’re on the right track.
The Layers in Practice
Layered separation makes your codebase maintainable and testable:
- Entities: Plain business objects (e.g.
Car,Rental) - Use Cases: Application logic (e.g. "rent a car", "list all cars")
- Repositories/Interfaces: Abstractions for data access or services
- Frameworks/Drivers: Django, Flask, databases, UI code
UML Class Diagram
classDiagram
class Car {
+int car_id
+str make
+str model
+bool is_available
}
class Rental {
+int rental_id
+int car_id
+int user_id
+str start_date
+str end_date
}
class CarRepository {
+save(car)
+get_by_id(car_id)
+get_all()
+delete(car_id)
}
class RentalRepository {
+save(rental)
+get_by_id(rental_id)
+get_all()
+delete(rental_id)
}
class CarCRUDUseCase {
-car_repository
+create_car(car_id, make, model)
+get_car(car_id)
+list_cars()
+update_car(car_id, make, model, is_available)
+delete_car(car_id)
}
class RentCarUseCase {
-car_repository
-rental_repository
+execute(car_id, user_id, start_date, end_date)
}
class DjangoCarRepository {
+save(car)
+get_by_id(car_id)
+get_all()
+delete(car_id)
}
class MongoCarRepository {
+save(car)
+get_by_id(car_id)
+get_all()
+delete(car_id)
}
class DjangoRentalRepository
class MongoRentalRepository
CarCRUDUseCase --> CarRepository
RentCarUseCase --> CarRepository
RentCarUseCase --> RentalRepository
DjangoCarRepository --|> CarRepository
MongoCarRepository --|> CarRepository
DjangoRentalRepository --|> RentalRepository
MongoRentalRepository --|> RentalRepository
Flow Diagram: Class Interaction at Runtime
How does a user action (“rent a car”) flow through the layers?
sequenceDiagram
participant User
participant UI as UI/View (Django/Flask)
participant UC as RentCarUseCase
participant CarRepo as CarRepository
participant RentalRepo as RentalRepository
participant DB as DB/Framework
User->>UI: Submit rent car form (car_id, user_id, dates)
UI->>UC: execute(car_id, user_id, start_date, end_date)
UC->>CarRepo: get_by_id(car_id)
CarRepo->>DB: Query car by id
CarRepo-->>UC: Car entity
UC->>CarRepo: save(car) (mark as unavailable)
CarRepo->>DB: Update car
UC->>RentalRepo: save(rental)
RentalRepo->>DB: Insert rental record
RentalRepo-->>UC: Rental entity
UC-->>UI: Return Rental entity
UI-->>User: Render result (success page/HTML)
Sample Code: Each Layer in Python
1. Entities (Business Objects)
# entities/car.py
class Car:
def __init__(self, car_id, make, model, is_available=True):
self.car_id = car_id
self.make = make
self.model = model
self.is_available = is_available
# entities/rental.py
class Rental:
def __init__(self, rental_id, car_id, user_id, start_date, end_date):
self.rental_id = rental_id
self.car_id = car_id
self.user_id = user_id
self.start_date = start_date
self.end_date = end_date
2. Use Cases (Application Logic)
# use_cases/car_crud.py
class CarCRUDUseCase:
def __init__(self, car_repository):
self.car_repository = car_repository
def create_car(self, car_id, make, model):
car = Car(car_id=car_id, make=make, model=model, is_available=True)
self.car_repository.save(car)
return car
def get_car(self, car_id):
return self.car_repository.get_by_id(car_id)
def list_cars(self):
return self.car_repository.get_all()
def update_car(self, car_id, make, model, is_available):
car = Car(car_id=car_id, make=make, model=model, is_available=is_available)
self.car_repository.save(car)
return car
def delete_car(self, car_id):
self.car_repository.delete(car_id)
# use_cases/rent_car.py
class RentCarUseCase:
def __init__(self, car_repository, rental_repository):
self.car_repository = car_repository
self.rental_repository = rental_repository
def execute(self, car_id, user_id, start_date, end_date):
car = self.car_repository.get_by_id(car_id)
if not car or not car.is_available:
raise Exception("Car not available")
car.is_available = False
self.car_repository.save(car)
rental = Rental(
rental_id=None, car_id=car_id, user_id=user_id,
start_date=start_date, end_date=end_date
)
self.rental_repository.save(rental)
return rental
3. Repository Interfaces and Implementations
# interface_adapters/car_repository.py
class CarRepository:
def save(self, car): ...
def get_by_id(self, car_id): ...
def get_all(self): ...
def delete(self, car_id): ...
Django ORM Example:
# frameworks/django_repositories.py
from entities.car import Car
from myapp.models import CarModel
class DjangoCarRepository(CarRepository):
def save(self, car):
obj, _ = CarModel.objects.update_or_create(
pk=car.car_id,
defaults={'make': car.make, 'model': car.model, 'is_available': car.is_available}
)
return car
def get_by_id(self, car_id):
try:
obj = CarModel.objects.get(pk=car_id)
return Car(car_id=obj.pk, make=obj.make, model=obj.model, is_available=obj.is_available)
except CarModel.DoesNotExist:
return None
def get_all(self):
return [
Car(
car_id=obj.pk,
make=obj.make,
model=obj.model,
is_available=obj.is_available
)
for obj in CarModel.objects.all()
]
def delete(self, car_id):
CarModel.objects.filter(pk=car_id).delete()
MongoDB Example:
# frameworks/mongo_car_repository.py
from entities.car import Car
from interface_adapters.car_repository import CarRepository
class MongoCarRepository(CarRepository):
def __init__(self, db):
self.collection = db['cars']
def save(self, car):
self.collection.update_one(
{'car_id': car.car_id},
{'$set': {'make': car.make, 'model': car.model, 'is_available': car.is_available}},
upsert=True
)
return car
def get_by_id(self, car_id):
doc = self.collection.find_one({'car_id': car_id})
if not doc:
return None
return Car(
car_id=doc['car_id'],
make=doc['make'],
model=doc['model'],
is_available=doc.get('is_available', True)
)
def get_all(self):
return [
Car(
car_id=doc['car_id'],
make=doc['make'],
model=doc['model'],
is_available=doc.get('is_available', True)
)
for doc in self.collection.find()
]
def delete(self, car_id):
self.collection.delete_one({'car_id': car_id})
4. UI Layer Example: Django View and Template
# myapp/views.py
from django.shortcuts import render
from use_cases.car_crud import CarCRUDUseCase
from frameworks.django_repositories import DjangoCarRepository
def car_list_view(request):
use_case = CarCRUDUseCase(DjangoCarRepository())
cars = use_case.list_cars()
return render(request, "car_list.html", {"cars": cars})
Django Template (car_list.html):
<!DOCTYPE html>
<html>
<head>
<title>Car List</title>
</head>
<body>
<h1>Available Cars</h1>
<table border="1">
<tr>
<th>ID</th>
<th>Make</th>
<th>Model</th>
<th>Available</th>
</tr>
{% for car in cars %}
<tr>
<td>{{ car.car_id }}</td>
<td>{{ car.make }}</td>
<td>{{ car.model }}</td>
<td>{{ car.is_available|yesno:"Yes,No" }}</td>
</tr>
{% endfor %}
</table>
</body>
</html>
5. Flask API Example
from flask import Flask, request, jsonify
from pymongo import MongoClient
from use_cases.car_crud import CarCRUDUseCase
from frameworks.mongo_car_repository import MongoCarRepository
app = Flask(__name__)
client = MongoClient("mongodb://localhost:27017")
db = client["car_rental"]
car_repo = MongoCarRepository(db)
car_crud = CarCRUDUseCase(car_repo)
@app.route('/cars', methods=['POST'])
def create_car():
data = request.json
car = car_crud.create_car(data['car_id'], data['make'], data['model'])
return jsonify({"car_id": car.car_id, "make": car.make, "model": car.model, "is_available": car.is_available}), 201
@app.route('/cars', methods=['GET'])
def list_cars():
cars = car_crud.list_cars()
return jsonify([
{"car_id": c.car_id, "make": c.make, "model": c.model, "is_available": c.is_available}
for c in cars
])
6. Unit Testing Use Cases
import unittest
from entities.car import Car
from use_cases.car_crud import CarCRUDUseCase
class InMemoryCarRepository:
def __init__(self):
self._cars = {}
def save(self, car): self._cars[car.car_id] = car; return car
def get_by_id(self, car_id): return self._cars.get(car_id)
def get_all(self): return list(self._cars.values())
def delete(self, car_id): self._cars.pop(car_id, None)
class TestCarCRUDUseCase(unittest.TestCase):
def setUp(self):
self.repo = InMemoryCarRepository()
self.usecase = CarCRUDUseCase(self.repo)
def test_create_car(self):
car = self.usecase.create_car(1, 'Honda', 'Civic')
self.assertEqual(car.car_id, 1)
self.assertEqual(car.make, 'Honda')
def test_list_cars(self):
self.usecase.create_car(2, 'Toyota', 'Yaris')
cars = self.usecase.list_cars()
self.assertEqual(len(cars), 1)
self.assertEqual(cars[0].model, 'Yaris')
if __name__ == '__main__':
unittest.main()
Key Takeaways
- UI (Flask/Django/HTML): Just collects input, calls use cases, and presents results.
- Business logic is fully testable, reusable, and not coupled to frameworks.
- Repositories abstract data storage—you can swap MongoDB, SQL, or even in-memory for tests.
- Templates only display data—never mix business rules into your HTML.
If you can run all your core logic and tests without any web server or database, you’re building for the long run.
Want a deeper dive, form handling, or even an end-to-end repo? Let me know!
Get in Touch with us
Related Posts
- simpliShop:专为泰国市场打造的按需定制多语言电商平台
- simpliShop: The Thai E-Commerce Platform for Made-to-Order and Multi-Language Stores
- ERP项目为何失败(以及如何让你的项目成功)
- Why ERP Projects Fail (And How to Make Yours Succeed)
- Payment API幂等性设计:用Stripe、支付宝、微信支付和2C2P防止重复扣款
- Idempotency in Payment APIs: Prevent Double Charges with Stripe, Omise, and 2C2P
- Agentic AI in SOC Workflows: Beyond Playbooks, Into Autonomous Defense (2026 Guide)
- 从零构建SOC:Wazuh + IRIS-web 真实项目实战报告
- Building a SOC from Scratch: A Real-World Wazuh + IRIS-web Field Report
- 中国品牌出海东南亚:支付、物流与ERP全链路集成技术方案
- 再生资源工厂管理系统:中国回收企业如何在不知不觉中蒙受损失
- 如何将电商平台与ERP系统打通:实战指南(2026年版)
- AI 编程助手到底在用哪些工具?(Claude Code、Codex CLI、Aider 深度解析)
- 使用 Wazuh + 开源工具构建轻量级 SOC:实战指南(2026年版)
- 能源管理软件的ROI:企业电费真的能降低15–40%吗?
- The ROI of Smart Energy: How Software Is Cutting Costs for Forward-Thinking Businesses
- How to Build a Lightweight SOC Using Wazuh + Open Source
- How to Connect Your Ecommerce Store to Your ERP: A Practical Guide (2026)
- What Tools Do AI Coding Assistants Actually Use? (Claude Code, Codex CLI, Aider)
- How to Improve Fuel Economy: The Physics of High Load, Low RPM Driving













