FastAPI Clean Architecture: A Developer’s Practical Guide

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).

πŸ’‘ 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

ConceptFocus
Clean ArchitectureStructure of the whole system
DDDBusiness logic and rules (Domain)

Clean Architecture = the house
DDD = the rules of how the house should function

Layer (Simple Meaning)

LayerRoleExampleAnalogy
DomainBusiness rulesEntities, domain services🧠 Brain β€” What must happen
ApplicationWorkflow/use casesUse case classes🎼 Conductor β€” Coordinates the actions
InfrastructureTechnical detailsDB, Email, external servicesπŸ”§ Tools β€” Where data goes
PresentationAPI input/outputFastAPI 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 DeleteUser method.” 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 FraudDetectionService or a TaxCalculationService.

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.

LayerDependency DirectionKey Takeaway
Domain (Core)$\leftarrow$ Depends on nothingMost Stable. Your business logic shouldn’t import FastAPI or SQL.
Infrastructure (Outer)$\to$ Depends on everythingMost 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 NameArchitectural LayerPrimary ContentsWhy? (The Rule)
routers/PresentationFastAPI 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/InfrastructureSQLAlchemy 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.

FolderWhat You TestDependencyBenefit
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.

mple

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 RoleNew 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

ComponentPurposeKey Change from Pure CA
Use Cases / Service MethodsDefine the high-level workflow (place_order, register_user).STILL ORCHESTRATE.
Domain EntitiesPure Python objects with behaviors (Order.calculate_tax()).Logic lives here. The service method calls these behaviors or runs the logic directly.
Domain ServicesComplex logic spanning multiple entities (e.g., FraudDetectionService).Logic lives here.
Transaction ManagementStart/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.

ComponentPurposeKey Rule
ORM Models (SQLAlchemy)Database table mapping.All SQL/DB code belongs here (repository/).
Repository ImplementationsActual CRUD logic and DB queries.This is the physical execution of data persistence.
External Service AdaptersEmail, file storage (S3), payment processing.Handles interaction with all external APIs.
AuthenticationJWT 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.

ComponentPurposeKey Rule
FastAPI RoutersDefines endpoints (@router.post, @router.get).Delegates work immediately to the Services Layer.
Pydantic SchemasInput validation and output serialization.Used to shape the API request/response format.
Dependency InjectionAttaching 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.

ConcernLayerNotes
Logging, CORS, DICross-cuttingTechnical utilities used everywhere.
AuthenticationInfrastructureRelies on external tools (JWT, DB).
Authorization RulesDomainThe business rule itself: “Only admins can delete.”
Authorization EnforcementApplicationApplying 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.

PriorityTest TypeTarget LayerFocus
1stUnit TestsDomain (Business Logic)Protects core rules. Fastest, requires no DB or FastAPI.
2ndUse Case TestsApplication (Workflow)Tests permissions and multi-step business flows.
3rdIntegration TestsInfrastructure (DB/External)Tests real SQLAlchemy queries and Postgres connection.
4thAPI TestsPresentation (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:

Clean architecture FastAPI

Related Articals

  1. Introduction to FastAPI
Scroll to Top