-
-
Notifications
You must be signed in to change notification settings - Fork 0
DDD Value Objects
Cedric edited this page Feb 25, 2025
·
1 revision
Un Value Object (Objet Valeur) est un objet immuable qui décrit une caractéristique du domaine sans identité propre. Son égalité est déterminée par ses attributs plutôt que par une identité.
classDiagram
class Money {
-amount: float
-currency: str
+add(Money) Money
+convert_to(str) Money
}
class Email {
-value: str
+domain() str
+is_valid() bool
}
class Address {
-street: str
-city: str
-zip_code: str
+formatted() str
}
- Immuabilité : Une fois créé, son état ne change pas
- Pas d'Identité : Défini uniquement par ses valeurs
- Vie Éphémère : Peut être recréé à volonté
- Égalité par Valeur : Deux VOs sont égaux si leurs attributs sont identiques
# domain/common/value_objects.py
class Email:
__slots__ = ('_value',) # Immutabilité
def __init__(self, value: str):
if not self._is_valid(value):
raise InvalidEmailError()
self._value = value.lower().strip()
@staticmethod
def _is_valid(value: str) -> bool:
return re.match(r"[^@]+@[^@]+\.[^@]+", value) is not None
@property
def domain(self) -> str:
return self._value.split('@')[1]
def __eq__(self, other) -> bool:
return isinstance(other, Email) and self._value == other._value
# Usage dans une Entité
user = User(email=Email("test@example.com"))class Money:
def __init__(self, amount: float, currency: str):
self.amount = round(amount, 2)
self.currency = currency.upper()
def add(self, other: 'Money') -> 'Money':
if self.currency != other.currency:
raise CurrencyMismatchError()
return Money(self.amount + other.amount, self.currency)
def __repr__(self):
return f"{self.currency} {self.amount:.2f}"- Réduction de la duplication : Logique centralisée (ex: validation email)
-
Code plus expressif :
# ❌ Sans VO if '@' in user_email and user_email.count('.') >= 1: ... # ✅ Avec VO if user.email.domain == 'premium.com': ...
- Sécurité accrue : Immuabilité prévient les modifications accidentelles
- Testabilité : Logique isolée et réutilisable
-
Taille Minimaliste :
- Garder les VOs simples (max 3-4 attributs)
- Éviter les dépendances externes dans les VOs
-
Validation Précoce :
class PhoneNumber: def __init__(self, number: str): cleaned = re.sub(r'\D', '', number) if len(cleaned) < 10: raise InvalidPhoneNumberError() self._number = cleaned
-
Immuabilité en Python :
- Utiliser
__slots__ - Lever
AttributeErrorsur les setters - Préférer les
@propertycalculées
- Utiliser
-
Conversion Facile :
class Price: def to_dict(self) -> dict: return {'amount': self.amount, 'currency': self.currency} @classmethod def from_dict(cls, data: dict) -> 'Price': return cls(data['amount'], data['currency'])
| Critère | Entité | Value Object |
|---|---|---|
| Identité | ID unique | Valeurs des attributs |
| Cycle de vie | Suivi | Jetable |
| Mutabilité | Modifiable | Immuable |
| Exemple |
User, Order
|
Email, Address
|
-
Primitive Obsession :
# ❌ def create_user(email: str, password: str): ... # ✅ def create_user(email: Email, password: Password): ...
-
Mutations Cachées :
# ❌ vo.value += 10 # Brise l'immuabilité # ✅ new_vo = vo.add(10)
-
Sur-Engineering :
- Créer des VOs pour des primitives simples sans logique
- Tous les attributs sont-ils immuables ?
- L'égalité est-elle basée sur les attributs ?
- La validation est-elle complète ?
- Les méthodes retournent-elles de nouvelles instances ?
- Le VO a-t-il une responsabilité unique ?
📁 Structure du projet | 📚 Documentation du Wiki | 🐛 Signaler un bug
| Composant | Technologies clés |
|---|---|
| Core | Python 3.12, Clean Architecture, DDD |
| Infra | Flask, SQLAlchemy, Pydantic |
| Qualité | pytest, mypy, ruff, pre-commit |
| DevOps | Docker, Makefile, GitHub Actions |
👷 Maintenu par Cédric | 💡 Inspiré par Robert C. Martin (Clean Architecture) et Eric Evans (DDD)
flask-boilerplate/
├── domain/ # Couche métier pure
├── application/ # Orchestration des cas d'utilisation
├── infrastructure/ # Implémentations techniques
├── presentation/ # Contrôleurs et endpoints
└── tests/ # Tests automatisés