1.3 ארכיטקטורה נקייה פתרון
תרגיל 1 - ישויות - פתרון¶
# entities/project.py
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class Project:
id: int
name: str
description: str
owner_id: int
created_at: datetime
is_archived: bool = False
def archive(self):
self.is_archived = True
def can_be_modified_by(self, user_id: int) -> bool:
return self.owner_id == user_id
תרגיל 2 - Use Case - פתרון¶
# use_cases/project_use_cases.py
from entities.project import Project
from datetime import datetime
class ProjectRepository:
def save(self, project: Project) -> Project: ...
def find_by_owner(self, owner_id: int) -> list[Project]: ...
def find_by_id(self, project_id: int) -> Project | None: ...
class CreateProjectUseCase:
MAX_PROJECTS_PER_USER = 10
def __init__(self, repository: ProjectRepository):
self.repository = repository
def execute(self, name: str, description: str, owner_id: int) -> Project:
if not name or not name.strip():
raise ValueError("שם הפרויקט לא יכול להיות ריק")
if len(name) > 100:
raise ValueError("שם הפרויקט לא יכול לעלות על 100 תווים")
existing_projects = self.repository.find_by_owner(owner_id)
active_projects = [p for p in existing_projects if not p.is_archived]
if len(active_projects) >= self.MAX_PROJECTS_PER_USER:
raise ValueError(f"לא ניתן ליצור יותר מ-{self.MAX_PROJECTS_PER_USER} פרויקטים פעילים")
project = Project(
id=0,
name=name.strip(),
description=description,
owner_id=owner_id,
created_at=datetime.now()
)
return self.repository.save(project)
תרגיל 3 - מתאם - פתרון¶
# adapters/in_memory_project_repository.py
from entities.project import Project
from use_cases.project_use_cases import ProjectRepository
class InMemoryProjectRepository(ProjectRepository):
def __init__(self):
self._projects: dict[int, Project] = {}
self._next_id = 1
def save(self, project: Project) -> Project:
if project.id == 0:
project.id = self._next_id
self._next_id += 1
self._projects[project.id] = project
return project
def find_by_owner(self, owner_id: int) -> list[Project]:
return [p for p in self._projects.values() if p.owner_id == owner_id]
def find_by_id(self, project_id: int) -> Project | None:
return self._projects.get(project_id)
תרגיל 4 - בדיקה - פתרון¶
import pytest
from use_cases.project_use_cases import CreateProjectUseCase
from adapters.in_memory_project_repository import InMemoryProjectRepository
def test_cannot_create_more_than_10_projects():
repo = InMemoryProjectRepository()
use_case = CreateProjectUseCase(repo)
# יצירת 10 פרויקטים
for i in range(10):
use_case.execute(f"פרויקט {i + 1}", "תיאור", owner_id=1)
# הניסיון ה-11 צריך להיכשל
with pytest.raises(ValueError, match="לא ניתן ליצור"):
use_case.execute("פרויקט 11", "תיאור", owner_id=1)
def test_can_create_project_with_valid_data():
repo = InMemoryProjectRepository()
use_case = CreateProjectUseCase(repo)
project = use_case.execute("הפרויקט שלי", "תיאור הפרויקט", owner_id=1)
assert project.id > 0
assert project.name == "הפרויקט שלי"
assert project.owner_id == 1
assert not project.is_archived
def test_cannot_create_project_with_empty_name():
repo = InMemoryProjectRepository()
use_case = CreateProjectUseCase(repo)
with pytest.raises(ValueError, match="ריק"):
use_case.execute("", "תיאור", owner_id=1)
שימו לב: שלוש בדיקות רצות בלי שרת, בלי SQLite, בלי FastAPI, בלי HTTP. זו עוצמת הארכיטקטורה הנקייה.