When building a FastAPI backend, choosing the right structure is essential for long-term health. While pure theory can be complex, this guide focuses on a practical, three-layered approach that captures the spirit of Clean Architecture (CA) and Domain-Driven Design (DDD) without excessive complexity.
The goal is simple: Keep your business rules independent of the framework (FastAPI) and the database (SQLAlchemy).
Table of Contents
π‘ How Clean Architecture and DDD Work Together ?
Clean Architecture is a popular approach for structuring a backend application into clear four layers:
- Presentation (FastAPI routes)
- Application (use cases)
- Domain (business rules)
- Infrastructure (DB, email, external APIs)
In many projects, the Domain layer uses ideas from Domain-Driven Design (DDD). Clean Architecture helps keep a FastAPI project organized in layers so that business logic is independent from frameworks and databases.
In real SaaS backend systems, the most practical approach is:
Use Clean Architecture as the structure, and apply DDD concepts inside the Domain layer.
- Clean Architecture organizes the entire project into layers.
- DDD focuses on modeling the business rules correctly.
β
Backend is where business logic lives β so Clean + DDD is used the most.
β
FastAPI fits naturally into this approach.
π― Core Idea
| Concept | Focus |
|---|---|
| Clean Architecture | Structure of the whole system |
| DDD | Business logic and rules (Domain) |
Clean Architecture = the house
DDD = the rules of how the house should function
Layer (Simple Meaning)
| Layer | Role | Example | Analogy |
|---|---|---|---|
| Domain | Business rules | Entities, domain services | π§ Brain β What must happen |
| Application | Workflow/use cases | Use case classes | πΌ Conductor β Coordinates the actions |
| Infrastructure | Technical details | DB, Email, external services | π§ Tools β Where data goes |
| Presentation | API input/output | FastAPI routes/controllers | π Receptionist β Who is asking |
Dependency direction (Always inwards)
Presentation β Application β Domain
Infrastructure β Application β Domain
Client (HTTP)
β
Presentation Layer (FastAPI)
β
Application Layer (Use Cases)
β
Domain Layer (Business Rules)
β
Infrastructure Layer (DB, Email, External APIs)
β
Domain knows nothing about Frameworks, Databases, JWT, etc.
β FastAPI code inside Domain =Β wrong
Key Benefits in FastAPI Context
- Domain Layer: Pure business logic, no FastAPI/SQLAlchemy imports
- Application Layer: Orchestrates use cases
- Infrastructure: Handles databases, external APIs
- Presentation: FastAPI routes, just input/output
Clean Architecture and DDD are often used as inspirational frameworks or design philosophies, not strict blueprints. There is difference between architectural theory and real-world industry practice, especially in the context of speed and project complexity. In many real-world, production-ready systems, developers often simplify the four layers into three, and the architecture is used as an inspiration rather than a rigid rule set. In production main project do merge the application use case and domain service into single folder called services.
π― The Core Distinction: Flow vs. Logic
In a strict Clean Architecture (CA) or Domain-Driven Design (DDD) model, the Application Layer and the Domain Layer have separate, specific roles:
- Application Service (Use Case): Its job is workflow and orchestration (the “when” and “what”). It acts as the workflow decider, handling conditional flow like, “IF the user is an admin, THEN call the
DeleteUsermethod.” It dictates the sequence of steps and validates high-level conditions. - Domain Service: Its job is the main business logic (the “how”). It contains complex, framework-independent rules that don’t belong to a single entity, such as a
FraudDetectionServiceor aTaxCalculationService.
Practical Compromise in Most Projects
While this separation is ideal, in many real-world projects, the strict boundary between these two types of services is softened. We often merge Application and Domain services into a single “Services” layer. This combined layer handles both the workflow orchestration (the “when”) and the implementation of the core business logic (the “how”) for simplicity and development speed.
π A Developer’s Practical Guide
1. The Core Idea: The Dependency Rule π‘οΈ
Clean Architecture is based on one unbreakable rule: Inner layers cannot know about outer layers.
| Layer | Dependency Direction | Key Takeaway |
| Domain (Core) | $\leftarrow$ Depends on nothing | Most Stable. Your business logic shouldn’t import FastAPI or SQL. |
| Infrastructure (Outer) | $\to$ Depends on everything | Most Volatile. This layer handles all the technical details. |
2. Practical 3-Layer FastAPI Structure
For most production FastAPI apps, we simplify the four theoretical CA layers (Presentation, Application, Domain, Infrastructure) into three practical folders:
| Folder Name | Architectural Layer | Primary Contents | Why? (The Rule) |
routers/ | Presentation | FastAPI endpoints, Pydantic Schemas (Input/Output). | Handles HTTP I/O only. Delegates work. |
services/ | Application & Domain (Mixed) | Use Case Classes (workflow) and complex business logic (Domain Services). | Orchestrates. Calls the DB (via Repository) and runs business rules. |
repository/ & db/ | Infrastructure | SQLAlchemy ORM Models, Database session management, CRUD logic (Repository Implementations). | Handles all technical details (SQL, external APIs). |
3. Mapping Code to Folders (The calculate_tax() Example)
Let’s use the core business rule order.calculate_tax() to show where each piece of code lives.
A. The Business Logic (The “How”): services
In a simplified structure, the pure business logic often resides directly within a service or entity model inside the services/ folder. This is a practical compromise, ensuring it still doesn’t touch the DB.
- File:
services/order_service.py - Content: Contains the class/method defining the exact tax formula.
# services/order_service.py (Part of the "Mixed" Business Layer)
class OrderService:
def __init__(self, order_repo: OrderRepository):
# Dependencies injected (order_repo is the interface to the DB)
self.order_repo = order_repo
def place_order(self, items_value: float):
# 1. ORCHESTRATION (Application Layer Role)
order = OrderEntity(total_value=items_value)
# 2. CALL BUSINESS LOGIC (Domain Layer Role)
tax = self._calculate_tax_rule(items_value)
order.set_tax(tax)
# 3. USE INFRASTRUCTURE
self.order_repo.save(order)
return order
def _calculate_tax_rule(self, value: float) -> float:
# PURE BUSINESS RULE (Must not import SQLAlchemy)
if value > 100:
return value * 0.05 + 5.00 # 5% + $5 handling fee
return value * 0.05
That’s a great idea! We’ll synthesize the theory of Clean Architecture (CA) and Domain-Driven Design (DDD) with the practical realities of a FastAPI project structure.
Here is an article drafted from our conversation, focused on simplicity and practical application.
B. The Technical Details (The “Save”): repository/
This layer knows how to save the data to the external world. It handles the mapping between your business object (the OrderEntity) and the SQLAlchemy table schema.
- File:
repository/sql_order_repository.py - Content: Contains the SQLAlchemy imports, sessions, and CRUD methods.
C. The API Endpoint (The “I/O”): routers/
This layer is just the entry point. It takes the HTTP request, validates it with Pydantic, and immediately delegates the work to the services/ layer.
- File:
routers/orders.py - Content: The FastAPI route definition.
Python
# routers/orders.py (Presentation Layer)
from fastapi import APIRouter, Depends
from services.order_service import OrderService # Dependency Injection
router = APIRouter()
@router.post("/orders")
def create_order(value: float, order_service: OrderService = Depends()):
# NO business logic or database calls here!
order = order_service.place_order(value)
return {"status": "success", "order_id": order.id, "tax": order.tax_amount}
4. Why This Works (And Why It Matters)
This practical three-layer structure achieves the primary benefit of Clean Architecture: Testability.
| Folder | What You Test | Dependency | Benefit |
services/ | The business rules (_calculate_tax_rule) and the workflow (place_order). | None (or mocked repository). | Fastest, most reliable tests. Protects your most valuable code. |
repository/ | The database queries and mapping. | SQLAlchemy, DB connection. | Confirms your SQL is correct without hitting the API. |
routers/ | The API request/response format. | FastAPI TestClient. | Confirms the user receives the correct JSON output. |
By using CA and DDD as inspirationβfocusing on decoupling the logic (services/) from the database (repository/)βyou build a FastAPI application that is easy to maintain, understand, and scale.
πΉMore Info
β Schema / Models Confusion are not Domain Modelsβ Important Answer
In our code we had ORM model like below this is user.py model but is not domain models, here we had import and used SQLAlchemy ORM database model π So it belongs to the: Infrastructure Layer, That means it is not a pure business entity
Domain Layer = Pure Python classes with business behavior, not DB or API models.

