Lesson 39 โข Advanced
Architecture Patterns for Python Apps (MVC, Clean Architecture)
Master architectural patterns that separate concerns, enforce dependency rules, and build maintainable Python applications that scale from prototype to production
What You'll Learn
- What architecture means in Python applications
- MVC pattern and its modern interpretations
- Clean Architecture principles and dependency rules
- Ports and Adapters (Hexagonal Architecture)
- Layer separation: Domain, Application, Infrastructure, Interface
- Dependency inversion and abstraction
- Building testable, modular systems
- Real project structures for production apps
- Anti-patterns to avoid
- Refactoring strategies for existing codebases
- When to use complex architecture vs. simple patterns
- Testing strategies for each layer
What Is Architecture?
Architecture is how you organize code and responsibilities in an application. It determines where business rules live, how modules depend on each other, and how easy it is to change, test, and extend your system.
| โ Bad Architecture Symptoms | โ Good Architecture Signs |
|---|---|
| "God files" with 1000+ lines | Small, focused modules |
| Business logic mixed with SQL/HTTP | Clear layer boundaries |
| Can't test without starting servers | Each component testable in isolation |
| Changing DB breaks half the app | Can swap DB without major rewrites |
| New features cause ripple effects | Features added without breaking others |
MVC Pattern Basics
Model-View-Controller separates an application into three interconnected components:
Model
Represents data and business rules:
- Domain entities (User, Order, Product)
- Validation rules (email format, stock limits)
- Business operations (apply discount, cancel order)
- Domain logic that exists regardless of UI
View
Responsible for presenting data:
- HTML templates (Django, Flask + Jinja2)
- JSON responses (FastAPI, Flask-RESTful)
- CLI output formatting
- GUI windows
Views should be thin: format and present data, don't implement business rules
Controller
Coordinates between Model and View:
- Accept request/input
- Validate and parse data
- Call appropriate business logic
- Select and render the view
Clean Architecture Core Principle
"Dependencies point inward toward business rules, never outward."
This single rule prevents most long-term maintainability problems. Business logic should never depend on frameworks, databases, or UI code.
The Four Layers
1. Domain / Entities (Core)
Pure business objects and rules. Zero framework or database imports. Contains entities, value objects, and domain services.
2. Application / Use Cases
Orchestrates domain actions. Implements "what happens when..." logic. Defines interfaces (ports) for external dependencies.
3. Infrastructure / Adapters
Implements interfaces defined by inner layers. Contains DB, HTTP clients, file storage, external API wrappers, caching, queues.
4. Interface / Presentation
Entry points: web routes, CLI commands, event handlers, cron jobs. Thin layer that delegates to use cases.
Dependency Inversion Principle
Key Principle: High-level modules should not depend on low-level modules. Both should depend on abstractions (interfaces).
โ Bad: Direct Dependency
# use_case.py
from infrastructure.postgres import PostgresUserRepo
def register_user(email: str):
repo = PostgresUserRepo() # Tightly coupled!
repo.save(user)Problem: Use case knows about PostgreSQL. Can't test without DB. Can't switch to MongoDB.
โ Good: Depend on Abstraction
# interfaces.py
class UserRepository(Protocol):
def save(self, user: User) -> None: ...
# use_case.py
def register_user(email: str, repo: UserRepository):
repo.save(user) # Works with any implementation!Benefit: Use case depends on interface. Can test with fakes. Infrastructure is swappable.
Ports and Adapters (Hexagonal)
Hexagonal Architecture formalizes Clean Architecture with two key concepts:
Ports
Abstract interfaces defining what the application needs:
- โข UserRepository
- โข EmailSender
- โข PaymentGateway
- โข NotificationService
Adapters
Concrete implementations of ports:
- โข PostgresUserRepository
- โข SMTPEmailSender
- โข StripePaymentGateway
- โข SlackNotificationService
Key Benefits
- โข Switch PostgreSQL โ SQLite without touching business logic
- โข Test workflows with fake implementations (no network/DB)
- โข Change from CLI โ Web UI โ Mobile without rewriting core logic
- โข Each adapter is isolated - easier to maintain and replace
Production Project Structure
A professional Python application typically follows this structure:
project/
โ
โโโ domain/ # Core business logic
โ โโโ entities/ # Business objects
โ โ โโโ user.py
โ โ โโโ order.py
โ โ โโโ product.py
โ โโโ value_objects/ # Immutable values
โ โ โโโ email.py
โ โ โโโ money.py
โ โโโ services/ # Domain services
โ โโโ pricing.py
โ
โโโ application/ # Use cases / workflows
โ โโโ use_cases/
โ โ โโโ create_order.py
โ โ โโโ pay_order.py
โ โ โโโ cancel_order.py
โ โโโ dto/ # Data transfer objects
โ โ โโโ order_dto.py
โ โโโ interfaces/ # Ports (abstractions)
โ โโโ repositories.py
โ โโโ gateways.py
โ
โโโ infrastructure/ # Technical implementations
โ โโโ database/
โ โ โโโ sql_repository.py
โ โ โโโ models.py # ORM models
โ โโโ cache/
โ โ โโโ redis_cache.py
โ โโโ email/
โ โ โโโ smtp_sender.py
โ โโโ external/
โ โโโ payment_api.py
โ
โโโ interface/ # Entry points
โ โโโ api/ # REST API
โ โ โโโ routes.py
โ โ โโโ schemas.py
โ โโโ cli/ # Command line
โ โ โโโ commands.py
โ โโโ events/ # Event handlers
โ โโโ consumers.py
โ
โโโ tests/
โ โโโ unit/ # Domain & use case tests
โ โโโ integration/ # Infrastructure tests
โ โโโ e2e/ # Full system tests
โ
โโโ config/ # Configuration
โโโ settings.py
โโโ container.py # DI containerTesting Different Layers
| Test Type | What It Tests | Speed |
|---|---|---|
| Unit | Domain entities, value objects | โก Milliseconds |
| Integration | Use cases with fake repositories | โก Milliseconds |
| Infrastructure | Adapters with real DB/APIs | ๐ข Seconds |
| E2E | Full system, HTTP โ DB | ๐ Minutes |
Common Anti-Patterns
| Anti-Pattern | The Problem | The Fix |
|---|---|---|
| Fat Controller | Route handler does EVERYTHING | Extract logic to use cases |
| Anemic Domain | Entities are just data bags | Put behavior where data is |
| DB-Driven Design | Models mirror DB tables | Design domain first, map later |
| Framework-First | Logic coupled to Django/Flask | Core logic is framework-agnostic |
| God Object | One class knows everything | Break into focused modules |
Refactoring Toward Clean Architecture
Follow these steps in order:
- 1.
Identify Core Business Rules
Extract pure logic into separate modules with no framework/DB imports
- 2.
Introduce Interfaces (Ports)
Define Protocol interfaces for repositories and external services
- 3.
Wrap External Code in Adapters
Move SQL, HTTP, cache logic into adapter modules implementing interfaces
- 4.
Create Use Case Classes
Extract workflow orchestration into dedicated use case modules
- 5.
Thin Out Controllers
Controllers become: parse input โ call use case โ format response
When to Use Clean Architecture
โ Use Clean Architecture When:
- โข Building production applications
- โข Multiple developers on the team
- โข Long-term maintenance expected
- โข Multiple interfaces (web + CLI + mobile)
- โข Business logic is complex
- โข High refactoring risk
- โข Need comprehensive testing
- โข Compliance/audit requirements
โ Simple Structure Is Better For:
- โข Small personal projects
- โข One-off scripts or tools
- โข MVPs with unclear requirements
- โข Solo developer, low complexity
- โข Short-lived applications
- โข Prototypes and experiments
- โข Simple CRUD applications
- โข When speed matters more than structure
Choose architecture proportional to your project's expected lifespan and complexity.
Validating Your Architecture
Ask yourself these questions to evaluate your architecture:
Can I change the database without rewriting core logic?
โ Good architecture isolates DB behind interfaces
Can I test workflows without starting servers or databases?
โ Use cases should work with fake implementations
Does business logic import framework modules?
โ Red flag - domain should be framework-agnostic
Is complexity growing linearly or exponentially?
โ Good architecture scales linearly as features are added
Can I add a CLI interface without touching existing code?
โ Multiple interfaces should share the same core logic
Complete Architecture Example
Domain entities, use cases, and repository pattern in action
# Python Architecture Patterns Examples
# ============================================
# 1. DOMAIN ENTITY WITH BUSINESS RULES
# ============================================
from dataclasses import dataclass, field
from decimal import Decimal
from typing import List
from datetime import datetime
@dataclass
class OrderItem:
"""Pure domain entity - no dependencies"""
product_id: str
product_name: str
quantity: int
unit_price: Decimal
def subtotal(self) -> Decimal:
...Key Takeaways
- Architecture determines how easy your app is to change, test, and grow
- MVC separates presentation, business logic, and control flow
- Clean Architecture enforces dependency inversion: core depends on nothing
- Layers: Domain (core) โ Application (use cases) โ Infrastructure (adapters) โ Interface (API/CLI)
- Ports and Adapters make external dependencies swappable
- Test domain logic without databases or networks using fakes
- Avoid anti-patterns: fat controllers, anemic models, database-driven design
- Refactor incrementally - you don't need to rewrite everything at once
- Choose architecture complexity proportional to project lifespan
- Good architecture makes growth linear, not exponential in complexity
๐ Quick Reference โ Architecture Patterns
| Pattern | What it achieves |
|---|---|
| MVC | Separates data, display, and logic |
| Clean Architecture | Domain core independent of frameworks |
| Repository Pattern | Abstract data access behind an interface |
| Dependency Injection | Decouple components for testability |
| Event-Driven Architecture | Decouple services via events/messages |
๐ Great work! You've completed this lesson.
You can now apply Clean Architecture, MVC, and the Repository Pattern to structure Python apps that scale across teams and years.
Up next: Final Project โ bring everything together and build a portfolio-worthy Python project.
Sign up for free to track which lessons you've completed and get learning reminders.