Aller au contenu principal

Architecture : Hexagonal

Structure des dossiers

src/
/domain # Logique métier pure, 0 dépendances externes
/entities # User, Order, Product (pas de DB)
/use_cases # CreateUser, PlaceOrder (interfaces requises)
/ports # IUserRepository, IEmailService (contrats)

/application # Orchestration, DTO
/handlers # Cas d'usage concrets
/dto # Requête/réponse

/infrastructure # Implémentations concrètes
/adapters
/persistence # SQLAlchemy, Prisma
/email # SendGrid, SMTP
/payment # Stripe API
/config # Base de données, variables d'env

Exemple : Créer un user

Domaine (logique métier pure)

# domain/use_cases/create_user.py (AUCUNE DÉPENDANCE EXTERNE)
class CreateUserUseCase:
def __init__(self, user_repository: IUserRepository):
self.repo = user_repository

def execute(self, email: str, name: str) -> User:
if not self._is_valid_email(email):
raise ValueError("Invalid email")

existing = self.repo.find_by_email(email)
if existing:
raise ValueError("User already exists")

user = User(email=email, name=name)
return self.repo.save(user)

@staticmethod
def _is_valid_email(email: str) -> bool:
return "@" in email

Infrastructure (implémentation concrète)

# infrastructure/adapters/persistence/user_repository.py (IMPLÉMENTATION)
class SQLAlchemyUserRepository(IUserRepository):
def __init__(self, session: Session):
self.session = session

def find_by_email(self, email: str) -> Optional[User]:
return self.session.query(UserModel).filter(UserModel.email == email).first()

def save(self, user: User) -> User:
model = UserModel(email=user.email, name=user.name)
self.session.add(model)
self.session.commit()
return user

Application (orchestration)

# application/handlers/create_user_handler.py (ORCHESTRATION)
@router.post("/users")
async def create_user(request: CreateUserRequest, db: Session = Depends()):
repository = SQLAlchemyUserRepository(db)
use_case = CreateUserUseCase(repository)

try:
user = use_case.execute(request.email, request.name)
return {"id": user.id, "email": user.email}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))

Règles pour l'IA

  • Jamais de dépendances externes dans /domain
  • /domain = Logique métier pure testable
  • /infrastructure = Détails techniques (DB, API, etc.)
  • Les ports (IUserRepository) sont des interfaces, pas des implémentations
  • Tests : mocker les repositories, tester la logique métier en isolation