Here is any example of domain entity example (pure python)
class User:
def __init__(self, email: str, password_hash: str):
if "@" not in email:
raise ValueError("Invalid email")
self.email = email
self.password_hash = password_hash
def activate(self):
self.is_active = True
β
No SQLAlchemy
β
No DB concern
β
Business rule inside (email must be valid)
2. Revised: The Services Layer (Combined Application & Domain)
When combining the two layers, the folder (e.g., services/ or use_cases/) becomes the central hub for all business-critical code. It is no longer just the “director”βit is both the director and the actor.
1. New Role and Core Responsibilities
| Previous Role | New Combined Role |
| Orchestration (Application) | Workflow Management: Decides the sequence of actions. Calls Infrastructure (Repositories) and handles transactions. |
| Business Logic (Domain) | Rule Execution: Contains the actual implementation of complex business rules (e.g., how to calculate tax, how to apply a discount, validating constraints). |
2. What Belongs in the Combined Services Layer
| Component | Purpose | Key Change from Pure CA |
| Use Cases / Service Methods | Define the high-level workflow (place_order, register_user). | STILL ORCHESTRATE. |
| Domain Entities | Pure Python objects with behaviors (Order.calculate_tax()). | Logic lives here. The service method calls these behaviors or runs the logic directly. |
| Domain Services | Complex logic spanning multiple entities (e.g., FraudDetectionService). | Logic lives here. |
| Transaction Management | Start/commit/rollback database operations. | STILL ORCHESTRATE. |
π§ 3. Infrastructure Layer (The Tools)
The Infrastructure Layer handles all technical and external dependencies. It acts as the technical interface to the outside world, ensuring the inner layers (Domain and Services) remain pure.
| Component | Purpose | Key Rule |
| ORM Models (SQLAlchemy) | Database table mapping. | All SQL/DB code belongs here (repository/). |
| Repository Implementations | Actual CRUD logic and DB queries. | This is the physical execution of data persistence. |
| External Service Adapters | Email, file storage (S3), payment processing. | Handles interaction with all external APIs. |
| Authentication | JWT encoding/decoding, password hashing, OAuth. | Because it relies on external libraries/storage (DB). |
β Crucial Distinction: If a file performs CRUD operations with SQLAlchemy or any database framework, it is an Infrastructure component (a Repository), NOT a business service.
ποΈ 4. Presentation Layer (The Messenger)
This is the entry point of your application, responsible only for handling HTTP requests and responses. It is the boundary between your internal logic and the user.
| Component | Purpose | Key Rule |
| FastAPI Routers | Defines endpoints (@router.post, @router.get). | Delegates work immediately to the Services Layer. |
| Pydantic Schemas | Input validation and output serialization. | Used to shape the API request/response format. |
| Dependency Injection | Attaching the correct services (Use Cases, Repositories) to the route. | It’s a technical concern for framework setup. |
The Presentation Layer Never:
- Contains business rules.
- Executes database queries (No SQLAlchemy imports).
The main benefit is flexibility: if you switch from FastAPI to another framework, only this layer needs to be rewritten; your core business logic remains intact.
Python
from fastapi import APIRouter, Depends
# Imports the Service/Use Case from the Services Layer
from services.user_service import CreateUserUseCase
# Imports Pydantic Schemas from the Presentation Layer
from schemas.user import UserCreate, UserResponse
router = APIRouter()
@router.post("/users", response_model=UserResponse)
def create_user(data: UserCreate, use_case: CreateUserUseCase = Depends()):
# Delegates the entire workflow to the Application Layer
return use_case.execute(data)
π οΈ Cross-Cutting Concerns (The Shared Tools)
These are technical elements that apply across all layers but don’t belong exclusively to any one layer. They typically live in a core/ or shared/ folder.
| Concern | Layer | Notes |
| Logging, CORS, DI | Cross-cutting | Technical utilities used everywhere. |
| Authentication | Infrastructure | Relies on external tools (JWT, DB). |
| Authorization Rules | Domain | The business rule itself: “Only admins can delete.” |
| Authorization Enforcement | Application | Applying the rule in the workflow: “Check if the current user meets the Domain’s rule.” |
β Testing in Clean Architecture
Testing is not a layer; it’s a separate slice that validates each layer’s behavior independently. Prioritize testing the inner layers first.
| Priority | Test Type | Target Layer | Focus |
| 1st | Unit Tests | Domain (Business Logic) | Protects core rules. Fastest, requires no DB or FastAPI. |
| 2nd | Use Case Tests | Application (Workflow) | Tests permissions and multi-step business flows. |
| 3rd | Integration Tests | Infrastructure (DB/External) | Tests real SQLAlchemy queries and Postgres connection. |
| 4th | API Tests | Presentation (FastAPI) | Confirms correct HTTP input/output using TestClient. |
Testing must live outside the main source code (e.g., in a tests/ directory) to avoid violating the Dependency Rule.
Summary Table (Perfect Version
Tests
|
------------------------------------------------
| Presentation | Application | Domain | Infrastructure |
Types of Tests & Their Layer Mapping
project/
βββ tests/
βββ unit/ (domain)
βββ application/
βββ infrastructure/
βββ api/
Summary:

Related Articals