Modélisation du domaine

Au cœur de toute application orientée domaine se trouve le modèle qui représente la connaissance centrale et les règles métier du système. Bien modéliser le domaine signifie traduire les concepts du monde réel en structures logicielles expressives, cohérentes et consistantes.

Entités

Une entité est un objet défini principalement par son identité et non uniquement par ses attributs. Même si les attributs changent au fil du temps, l'identité d'une entité reste la même.

Caractéristiques principales :

  • Possède une identité unique (généralement un Id).
  • Ce qui importe, c'est qui est l'entité, pas seulement ce qu'elle contient.
  • Ses attributs peuvent changer au fil du temps.

Créer une entité avec Lino

Pour créer une nouvelle entité en utilisant Lino, exécutez :

lino entity new

L'assistant CLI vous demandera :

  • Service – Le service dans lequel l'entité sera créée.
  • Module – Le module dans lequel l'entité sera créée (uniquement dans les services modulaires).
  • Nom de l'entité – Le nom utilisé dans le domaine et dans la table de la base de données.

Ensuite, vous définirez les champs qui composent l'entité en configurant chacun d'eux.

Types de champs disponibles

Type Description Plage / Observations
shortEntier 16 bits-32 768 → 32 767
intEntier 32 bits-2 147 483 648 → 2 147 483 647
longEntier 64 bits-9 223 372 036 854 775 808 → 9 223 372 036 854 775 807
stringTexteJusqu'à environ 2 milliards de caractères
boolValeur booléennetrue ou false
GuidIdentifiant global uniqueUnicité distribuée
decimalNombre décimal haute précisionIdéal pour valeurs monétaires
floatNombre flottant (32 bits)≈ 6–9 chiffres de précision
doubleNombre flottant (64 bits)≈ 15–17 chiffres de précision
DateTimeDate et heureInclut le fuseau horaire
DateOnlySeulement la date (C# 10+)
TimeOnlySeulement l'heure (C# 10+)
EntityRéférence à une autre entité1:1 ou 1:N
Value ObjectValue Object immuableEx.: Adresse, CPF
EnumÉnumérationEnsemble fixe de valeurs
List<Entity>Liste d'entités1:N
ManyToManyPlusieurs-à-plusieursNécessite une table de jointure

Exemple

Création de l'entité Person :

┌────┬────┬───────────────┬────────┬────────┬───────────┬────────────────┐
│ PK │ FK │ Property name │ Type   │ Length │ Required  │ Auto-increment │
├────┼────┼───────────────┼────────┼────────┼───────────┼────────────────┤
│ x  │    │ Id            │ int    │        │     x     │       x        │
├────┼────┼───────────────┼────────┼────────┼───────────┼────────────────┤
│    │    │ Name          │ string │  100   │     x     │                │
└────┴────┴───────────────┴────────┴────────┴───────────┴────────────────┘

Structure générée par Lino :

<ProjectName>/
└── src/
    └── Services/
        └── <ServiceName>/
            └── Domain/
                ├── <ProjectName>.<ServiceName>.Domain.csproj
                └── Aggregates/
                    └── People/
                        ├── Person.cs
                        ├── Errors/
                        │   └── PersonErrors.cs
                        ├── Repositories/
                        │   └── IPersonRepository.cs
                        └── Resources/
                            └── Person/
                                ├── PersonResources.resx
                                ├── PersonResources.en.resx
                                └── PersonResources.pt-BR.resx

Après avoir défini vos entités, utilisez Lino lui-même pour gérer les Migrations et garder la base de données synchronisée. Ce processus sera détaillé dans la section Couche de Persistance.

Flux actuel d'évolution

En plus du flux interactif avec lino entity new, la CLI permet aussi d'exprimer explicitement l'intention dans la commande lorsque le service, le module et le nom sont déjà connus :

lino entity new --name <EntityName> --service <ServiceName> --module <ModuleName>
lino entity edit --service <ServiceName> --module <ModuleName> --entity <EntityName>
lino entity list --service <ServiceName> --module <ModuleName>

Utilisez entity list avant de créer de nouveaux concepts dans de grands modules. Pendant l'édition, vérifiez l'identifiant, les propriétés, l'obligation, la longueur, les relations, les index, l'ownership, le tenant et l'impact sur les migrations.

Strongly Typed IDs, ownership et invariants

Lorsque les Strongly Typed IDs sont activés, l'identifiant devient un type dédié, comme ProductId, ce qui réduit les confusions accidentelles entre IDs d'entités différentes. Les relations doivent refléter l'ownership réel du domaine : toute référence n'a pas besoin de devenir une navigation directe ; souvent, un identifiant, une shadow entity, un événement d'intégration ou une requête explicite protège mieux les limites entre modules.

Placez les invariants dans le domaine, pas seulement dans l'UI, la base de données ou les validators. Après avoir modifié des entités, exécutez un build, vérifiez le diff et générez ou mettez à jour les migrations.

Value Objects

Un Value Object représente un concept du domaine défini uniquement par ses attributs – il ne possède pas d’identité propre. Deux Value Objects sont considérés comme égaux si toutes leurs valeurs sont identiques.

Principales caractéristiques :

  • Immuables après création.
  • Ne possèdent pas de Id.

Créer un Value Object avec Lino

Exécutez :

lino value-object new

Le CLI demandera :

  • Service – Service dans lequel l’objet sera créé.
  • Module – Module dans lequel l’objet sera créé (uniquement dans les services modulaires).
  • Emplacement – Racine du domaine ou agrégat spécifique.
  • Nom de l’Value Object.

Puis définissez les champs qui composent l’objet.

Types de champs disponibles

TypeDescriptionRemarques
shortEntier 16 bits-32 768 → 32 767
intEntier 32 bits-2 147 483 648 → 2 147 483 647
longEntier 64 bits-9 223 372 036 854 775 808 → 9 223 372 036 854 775 807
stringTexteJusqu’à ~2 milliards de caractères
boolBooléentrue/false
decimalDécimal précisValeurs monétaires
floatNombre à virgule flottante (32 bits)≈ 6–9 chiffres
doubleNombre à virgule flottante (64 bits)≈ 15–17 chiffres
DateTimeDate/heureInclut le fuseau horaire
DateOnlySeulement la dateC# 10+
TimeOnlySeulement l’heureC# 10+

Exemple

Value Object Address :

┌───────────────┬────────┬────────┬───────────┐
│ Property name │ Type   │ Length │ Required  │
├───────────────┼────────┼────────┼───────────┤
│ Street        │ string │  100   │     x     │
├───────────────┼────────┼────────┼───────────┤
│ Number        │ string │   10   │     x     │
├───────────────┼────────┼────────┼───────────┤
│ Neighborhood  │ string │   50   │           │
├───────────────┼────────┼────────┼───────────┤
│ City          │ string │  100   │     x     │
├───────────────┼────────┼────────┼───────────┤
│ State         │ string │   2    │     x     │
├───────────────┼────────┼────────┼───────────┤
│ PostalCode    │ string │   20   │     x     │
├───────────────┼────────┼────────┼───────────┤
│ Country       │ string │  100   │     x     │
└───────────────┴────────┴────────┴───────────┘

Structure des fichiers générée (agrégat Person) :

<ProjectName>/
└── src/
    └── Services/
        └── <ServiceName>/
            └── Domain/
                ├── <ProjectName>.<ServiceName>.Domain.csproj
                └── Aggregates/
                    └── People/
                        ├── Person.cs
                        ├── ValueObjects/
                        │   └── Address.cs
                        ├── Errors/
                        │   ├── AddressErrors.cs
                        │   └── PersonErrors.cs
                        ├── Repositories/
                        │   └── IPersonRepository.cs
                        └── Resources/
                            ├── Address/
                            │   ├── AddressResources.resx
                            │   ├── AddressResources.en.resx
                            │   └── AddressResources.pt-BR.resx
                            └── Person/
                                ├── PersonResources.resx
                                ├── PersonResources.en.resx
                                └── PersonResources.pt-BR.resx

Comme pour les entités, les Migrations peuvent être gérées par Lino pour maintenir la synchronisation du modèle de données.

Flux actuel d'évolution

En plus de la commande interactive, utilisez des paramètres lorsque vous savez déjà où le concept doit être créé :

lino value-object new --name <ValueObjectName> --service <ServiceName> --module <ModuleName>
lino value-object edit --service <ServiceName> --module <ModuleName> --value-object <ValueObjectName>
lino value-object list --service <ServiceName> --module <ModuleName>

Lorsqu'une propriété d'entité est déclarée comme ValueObject, Lino peut réutiliser un Value Object existant ou en créer un nouveau pendant la modélisation de l'entité. Préférez cette option lorsque l'ensemble de champs représente un seul concept, comme l'argent, l'adresse, les dimensions, la période ou le document.

Persistance, UI et localisation

Lino mappe les propriétés des Value Objects via la couche de persistance du module et transporte les métadonnées d'affichage vers les resources. Cela permet des labels et messages localisés pour les valeurs imbriquées. Le Value Object appartient au module où il a été généré ; il ne devient pas un contrat partagé entre modules sans intégration explicite.

Protégez les invariants dans le type lui-même : les valeurs négatives, une devise invalide, des périodes inversées ou des documents mal formés ne doivent pas naître valides.

Énumérations

Les énumérations en DDD peuvent aller au-delà des enum traditionnels de C#. Elles peuvent être des objets riches représentant des états fixes, contenant des validations, des méthodes auxiliaires et même des comportements.

Motivation :

  • Les enum de C# sont limités à une valeur entière ou chaîne.
  • Modéliser une Enumeration comme une classe offre plus de flexibilité et d’expressivité.

Caractéristiques principales :

  • Ce sont des classes héritant d’une base commune et encapsulant Id et Nom.
  • Elles permettent d’ajouter des validations, des méthodes auxiliaires et des comportements.

Créer une énumération avec Lino

Exécutez :

lino enumeration new

L’assistant vous demandera :

  • Service.
  • Module (si applicable).
  • Emplacement – racine du domaine ou agrégat.
  • Nom de l’énumération.
  • Typeenum traditionnel ou Smart Enum (class).
  • Stockageint ou string en base de données.

Exemple

Énumération PersonStatus :

┌───────┬───────────┬──────────────┐
│ Value │ Name      │ Display Name │
├───────┼───────────┼──────────────┤
│ 1     │ Active    │ Active       │
├───────┼───────────┼──────────────┤
│ 2     │ Inactive  │ Inactive     │
├───────┼───────────┼──────────────┤
│ 3     │ Suspended │ Suspended    │
├───────┼───────────┼──────────────┤
│ 4     │ Deleted   │ Deleted      │
└───────┴───────────┴──────────────┘

Structure générée :

<ProjectName>/
└── src/
    └── Services/
        └── <ServiceName>/
            └── Domain/
                ├── <ProjectName>.<ServiceName>.Domain.csproj
                └── Aggregates/
                    └── People/
                        ├── Person.cs
                        ├── Enums/
                        │   └── PersonStatus.cs
                        ├── ValueObjects/
                        │   └── Address.cs
                        ├── Errors/
                        │   ├── AddressErrors.cs
                        │   └── PersonErrors.cs
                        ├── Repositories/
                        │   └── IPersonRepository.cs
                        └── Resources/
                            ├── Address/
                            │   ├── AddressResources.resx
                            │   ├── AddressResources.en.resx
                            │   └── AddressResources.pt-BR.resx
                            ├── Person/
                            │   ├── PersonResources.resx
                            │   ├── PersonResources.en.resx
                            │   └── PersonResources.pt-BR.resx
                            └── PersonStatus/
                                ├── PersonStatusResources.resx
                                ├── PersonStatusResources.en.resx
                                └── PersonStatusResources.pt-BR.resx

Stocker la valeur d’une énumération comme string est valide et peut améliorer la lisibilité, mais tend à être moins efficace en termes de performances et de stockage. C’est pourquoi nous recommandons de stocker la valeur comme int, et pour maintenir l’intégrité référentielle et faciliter la maintenance, de créer une entité (table) auxiliaire dont la clé primaire correspond à la valeur de l’énumération.

Après avoir défini les énumérations, utilisez Lino pour générer et appliquer les Migrations, garantissant que la base de données reflète le modèle de domaine. Voir les détails dans la section Couche de Persistance.

Flux actuel d'évolution

En plus de la commande interactive, utilisez des paramètres lorsque l'emplacement et le nom sont déjà définis :

lino enumeration new --name <EnumerationName> --service <ServiceName> --module <ModuleName>
lino enumeration edit --service <ServiceName> --module <ModuleName> --enumeration <EnumerationName>
lino enumeration list --service <ServiceName> --module <ModuleName>

Enumeration, entité ou configuration

Utiliser une enumerationUtiliser une entité ou une configuration
Statut de commande, état de publication, type technique d'intégrationCatégorie administrable par l'utilisateur, motif configurable par tenant, fournisseur géré dans le backoffice
Valeurs versionnées avec le codeValeurs modifiées au runtime ou contrôlées par des permissions
Règle simple ou petit comportement par valeurCycle de vie, audit, traduction dynamique ou relation propre

Si l'enumeration expose un nom d'affichage, Lino peut générer des resources pour des labels localisés. Si elle est persistée ou utilisée comme seed data, vérifiez les migrations après l'ajout, la suppression ou le renommage de valeurs. La cohérence entre le code, la base de données et l'UI fait partie du changement de domaine.

Une erreur non gérée est survenue. Rafraîchir 🗙