Import Guidelines¶
This document defines the import conventions for the Dashtam codebase to ensure clean architecture boundaries and prevent circular imports.
1. Core Principle: Protocol-First Imports¶
All cross-layer dependencies should use protocols (interfaces), not concrete implementations.
# ✅ CORRECT: Import protocol from domain
from src.domain.protocols.user_repository import UserRepository
# ❌ WRONG: Import concrete implementation
from src.infrastructure.persistence.repositories.user_repository import UserRepository
2. Layer-Specific Import Rules¶
2.1 Domain Layer (src/domain/)¶
Imports: NONE from other layers (domain is the core, depends on nothing)
Allowed imports:
- Standard library only
- Other domain modules (
domain.entities,domain.events,domain.protocols)
# ✅ CORRECT: Domain imports
from uuid import UUID
from datetime import datetime
from typing import Protocol
from src.domain.entities.user import User
from src.domain.events.auth_events import UserRegistrationSucceeded
# ❌ WRONG: Domain should NEVER import from other layers
from src.application.commands.register_user import RegisterUser # NO!
from src.infrastructure.persistence.models.user import UserModel # NO!
2.2 Application Layer (src/application/)¶
Imports: Domain layer ONLY (protocols, entities, events, errors)
Allowed imports:
- Standard library
src.domain.*(protocols, entities, events, errors)- Other application modules
# ✅ CORRECT: Application imports
from src.domain.protocols.user_repository import UserRepository
from src.domain.protocols.password_hashing_protocol import PasswordHashingProtocol
from src.domain.entities.user import User
from src.domain.events.auth_events import UserRegistrationSucceeded
from src.domain.errors.authentication_error import AuthenticationError
# ❌ WRONG: Application should NEVER import from infrastructure
from src.infrastructure.persistence.repositories.user_repository import UserRepository # NO!
from src.infrastructure.persistence.models.user import UserModel # NO!
from src.infrastructure.auth.bcrypt_password_service import BcryptPasswordService # NO!
2.3 Infrastructure Layer (src/infrastructure/)¶
Imports: Domain layer (implements protocols), external libraries
Allowed imports:
- Standard library
src.domain.protocols.*(to implement)src.domain.entities.*(to convert to/from models)- External libraries (SQLAlchemy, Redis, etc.)
- Other infrastructure modules
# ✅ CORRECT: Infrastructure imports
from src.domain.protocols.user_repository import UserRepository # Protocol to implement
from src.domain.entities.user import User # Entity to convert
from sqlalchemy.ext.asyncio import AsyncSession # External library
from src.infrastructure.persistence.models.user import UserModel # Internal model
# ❌ WRONG: Infrastructure should NEVER import from application
from src.application.commands.handlers.register_user_handler import RegisterUserHandler # NO!
2.4 Presentation Layer (src/presentation/)¶
Imports: Application layer (commands, queries), domain layer (entities for DTOs)
Allowed imports:
- Standard library
src.application.*(commands, queries, handlers)src.domain.entities.*(for response schemas)src.domain.errors.*(for error handling)- FastAPI dependencies (
src.core.container) - Pydantic schemas
# ✅ CORRECT: Presentation imports
from src.application.commands.register_user import RegisterUser
from src.application.commands.handlers.register_user_handler import RegisterUserHandler
from src.domain.entities.user import User
from src.core.container import get_register_user_handler
# ❌ WRONG: Presentation should NEVER import infrastructure directly
from src.infrastructure.persistence.repositories.user_repository import UserRepository # NO!
2.5 Container (src/core/container.py)¶
Special case: Container is the "Composition Root" and MUST import from infrastructure.
Allowed imports:
- All layers (this is where wiring happens)
- Infrastructure implementations
- Domain protocols
# ✅ CORRECT: Container imports (composition root)
from src.domain.protocols.user_repository import UserRepository # Protocol for type hint
from src.infrastructure.persistence.repositories.user_repository import UserRepository as UserRepositoryImpl # Implementation
3. Re-export Rules¶
3.1 No Cross-Boundary Re-exports¶
Each __init__.py should only re-export items from its own module:
# src/domain/protocols/__init__.py
# ✅ CORRECT: Re-export protocols only
from src.domain.protocols.user_repository import UserRepository
from src.domain.protocols.cache_protocol import CacheProtocol
# ❌ WRONG: Never re-export from other modules
from src.domain.events.auth_events import UserRegistrationSucceeded # NO!
3.2 Module Boundaries¶
| Module | Re-exports |
|---|---|
domain/protocols/ |
Protocols only |
domain/entities/ |
Entities only |
domain/events/ |
Events only |
domain/errors/ |
Errors only |
application/commands/ |
Commands only |
application/queries/ |
Queries only |
4. Protocol Naming Conventions¶
4.1 Repository Protocols¶
Pattern: <Entity>Repository
# ✅ CORRECT
class UserRepository(Protocol): ...
class RefreshTokenRepository(Protocol): ...
# ❌ WRONG: Don't use I prefix (not Pythonic)
class IUserRepository(Protocol): ... # NO!
class UserRepositoryInterface(Protocol): ... # NO!
4.2 Service Protocols¶
Pattern: <Feature>Protocol or <Feature>Service
# ✅ CORRECT
class PasswordHashingProtocol(Protocol): ...
class TokenGenerationProtocol(Protocol): ...
class CacheProtocol(Protocol): ...
5. Import Organization¶
5.1 Import Order¶
- Standard library
- Third-party packages
- Domain layer (
src.domain.*) - Application layer (
src.application.*) - Infrastructure layer (
src.infrastructure.*) - only in allowed contexts - Local imports
# Example: Application layer handler
from dataclasses import dataclass # 1. stdlib
from uuid import UUID
from result import Result, Ok, Err # 2. third-party
from src.domain.protocols.user_repository import UserRepository # 3. domain
from src.domain.entities.user import User
from src.domain.events.auth_events import UserRegistrationSucceeded
from src.application.commands.register_user import RegisterUser # 4. application (local)
5.2 Absolute vs Relative Imports¶
Always use absolute imports from src:
# ✅ CORRECT: Absolute imports
from src.domain.protocols.user_repository import UserRepository
# ❌ WRONG: Relative imports (harder to refactor)
from ..protocols.user_repository import UserRepository
6. Registry Pattern Imports¶
Dashtam uses registry patterns for events, routes, and rate limits. These follow specific import rules.
6.1 Event Registry¶
Location: src/domain/events/registry.py
The Event Registry is a domain module that catalogs all domain events. Import it in container and infrastructure:
# ✅ CORRECT: Container imports event registry
# src/core/container/events.py
from src.domain.events.registry import EVENT_REGISTRY, EventMetadata
# ✅ CORRECT: Infrastructure imports specific events
from src.domain.events.auth_events import UserRegistrationSucceeded
# ❌ WRONG: Application layer shouldn't import registry directly
# (Use event_bus.publish() instead)
6.2 Route Registry¶
Location: src/presentation/routers/api/v1/routes/registry.py
The Route Registry is a presentation module that catalogs all API routes:
# ✅ CORRECT: Route generator imports registry
# src/presentation/routers/api/v1/routes/generator.py
from src.presentation.routers.api.v1.routes.registry import ROUTE_REGISTRY
from src.presentation.routers.api.v1.routes.metadata import RouteMetadata
# ✅ CORRECT: Rate limit module imports for derivation
# src/infrastructure/rate_limit/from_registry.py
from src.presentation.routers.api.v1.routes.registry import ROUTE_REGISTRY
6.3 Registry Import Summary¶
| Registry | Location | Who Can Import |
|---|---|---|
EVENT_REGISTRY |
domain/events/registry.py |
Container, Infrastructure (event handlers) |
ROUTE_REGISTRY |
presentation/routers/.../registry.py |
Route generator, Rate limit infrastructure |
7. Common Violations to Avoid¶
6.1 Application Importing Infrastructure Models¶
# ❌ WRONG: Handler constructing infrastructure model
from src.infrastructure.persistence.models.user import UserModel
class RegisterUserHandler:
async def handle(self, cmd: RegisterUser) -> Result:
# BAD: Handler knows about database model
user_model = UserModel(email=cmd.email, ...)
await self.user_repo.save(user_model)
Fix: Handler should work with domain entities, repository handles conversion.
# ✅ CORRECT: Handler uses domain entity
from src.domain.entities.user import User
class RegisterUserHandler:
async def handle(self, cmd: RegisterUser) -> Result:
# GOOD: Handler uses domain entity
user = User(email=cmd.email, ...)
await self.user_repo.save(user) # Repo converts to model internally
6.2 Domain Importing External Libraries¶
# ❌ WRONG: Domain depends on SQLAlchemy
from sqlalchemy import Column, String # NO!
class User:
id = Column(String, primary_key=True) # NO!
Fix: Domain entities are pure Python dataclasses.
8. Verification Commands¶
Check for Infrastructure Imports in Application Layer¶
Check for Application Imports in Domain Layer¶
Verify Import Test¶
docker compose -f compose/docker-compose.dev.yml exec app uv run python -c "from src.application.commands.handlers.register_user_handler import RegisterUserHandler; print('✅')"
Created: 2025-11-25 | Last Updated: 2026-01-10