Users Module (Reference Implementation)¶
Purpose¶
The users module demonstrates the production contract for all modules.
API Layer¶
@router.post("", response_model=UserRead, status_code=status.HTTP_201_CREATED)
def create_user(payload: UserCreate, service: UserService = Depends(get_user_service)):
user = service.create_user(payload)
return UserRead.model_validate(user)
Service Layer¶
def create_user(self, payload: UserCreate):
existing = self.repo.get_by_email(payload.email)
if existing is not None:
raise ConflictError(f"User with email {payload.email} already exists")
return self.repo.create(email=str(payload.email), full_name=payload.full_name)
Repo Layer¶
def get_by_email(self, email: str) -> User | None:
stmt = select(User).where(User.email == email)
return self.db.execute(stmt).scalar_one_or_none()
DI Wiring¶
def get_user_repo(db: Session = Depends(get_db)) -> UserRepo:
return UserRepo(db)
def get_user_service(repo: UserRepo = Depends(get_user_repo)) -> UserService:
return UserService(repo)
DO¶
- Raise domain exceptions from service.
- Keep repo methods query-focused.
- Return schema models from API.
DON'T¶
- Raise HTTPException inside repo.
- Parse request headers in service.
- Commit DB transactions from API.
Common Mistakes¶
- Duplicate uniqueness checks in both API and service.
- Returning ORM directly without schema mapping policy.
When to Break the Rule¶
Never bypass service for create/update/delete flows.