Skip to content

DDD Value Objects

Cedric edited this page Feb 25, 2025 · 1 revision

🎯 Domain-Driven Design - Value Objects

🧱 Définition

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
    }
Loading

🔑 Caractéristiques Clés

  1. Immuabilité : Une fois créé, son état ne change pas
  2. Pas d'Identité : Défini uniquement par ses valeurs
  3. Vie Éphémère : Peut être recréé à volonté
  4. Égalité par Valeur : Deux VOs sont égaux si leurs attributs sont identiques

💡 Exemples Concrets

1. Email (Validation Intégrée)

# 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"))

2. Money (Logique Métier)

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ôle dans le Projet

Avantages Clés :

  • 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

📌 Best Practices Projet

  1. Taille Minimaliste :

    • Garder les VOs simples (max 3-4 attributs)
    • Éviter les dépendances externes dans les VOs
  2. 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
  3. Immuabilité en Python :

    • Utiliser __slots__
    • Lever AttributeError sur les setters
    • Préférer les @property calculées
  4. 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'])

🔄 Comparaison Entité vs Value Object

Critère Entité Value Object
Identité ID unique Valeurs des attributs
Cycle de vie Suivi Jetable
Mutabilité Modifiable Immuable
Exemple User, Order Email, Address

🚫 Anti-Patterns Courants

  1. Primitive Obsession :

    # ❌
    def create_user(email: str, password: str): ...
    
    # ✅
    def create_user(email: Email, password: Password): ...
  2. Mutations Cachées :

    # ❌
    vo.value += 10  # Brise l'immuabilité
    
    # ✅
    new_vo = vo.add(10)
  3. Sur-Engineering :

    • Créer des VOs pour des primitives simples sans logique

✅ Checklist de Conception

  • 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 ?

➡️ Prochaines Étapes

🏗️ flask-boilerplate

🔍 Navigation


🧠 Concepts Clés

🧩 Clean Architecture

🎯 Domain-Driven Design


📂 Structure du Projet

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

🛠️ Guides & Tutoriels

  1. Premier cas d'utilisation
  2. Tests d'intégration
  3. Déploiement Docker
  4. Migration de base

🤝 Contribution


🔗 Liens Utiles

Clone this wiki locally