Architecture Patterns
Master architectural patterns that separate concerns, enforce dependency rules, and build maintainable Python applications that scale from prototype to production
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
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
- 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
- HTML templates (Django, Flask + Jinja2)
- JSON responses (FastAPI, Flask-RESTful)
- CLI output formatting
Views should be thin: format and present data, don't implement business rules
Controller
- 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
Problem: Use case knows about PostgreSQL. Can't test without DB. Can't switch to MongoDB.
✓ Good: Depend on Abstraction
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
- • 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:
Testing Different Layers
Test Type
What It Tests
Speed
Unit
Domain entities, value objects
⚡ Milliseconds
Integration
Use cases with fake repositories
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
- 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
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
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.
Practice quiz
What is the single core rule of Clean Architecture?
- Dependencies point outward toward the database
- Every layer can import every other layer
- Dependencies point inward toward business rules, never outward
- The UI controls the domain
Answer: Dependencies point inward toward business rules, never outward. Clean Architecture's central rule is that dependencies point inward; business logic never depends on frameworks, DBs, or UI.
In MVC, which component holds the business rules and domain entities?
- Model
- View
- Controller
- Router
Answer: Model. The Model represents data, validation, and business operations; Views present data and Controllers coordinate.
What should a View do in MVC?
- Implement business rules
- Talk directly to the database
- Validate domain invariants
- Stay thin — just format and present data
Answer: Stay thin — just format and present data. Views should be thin: they format and present data and leave business rules to the Model.
What does the Dependency Inversion Principle state?
- High-level modules should import low-level modules directly
- Both high- and low-level modules should depend on abstractions (interfaces)
- Interfaces should depend on implementations
- Avoid all abstractions
Answer: Both high- and low-level modules should depend on abstractions (interfaces). High-level and low-level modules both depend on abstractions, so concrete details become swappable.
In Hexagonal (Ports and Adapters) architecture, what is a 'port'?
- An abstract interface defining what the application needs
- A concrete database class
- A network socket
- A UI component
Answer: An abstract interface defining what the application needs. Ports are abstract interfaces (e.g. UserRepository, EmailSender) describing required capabilities; adapters implement them.
Which is an example of an 'adapter' for the EmailSender port?
- EmailSender itself
- The use case
- SMTPEmailSender
- The domain entity
Answer: SMTPEmailSender. Adapters are concrete implementations of ports, such as SMTPEmailSender implementing the EmailSender interface.
What is the recommended order of the four Clean Architecture layers from core outward?
- Interface → Infrastructure → Application → Domain
- Domain → Application → Infrastructure → Interface
- Infrastructure → Domain → Interface → Application
- Application → Domain → Interface → Infrastructure
Answer: Domain → Application → Infrastructure → Interface. The lesson orders them Domain (core) → Application (use cases) → Infrastructure (adapters) → Interface (entry points).
Why are fake repositories useful when testing use cases?
- They run faster than real code but break easily
- They are required for production
- They replace the domain layer
- They let you test workflows in milliseconds without a real database or network
Answer: They let you test workflows in milliseconds without a real database or network. Because use cases depend on interfaces, you can inject in-memory fakes and test logic without servers or DBs.
Which is described as an anti-pattern in the lesson?
- Thin controllers that delegate to use cases
- An 'anemic domain' where entities are just data bags with no behavior
- Framework-agnostic core logic
- Small, focused modules
Answer: An 'anemic domain' where entities are just data bags with no behavior. An anemic domain (entities as mere data bags) is an anti-pattern; behavior should live with the data.
When is a simple structure preferred over full Clean Architecture?
- Large production apps with many developers
- Apps needing multiple interfaces
- Small personal projects, one-off scripts, and short-lived prototypes
- Compliance-heavy systems
Answer: Small personal projects, one-off scripts, and short-lived prototypes. Clean Architecture adds complexity; for small scripts, MVPs, and prototypes a simple structure is the better fit.
Continue this course
- Previous: Language Integration
- Next: Final Project