Learn how to create new features following Clean Architecture and DDD principles
The Soft-Bee API uses a feature-based architecture where each feature is a self-contained module following Clean Architecture and Domain-Driven Design (DDD) principles.
The project includes an automated script to generate the feature structure.
1
Run the Feature Creation Script
Execute the create_feature.sh script with your feature name:
./create_feature.sh <feature_name>
Example:
./create_feature.sh apiary
This will create the complete directory structure and all necessary __init__.py files.
2
Define Domain Entities
Start by creating your domain entities in domain/entities/. Entities represent core business objects.Example:src/features/auth/domain/entities/user.py:9-99
from dataclasses import dataclass, fieldfrom datetime import datetimefrom typing import Optional, Listfrom ...domain.value_objects.email import Emailfrom ...domain.events.auth_events import UserRegisteredEvent@dataclassclass User: """Entidad User para autenticación""" email: Email username: str hashed_password: str id: Optional[str] = None is_active: bool = True is_verified: bool = False last_login: Optional[datetime] = None created_at: datetime = field(default_factory=datetime.utcnow) updated_at: datetime = field(default_factory=datetime.utcnow) # Domain events _events: List = field(default_factory=list, init=False, repr=False) def __post_init__(self): self.validate() def validate(self): """Validar reglas de negocio""" if len(self.username) < 3: raise InvalidUserException("Username must be at least 3 characters") if not self.email.is_valid(): raise InvalidUserException("Invalid email address") def login_successful(self): """Manejar login exitoso""" self.last_login = datetime.utcnow() self.failed_login_attempts = 0 self.updated_at = datetime.utcnow() # Registrar evento self._register_event(UserLoggedInEvent( user_id=self.id, email=str(self.email), timestamp=datetime.utcnow() ))
Key Points:
Use @dataclass for clean entity definition
Include validation in __post_init__ or validate()
Implement business logic as methods
Use domain events for side effects
3
Create Value Objects
Define value objects in domain/value_objects/. Value objects are immutable and defined by their attributes.Example: Email value object
from dataclasses import dataclassimport re@dataclass(frozen=True)class Email: """Value Object para email""" value: str def __post_init__(self): if not self.is_valid(): raise ValueError(f"Invalid email: {self.value}") def is_valid(self) -> bool: pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' return bool(re.match(pattern, self.value)) def __str__(self): return self.value
4
Define Repository Interfaces
Create repository interfaces in application/interfaces/repositories/.Example: User repository interface
from abc import ABC, abstractmethodfrom typing import Optionalfrom ...domain.entities.user import Userclass IUserRepository(ABC): """Interface for User repository""" @abstractmethod def save(self, user: User) -> User: """Save a user""" pass @abstractmethod def find_by_id(self, user_id: str) -> Optional[User]: """Find user by ID""" pass @abstractmethod def find_by_email(self, email: str) -> Optional[User]: """Find user by email""" pass @abstractmethod def exists_by_email(self, email: str) -> bool: """Check if email exists""" pass
5
Create DTOs
Define Data Transfer Objects in application/dto/ for request/response data.Example: Authentication DTOs
from dataclasses import dataclassfrom datetime import datetimefrom typing import Optional@dataclassclass RegisterRequestDTO: """DTO for user registration request""" email: str username: str password: str@dataclassclass RegisterResponseDTO: """DTO for user registration response""" id: str email: str username: str is_verified: bool created_at: datetime message: str = "User registered successfully"
6
Implement Use Cases
Create use cases in application/use_cases/. Use cases orchestrate business logic.Example:src/features/auth/application/use_cases/register_user.py:9-69