Layered Architecture
The four layers (domain, application, infrastructure, API), their responsibilities, and interaction patterns.
Status: Extracted
Purpose: Layer structure, layer responsibilities, and dependency direction.
Layer Structure
Four-Layer Architecture (API Services)
For services with public APIs (e.g., account-api):
┌─────────────────────────────────────┐
│ API Layer │ ← FastAPI routes, webhooks, request/response
├─────────────────────────────────────┤
│ Application Layer │ ← Use cases, orchestration, commands
├─────────────────────────────────────┤
│ Domain Layer │ ← Aggregates, entities, value objects, domain services
├─────────────────────────────────────┤
│ Infrastructure Layer │ ← Repositories, adapters, external services
└─────────────────────────────────────┘
Three-Layer Architecture (Worker Services)
For worker services without public APIs (e.g., message-distribution):
┌─────────────────────────────────────┐
│ Application Layer │ ← Event handlers, routing orchestration, command handlers
├─────────────────────────────────────┤
│ Domain Layer │ ← Aggregates, entities, value objects, domain services
├─────────────────────────────────────┤
│ Infrastructure Layer │ ← Event consumers, event publishers, adapters
└─────────────────────────────────────┘
Layer Responsibilities
Domain Layer (Innermost)
Location: domain/
Responsibilities:
- Aggregates: Aggregate roots and entities
- Value Objects: Immutable domain objects
- Domain Services: Domain operations not belonging to single entity
- Business Rules: Invariants and domain logic
- Repository Interfaces: Ports (interfaces) for data access
Characteristics:
- No dependencies on other layers
- Pure domain logic
- Framework-independent
- Testable in isolation
Application Layer
Location: application/
Responsibilities:
- Use Cases: Orchestration of domain operations
- Command Handlers: Handle commands from API layer
- Event Handlers: Handle events from infrastructure
- Application Services: Coordinate domain objects and repositories
- Service Interfaces: Ports (interfaces) for external services
Characteristics:
- Depends on domain layer only
- Orchestrates domain operations
- Coordinates infrastructure adapters
- Framework-independent business logic
API Layer (If Present)
Location: api/
Responsibilities:
- Routes: HTTP endpoints (FastAPI routes)
- Request/Response: Request validation, response formatting
- Webhooks: Webhook handlers
- Authentication: Token extraction (delegates to infrastructure)
- Error Handling: Exception handling and HTTP response mapping
Characteristics:
- Depends on application layer
- Framework-specific (FastAPI)
- Thin layer (delegates to application)
- Handles HTTP concerns
Infrastructure Layer (Outermost)
Location: infrastructure/
Responsibilities:
- Repositories: Data access implementations
- Adapters: External service clients
- Event Publishers: Event publishing implementations
- Event Consumers: Event consumption (SQS, Redis)
- Database: SQLAlchemy models, migrations
- Logging: Logging infrastructure
- Authorization: Authorization service implementations
Characteristics:
- Depends on all inner layers
- Framework and tool-specific
- Implements ports (interfaces) from inner layers
- Handles technical concerns
Dependency Direction
Dependency Rule
Dependencies point inward. Outer layers depend on inner layers, never the reverse.
Flow:
- Infrastructure → API → Application → Domain
- Inner layers never depend on outer layers
- Inner layers define interfaces (ports)
- Outer layers implement interfaces (adapters)
Dependency Injection
Principle: Use dependency inversion for testability and flexibility.
Approach:
- Interfaces defined in appropriate layers (domain/application define what they need)
- Implementations in infrastructure layer
- Dependencies injected via constructor or function parameters
- Configuration/bootstrap triggered on application startup
Layer Interaction Patterns
API → Application
Pattern:
# API layer
@router.post("/accounts")
async def create_account(
request: CreateAccountRequest,
account_service: AccountService = Depends(get_account_service),
):
"""Create account endpoint."""
command = CreateAccountCommand(
phone=request.phone,
name=request.name,
)
account = await account_service.create_account(command)
return AccountResponse.from_domain(account)Application → Domain
Pattern:
# Application layer
class AccountService:
def __init__(self, account_repository: IAccountRepository):
self._account_repository = account_repository
async def create_account(self, command: CreateAccountCommand) -> Account:
"""Create account use case."""
account = Account.create(
phone=command.phone,
name=command.name,
)
await self._account_repository.add(account)
return accountInfrastructure → Application/Domain
Pattern:
# Infrastructure layer implements port
class SQLAlchemyAccountRepository:
"""Implements IAccountRepository from domain layer."""
async def add(self, account: Account) -> None:
"""Add account to repository."""
account_model = self._to_model(account)
self.session.add(account_model)Related Documents
- Domain-Driven Design - DDD patterns
- Hexagonal Architecture - Ports & Adapters
- Repository Pattern - Repository implementation
Reference implementation: domain/, application/, infrastructure/, api/ folder structure in any FastAPI service.
Framework implementations
This guide covers how our abstract architecture standards are implemented in FastAPI. It does not re-explain DDD or hexagonal architecture — see the references below for those.
See also:
How the Layers Map
| Abstract layer | FastAPI folder | Role |
|---|---|---|
| Domain | domain/ | Aggregates, entities, value objects, abstract repository interfaces. Zero external dependencies. |
| Application | application/ | Use-case handlers (commands), query handlers, abstract service interfaces (publisher, logger, monitor, UoW). |
| Infrastructure | infrastructure/ | Concrete adapter implementations — database, events, cache, storage, monitoring, logging, auth. |
| API (Driving adapter) | api/ | FastAPI routes, middleware, request/response schemas, DI container, exception handlers. |
| Configuration | config/ | Pydantic BaseSettings — environment variables, adapter selection, feature flags. |
Dependency direction: api/ → application/ → domain/. infrastructure/ implements interfaces defined in application/ and domain/. config/ is read by the infrastructure factories and the DI container only.
Key FastAPI Idioms
Dependency Injection via Depends()
FastAPI's Depends() is the mechanism for injecting services into routes. All service construction happens in api/dependencies.py. Routes declare what they need via function parameters — FastAPI resolves and injects them.
Adapter switching via settings
Each infrastructure concern (database, events, storage, monitoring) has multiple adapter implementations and a factory. The factory reads a settings field (e.g. event_adapter = "inmemory" | "sns") and returns the appropriate implementation. This allows swapping adapters without changing application code.
Middleware for cross-cutting concerns
Logging, auth, request context, and CORS are handled as ASGI middleware registered on the FastAPI app — not inside route handlers. See 04-middleware-stack.md.
Lifespan for startup/shutdown
Database connection pools, background flush tasks (monitoring), and other process-level resources are initialised and torn down in api/lifespan.py using FastAPI's lifespan context manager.
Reference Implementation
All patterns in this guide reflect a canonical FastAPI service implementing the full four-layer structure described above.